内存管理
在rtos中调用malloc和free函数是非常危险的,因为这样会出现内存碎片,再者由于内存管理算法上的原因,这两个函数的执行时间不确定,在uc中,系统把内存按分区管理,每个分区中包含整数个大小相同的内存块,对malloc和free函数做了改进,以确定执行时间。
每个分区又划分成相等的若干块,根据任务所需内存的大小去寻找分区,找到后分配,释放时回到所属分区,这样解决了内存碎片问题
uc/os是怎样管理内存的?
答:一个数据结构(OS_MEM)内存控制块数据结构(在内存分区状态查询中还定义了一个OS_MEM_DATA数据结构),五个函数(分区建立、内存块分配、释放、内存分区状态查询、内存控制块链表初始化)
如果没有没有可用的空余内存块了咋办?
答:1、让这个申请内存块的任务等待
2、可以使用计数型信号量,只有拿到信号量才能申请内存。
转:
内存管理模块主要由一个数据结构体和五个函数组成:
◇ 内存控制块数据结构OS_MEM
◇ 内存分区建立函数OSMemCreate()
◇ 内存块分配函数OSMemGet()
◇ 内存块释放函数OSMemPut()
◇ 内存分区状态查询函数OSMemQuery()
◇ 内存控制块链表初始化函数OSMemInit()
他们一同构成内存管理模块来对需要管理的内存块进行简单的管理――分配(动态分配)和
释放(动态回收);
◇ 内存控制块数据结构OS_MEM
typedef struct {
void *OSMemAddr; 指向分区起始地址的指针
void *OSMemFreeList; 指向下一个空余控制块的指针
INT32U OSMemBlkSize; 内存分区中内存的大小
INT32U OSMemNBlks; 分区中总的内存数量
INT32U OSMemNFree; 内存分区中当前可以获得的空余内存块数
} OS_MEM;
系统中每个内存分区必须有一个属于自己的内存控制块,只有这样,内存管理模块中的五个
函数才能对这个内存分区进行管理和操作;
操作系统首先在内存中声明了一个全局的内存控制块数组和指针:
static OS_MEM *OSMemFreeList;
static OS_MEM OSMemTbl[OS_MAX_MEM_PART];
然后在系统初始化的时候调用内存控制块链表初始化函数把这个全局的内存控制块数组
OSMemTbl[ ]构建成一个单向链表,并把这个链表的头指针赋给OSMemFreeList,这样以后,
每当用内存分区建立函数OSMemCreate()建立一个分区时,从这个链表中取出一个内存控制块来对这个内存分区进行管理;内存控制块数组的大小决定系统中内存分区的最大数
目;
◇ 内存分区建立函数OSMemCreate()
要建立一个内存分区,必须具备三个条件:
1. 有供建立内存分区的内存空间:一般处理方法是以二维数组的形式来声明一个变量,这
个变量在编译、链接的时候必定分配给一定的内存空间,这个内存空间只能通过内存块分配
函数来获取使用;
2. 内存控制块数组中有闲置的内存控制块:一般的处理方法是先统计需要建立的内存分区
的数目,然后依此来声明足够的内存控制块;
3.声明一个内存控制块指针,便于以后对这个内存分区的访问;
内存分区建立后,这个二维数组就被构建成了一个单向链表,每个节点就是一个内存块,它
由两部分组成:指向下一个内存块的指针(不能用来作为动态内存来使用)、能够作为动态
内存来使用的存储空间;
◇ 内存块分配函数OSMemGet()
用户创建的任务要使用内存分区中的内存块,就必须通过调用内存分配函数来申请;一般的
处理方法是:先在用户任务中声明一个void类型的指针和一个INTU8 型变量,然后调用
OSMemGet()来从指定的内存分区中申请一个内存块,并把内存块的指针赋给预先声明的
void类型指针;接着对INTU8 型变量进行判断,看申请内存块是否成功。如果成功,就可以
使用这个内存块中可以用动态使用的存储空间。
◇ 内存块释放函数OSMemPut()
用户创建的任务不在使用申请来的内存块的时候,必须及时的调用OSMemPut()来把内存块
释放到相应的内存分区中去。需要注意的是,这个内存块从那个内存分区中申请来的就必须
释放到那个内存分区中去,否则会造成系统崩溃;这个用户在编写任务的时候注意就可以避
免了;
OSMemGet()和OSMemPut()应该成对使用;
◇ 内存分区状态查询函数OSMemQuery()
调用内存分区状态查询函数可以获取一个内存分区的相关信息,它的实现方法就是把内存分
区控制块中的信息拷贝到OS_MEM_DATA的数据结构体中供调用查看、使用;那么大家也许会
问为什么我们不直接通过访问分区控制块来获取这个分区的使用信息呢?这就涉及到代码的
临界区的问题。试想如果我们直接访问内存控制块中的域的话而没有关中断的话,那么如果
时钟节拍到来的时候我们的访问肯定被打断(我们只获取了内存分区的部分信息,还有部分
信息没来得及获取),这个时候通过调度准备就绪而且优先级高的任务得以运行,那么前面
的任务就得等待;试想现在运行的任务如果对这个内存分区进行操作的话,那么上一个任务
获取的内存分区的信息就不一致了,这个可能给我们不可预测的后果;为此在访问内存分区
控制块前,我们得关中断,之后我们还得开中断,这个给我们的移值带来琐碎的麻烦!这也
就是编写这个函数的原因!
下面就内存管理模块来举个例子:
#define BlockNum 100
#define BlockSize 32
OS_MEM *CommRAMA_Ptr;
OS_MEM *CommRAMB_Ptr;
INT8U CommRAMA[BlockNum][BlockSize];
INT8U CommRAMB[BlockNum][BlockSize*4];
… … … …
void main(void)
{
INT8U error;
… … … …
/*用内存分区建立函数OSMemCreate()把这两块RAM存储空间构建成内存 */
/*区,并把管理这两块内存分区的内存分区控制块的地址赋给两个OS_MEM指针*/
CommRAMA_Ptr=OSMemCreate(CommRAMA,BlockNum, BlockSize,error);
CommRAMB_Ptr=OSMemCreate(CommRAMB,BlockNum, BlockSize*2,error);
… … … …
OSStart();
}
void MyTask(void)
{
… … … …
INT8U Error;
OS_MEM_DATA MemInfo;
INT8U BlkA_Ptr,BlkB_Ptr;
… … … …
for(){
Error= OSMemQuery(CommRAMA_Ptr,&MemInfo); ------(1)
If(MemInfo.OSNFree≥2){ ------(2)
BlkA_Ptr=(INT8U *)OSMemGet(CommRAMA_Ptr,&Error); ------(3)
BlkB_Ptr=(INT8U *)OSMemGet(CommRAMA_Ptr,&Error); ------(4)
/*使用获得的内存块*/
… … … …
/*释放获得的内存块*/
OSMemPut(CommRAMA_Ptr,BlkA_Ptr);
OSMemPut(CommRAMA_Ptr,BlkB_Ptr);
}else{
Error= OSMemQuery(CommRAMB_Ptr,&MemInfo);
If(MemInfo.OSNFree≥1){
BlkA_Ptr=(INT8U *)OSMemGet(CommRAMB_Ptr,&Error);---(5)
/*使用获得的内存块*/
… … … …
/*释放获得的内存块*/
OSMemPut(CommRAMA_Ptr,BlkB_Ptr);
}else{
OSTimeDly(1); /*等待一个时钟节拍*/
}
}
… … … …
}
}
系统建立了两个内存分区:内存分区CommRAMA的内存块的大小为32个INT8U(可用的为31个
INT8U),内存分区CommRAMB的内存块的大小为128个INT8U(可用的为127个INT8U);现在
用户创建了一个名为MyTask的任务,它运行时需要一个大小为60个INT8U的内存空间,在这
个系统有两个解决办法:从内存分区CommRAMA中申请两个内存块以及从内存分区CommRAMB中
申请一个内存块;具体的实现方法是先获取内存分区CommRAMA的使用信息,看有没有足够的
内存块(剩余块数不小于2)如果够的话,就不用从内存分区CommRAMB中申请了,否则就的
从CommRAMB中申请,如果这里也没有的话,那么就等待一个时钟节拍!
使用内存管理模块需要做的工作还有:
1.打开配置文件OS_CFG.H,将开关量OS_MEM_EN设置为1:
#define OS_MEM_EN 0
2.打开配置文件OS_CFG.H,设置系统要建立的任务分区的数量:
#define OS_MAX_MEM_PART 2