在上篇中提到了「下篇预告」和「下下篇预告」, 这两部分尽管都已经研究过了, 但是似乎讲述顺序上不太合理. 所以这里要出尔反尔, 打乱一下讲述顺序了. 囧
内存管理是一个很重要的部分.
---------------------------------
为何需要内存管理
这似乎是一个很废话的问题, 但是对于我等没有接触过「操作系统」课程的, 曾经比较颓废的青年来说, 还是很必要的. 后悔莫及已经逝去的岁月, 罢了罢了, 已经过去的事情就不要谈了.
譬如, 你的用户程序需要
nmem_blk * blk_size
大小的内存块, 那么你可以使用 malloc(nmem_blk * blk_size)
来获取之. 但是这样有几个缺点: 这个函数的执行时间并不确定, 对于实时系统而言, 这不是一个好兆头; 经常的 Type *p = (Type *)malloc(N); free(p);
会导致大量的「内存碎片」产生.说实话, 「内存碎片」这个术语我今天是第一次听说, 真的是很汗颜. 决定开始仔细研究OS相关课程, 包括「深入理解计算机系统」这样的书籍.
为了避免这样的情况,
μC/OS-II
引入了自己的内存管理算法. 其执行时间是常数, 而且有效避免了内存碎片中的所谓「外部碎片」, external fragments, 也就是最让嵌入式开发头疼的内存碎片类型了.---------------------------------
内存管理函数接口
如果不需要动态分配内存, 可以将其关闭,
#define OS_MEM_EN 0
, 反之开启, #define OS_MEM_EN 1
.接口函数:
/*********************** 创建一个内存分区; 分区内含有nblks个内存块, 每个内存块大小是blksize个字节; 返回值是一个指针, 指向此分区的内存控制块. ***********************/ OS_MEM *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err); /* 指定一个内存分区, 从中拿出一个空闲内存块; 返回值是一个指针, 指向这个内存块 */ void *OSMemGet (OS_MEM *pmem, INT8U *err); /* 将pblk指向的内存块, 放回pmem所控制的内存分区 */ INT8U OSMemPut (OS_MEM *pmem, void *pblk); /* 获取pmem所控制的内存分区的信息, 放入pdata中 */ #if OS_MEM_QUERY_EN > 0 INT8U OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *pdata); #endif
---------------------------------
内存控制块
很重要的一个结构体, 用于控制内存分区. 每一个内存 partition 就会有一个 control block.
#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0) typedef struct { void *OSMemAddr; /* Pointer to beginning of memory partition */ void *OSMemFreeList; /* Pointer to list of free memory blocks */ INT32U OSMemBlkSize; /* Size (in bytes) of each block of memory */ INT32U OSMemNBlks; /* Total number of blocks in this partition */ INT32U OSMemNFree; /* Number of memory blocks remaining in this partition */ } OS_MEM; #endif
注释里都阐述的很清楚了, 就不再赘述了.
---------------------------------
有点不想写了, 貌似写这类系列文章好麻烦. 篇幅太大, 时间不够. 直接就贴图吧. 反正主要是备份所需嘛. 囧
系统初始化时, 建立的空闲内存控制块链表:
创建内存分区
OSMemCreate()
函数, 将内存分区, memory partition, 中的所有内存块, memory blocks, 用一个单向链表连接起来, 这一点非常关键:---------------------------------
具体的源码实现
不分析了, 大家伙下载阅读吧. -_-
这里说一下注意点:
#1
OS_ARG_CHK_EN
宏定义用于指出「是否检测给系统函数传递的参数」, 如果置零, 将会压缩一部分代码空间, 但是最好还是置一.#2
内存控制块中, 有一个
void *OSMemFreeList;
. 其用途有两个:1. 当这个内存控制块未被赋值给某个内存分区时, 也就是当这个控制块依然是处于「空闲内存控制块链表」中时,
OSMemFreeList
是指向链表中的下一个空闲内存控制块.2. 此控制块被分配给某个partition后,
OSMemFreeList
指向partition中的下一个free memory block 空闲内存块. 换句话说, 此时的 OSMemFreeList
指向分区中的空闲内存块单向链表的头结点.#3
内存控制块中的
OSMemFreeList
内部标识符, 和系统的全局变量 OSMemFreeList
相同, 务必注意区分. 可以参考上面两张图片的第一张.#4
释放某个内存块,
INT8U OSMemPut (OS_MEM *pmem, void *pblk);
.牢记: 系统没法动态确认
pblk
指向的内存块是否是属于 pmem
控制的内存分区.这需要我们自己小心. 不然随时可能会因为内存分配问题而程序崩溃.
#5
在某个 partition 没有free block 的情况下, 让某个申请内存块的任务暂时进入waiting状态, 是可以实现的.
系统并不支持, 但是可以对某个特定的内存分区使用计数型信号量, 就ok了.
#6
在阅读源码的过程中, 你可能会碰见类似这样的代码:
plink = (void **)addr; /* Create linked list of free memory blocks */ pblk = (INT8U *)addr + blksize; for (i = 0; i < (nblks - 1); i++) { *plink = (void *)pblk; plink = *plink; pblk = pblk + blksize; } *plink = (void *)0; /* Last memory block points to NULL */
OS_ENTER_CRITICAL(); if (pmem->OSMemNFree > 0) { /* See if there are any free memory blocks */ pblk = pmem->OSMemFreeList; /* Yes, point to next free memory block */ pmem->OSMemFreeList = *(void **)pblk; /* Adjust pointer to new free list */ pmem->OSMemNFree--; /* One less memory block in this partition */ OS_EXIT_CRITICAL(); *err = OS_NO_ERR; /* No error */ return (pblk); /* Return memory block to caller */ } OS_EXIT_CRITICAL();我对于这两段代码非常惭愧, 因为我经常自认为对指针等非常熟练, 可惜这个指向指针的指针
*(void **) pblk;
让我想了差不多10分钟才想明白. 真是羞愧难当啊.现在简单阐述如下:
pblk指向某物理地址addr_pblk;
(addr_pblk)中所存储的内容, 也是一个指针, 称作ptr_a, ptr_a指向物理地址addr_a, 也就是说, ptr_a中存储的是物理地址addr_a;
那么, pblk本质上就是指向指针的指针.
但是, 由于pblk的类型是 void *pblk, 所以如果简单的使用 *pblk, 得到的将是addr_pblk中的具体内容, 也就是物理地址addr_a的数值表示, 而非指针!
因此先强制类型转换 (void **)pblk, 再取值, *(void **)pblk, 就可以得到指向addr_a的指针了.
- EOF -
没有评论:
发表评论
不要使用过激的暴力或者色情词汇.
不要充当勇猛小飞侠 --- 飘过 飞过 扑扑翅膀飞走 被雷得外焦里嫩地飞走.
万万不可充当小乌龟 --- 爬过.
构建河蟹社会 责任你有 我有 大家有 -_-