uCos的内存管理

作为一个操作系统内核,必须有向用户提供申请和释放内存的服务,uCos作为一个实时操作系统也不例外。

内存的动态申请和释放在嵌入式编程中经常用到,比如我们需要给另外一个任务发送一个消息,我们就可以在发送消息前,申请(OSMemGet )一个内存块,然后把这个内存块作为消息发送出去(OSQPost ),消息被处理完后,内存块释放(OSMemPut)掉,但如果不用内存块来存放消息内容,比如用一个全局的数组,也是可以实现这样的效果的,但这样做,管理起来会麻烦点,发送消息的时间和数量是不确定的,那么可能需要定义多个这样的数组,而且这些数组需要进行互斥访问,事实上,uCos已经帮我们做好了这些管理,我们只需要使用这些服务就可以了,而且这些服务质量是很高滴。

uCos的内存服务不同于我们使用mallocmfree那样可以申请和释放任意大小的内存块,由于嵌入式软件的特殊性,程序里需要申请的内存块大小和数量是可以预先估计和计算的,换句话说,我们根本不需要分配任意大小的内存块,我们只需要申请本应用中确定的几种内存块就足够了,比如我们在程序中定义串口通讯的数据包大小是32个字节,则我们在任务间传递这样的串口数据包时,需要用的内存块大小就可以固定为32字节,当然在PC中,这不行,因为用户的应用程序是不可预知的,在嵌入式程序中,则是可行的。

uCos的内存服务正如上面所说的,我们可以向uCos申请一些特定大小的内存块,当然这些内存块的大小是我们可以设定的,我们首先告诉uCos我们需要的内存块大小为多少,以及这样的内存块我们最多需要多少个,然后uCos帮我们建立和管理起来,以后申请和释放这个特定大小的内存块了。

先介绍下uCos的内存管理的程序模型,这个模型事实上不复杂,甚至很简单,但简单并意味着简陋。

uCos的内存服务使用实例如下:

INT8U   Mem_Of_Buff[100][32];

OS_MEM  *Mem_Buff;

void uCos_mem_test(void)

{

INT8U err;

INT8U p_got_mem;

/* 创建一个内存分区Mem_Buff,创建后该内存分区里将有100个大小为32字节的内存块 */

Mem_Buff = OSMemCreate(Mem_Of_Buff, 100, 32, &err);

/* 从内存分区Mem_Buff中申请一个内存块 */

p_got_mem = (INT8U *)OSMemGet(Mem_Buff,&err);

/*把之前申请到的内存块释放回内存分区Mem_Buff */

OSMemPut (Mem_Buff,p_got_mem);

}

从上面的例子中,可以看出要使用uCos的内存管理服务很简单,事实上,uCos的内存管理服务提供的函数总共才4个,她们分别如下:

OS_MEM  *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err);

void  *OSMemGet (OS_MEM *pmem, INT8U *err);

INT8U  OSMemPut (OS_MEM  *pmem, void *pblk);

INT8U  OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *pdata);

void  OS_MemInit (void)--- 这个函数用户不能调用

这里又要特别注意,OS_MemInit这个函数是由OSInit这个函数调用的,用户不能也不需要主动调用OS_MemInit函数。下面我们仔细分析这几个函数来学习uCos的内存管理是怎么实现的。

uCos的内存管理程序的编程大概思路可概况如下:uCos用一个结构体OS_MEM来管理一个内存分区,同时根据用户的配置定义了一个元素类型为OS_MEM的数组OSMemTbl来存放所有的OS_MEM的结构体,同时这个OSMemTbl数组里的每个元素用链表连接起来,当用户建立一个分区时,则从该链表里取出一个元素来管理新建立的分区。所以我们调用OSMemCreate来建立一个新的内存分区时,返回的是一个指向OS_MEM的指针,实际上,返回的就是这个内存管理控制块的其中某一块的首地址。

内存管理控制块的链表结构如下:

 

如上图所示,OSMemFreeList是一个全局变量,他指向链表头结点。OS_MAX_MEM_PART是一个供用户配置的宏,所以用户建立的内存分区数不能超过OS_MAX_MEM_PART这个值,否则建立分区失败。这里特别注意OS_MEM结构体里的OSMemFreeList成员,该成员在该内存管理控制块未被使用前是用来指向下一个空闲的内存管理控制块,但当该控制块被用于管理一个特定的分区后,OSMemFreeList将被用户指向该分区的下一个空闲内存块。

在调用OSMemCreate建立分区时,uCos会将该分区里的每个内存块通过链表连接起来,同时把内存管理控制块的OSMemFreeList成员指向该链表的头结点。

当用户申请一个内存块时,就从该链表里取出一个元素,当用户释放一个内存块时,就把该内存块添加到链表里。从上图可以看出,每个内存块的启始地址存放的是下一个空闲内存块的起始地址,uCos在这里巧妙利用了内存块本身来构建链表,使得内存的使用效率大大提高,由此也可以看出,每个内存块的大小至少应该能存放一个指针。而且由于内存分区里的每个内存块的首地址被用来存放一个指针,所以在内存分区里的内存必须在调用OSMemGet获得该内存后,才能进行操作,不允许直接操作内存分区里的内存块,尽管我们可以直接对内存分区里的每个内存块进行操作。

*******************************************************

ucos-II的内存管理提供了对某块完整内存的修改,功能非常简单NBNC的代码大概只有200多行,在整个内核中也基本是独立的。它通过如下方式对内存块进行管理:

复制代码
OS_MEM *mem;
INT8U buff[16][128];

void main(void)
{
    INT8U err; 
    mem = OSMemCreate(&buff[0][0],16,128,&err);  
}
复制代码

下文分析os_mem.c文件,从全局变量的定义与接口函数的实现两方面记录ucos-II的内存管理机制。

1. MEM的全局变量

OS_EXT  OS_MEM *OSMemFreeList; /* Pointer to free list of memory partitions       */
OS_EXT  OS_MEM OSMemTbl[OS_MAX_MEM_PART]; /* Storage for memory partition manager            */

上面两个全局变量在内核初始化中由OS_MemInit初始,用户试图进行管理的一个内存块,如上例中的buff,对应内核中的一个OS_MEM资源,OS_MEM结构体定义如下:

复制代码
typedef struct { /* MEMORY CONTROL BLOCK                                     */
    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;
复制代码

2. MEM的接口函数

  1. 建立内存分区OSMemCreate:在该函数中一个空闲的OS_MEM资源通过OSMemFreeList被获取,同时这个内存分区内的内存块被初始化为一个起始地址是OSMemAddr的链表。我认为这个链表的定义是非常巧妙的,当前节点的值存储下一个节点的地址,一次类推,代码如下(映像中linux内核中的万用链表也是类似的机制):
复制代码
    plink = (void **)addr;  //addr是内存分区的起始地址、blksize是内存分区中每个内存块的大小           
    pblk  = (INT8U *)addr + blksize;
    for (i = 0; i < (nblks - 1); i++) {
        *plink = (void *)pblk; //相当于把pNext的地址复制给pCur的值
        plink  = (void **)pblk;
        pblk   = pblk + blksize;
    }
    *plink              = (void *)0;                 
    pmem->OSMemAddr     = addr;                       
    pmem->OSMemFreeList = addr;                       
复制代码

     2. 主要接口函数诸如获取一个内存块或者释放一个内存块,都是对上述链表的操作。但由于链表的定义,链表的插入和删除变得很简单。例如返回一个内存块给内存分区:

    *(void **)pblk      = pmem->OSMemFreeList; //把当前的pFree作为pBlk的值 
    pmem->OSMemFreeList = pblk;//pblk赋值给pFree
    pmem->OSMemNFree++;  

返回前:pData -> OSMemFreeList(old),
返回后: pData -> pblk -> OSMemFreeList(Old); pblk成为当前的OSMemFreeList(New)

获取一个内存块给内存分区:

    pblk                = pmem->OSMemFreeList; //当前空节点的地址返回 
    pmem->OSMemFreeList = *(void **)pblk; //当前空节点的值(即pNext)赋值给OSMemFreeList       
    pmem->OSMemNFree--;    

其他:OS_MEM_DATA 定义了一个内存块的信息,在OSMemQuery中使用,没有特别的意义。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值