本篇介绍内存管理方面的内容,具体如下:
-
简单介绍内存管理
-
新建内存单元OS_MEM *OSMemCreate (void *addr,INT32U nblks,INT32U blksize,INT8U *perr)\
-
获得内存块void *OSMemGet (OS_MEM *pmem,INT8U *perr)
-
得到内存分区名称INT8U OSMemNameGet (OS_MEM *pmem,INT8U **pname,INT8U *perr)\
-
给内存分区设置名称
-
释放内存块INT8U OSMemPut (OS_MEM *pmem,void *pblk)\
-
查询内存分区信息INT8U OSMemQuery (OS_MEM *pmem,OS_MEM_DATA *p_mem_data)\
-
初始化内存分区管理void OS_MemInit (void)
内存管理介绍:
我们都知道可以用malloc()和free()两个函数动态地分配内存和释放内存。但是,在嵌入式实时操作系统中,多次这样做会把原来很大的一块连续内存区域,逐渐地分割成许多非常小而且彼此又不相邻的内存区域,即内存碎片。内存碎片的大量存在不利于我们后续再分配内存。
所以,在μC/OS-II中,操作系统把连续的大块内存按分区来管理。每个分区中包含有整数个大小相同的内存块。利用这种机制,μC/OS-II 对malloc()和free()函数进行了改进,使得它们可以分配和释放固定大小的内存块。这样还有一个好处就是malloc()和free()函数的执行时间是固定的。
在一个系统中可以有多个内存分区。这样,用户的应用程序就可以从不同的内存分区中得到不同大小的内存块。但是,特定的内存块在释放时必须重新放回它以前所属于的内存分区。显然,采用这样的内存管理算法,上面的内存碎片问题就得到了解决。\
为了便于内存的管理,在μC/OS-II中使用内存控制块(memory control blocks)的数据结构来跟踪每一个内存分区,系统中的每个内存分区都有它自己的内存控制块。内存控制块的定义如下(uc/os-ii中):\
typedef struct os_mem { /* 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该分区剩余空闲块的数量 */
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName; /* Memory partition name内存分区的名字*/
#endif
} OS_MEM;
typedef struct os_mem_data {
void *OSAddr;/* Pointer to the beginning address of the memory partition指向内存分区起始地址的指针*/
void *OSFreeList;/* Pointer to the beginning of the free list of memory blocks指向空闲内存块列表开始的指针 */
INT32U OSBlkSize;/* Size (in bytes) of each memory block 每个内存块的大小 */
INT32U OSNBlks; /* Total number of blocks in the partition 该分区中块的总数量 */
INT32U OSNFree; /* Number of memory blocks free空闲内存块的数量 */
INT32U OSNUsed; /* Number of memory blocks used 已使用的内存块的数量 */
} OS_MEM_DATA;
新建内存分区OS_MEM *OSMemCreate (void *addr, INT32U nblks,INT32U blksize,INT8U *perr):
源代码如下:
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
/*2018/2/23
*********************************************************************************************************
* CREATE A MEMORY PARTITION
* 新建内存单元
* Description : Create a fixed-sized memory partition that will be managed by uC/OS-II.
*描述:建立一个大小可调节的内存单元,uc/os-ii管理该单元
* Arguments : addr is the starting address of the memory partition
*参数: --addr:内存单元的起始地址。内存区可以使用静态数组或在初始化时使用malloc()函数建立
* nblks is the number of memory blocks to create from the partition.
blksize is the size (in bytes) of each block in the memory partition.
* --nblks:需要的内存块的数目。块的大小是内存分区每一块的大小(字节为单位)
* perr is a pointer to a variable containing an error message which will be set by
* this function to either:
* OS_ERR_NONE if the memory partition has been created correctly.
* OS_ERR_MEM_INVALID_ADDR if you are specifying an invalid address for the memory
* storage of the partition or, the block does not align
* on a pointer boundary
* OS_ERR_MEM_INVALID_PART no free partitions available
* OS_ERR_MEM_INVALID_BLKS user specified an invalid number of blocks (must be >= 2)
* OS_ERR_MEM_INVALID_SIZE user specified an invalid block size
* - must be greater than the size of a pointer
* - must be able to hold an integral number of pointers
* --perr:指向错误码的指针。可以设置为以下值:
OS_ERR_NONE:内存分区成功建立。
OS_ERR_MEM_INVALID_ADDR:指定了非法地址或者为空指针
OS_ERR_MEM_INVALID_PART:没有空闲的分区可以使用。
OS_ERR_MEM_INVALID_BLKS:使用者指定了无效的内存块(内存块数要>=2)
OS_ERR_MEM_INVALID_SIZE:使用者指定了无效的块大小:
-必须比指针大;
-指针为整数值
* Returns : != (OS_MEM *)0 is the partition was created
* == (OS_MEM *)0 if the partition was not created because of invalid arguments or, no
* free partition is available.
返回值:!= (OS_MEM *)0:内存分区成功创建。
== (OS_MEM *)0:内存分区没有被创建因为参数无效或者没有可用的空闲分区。
*********************************************************************************************************
*/
OS_MEM *OSMemCreate (void *addr,
INT32U nblks,
INT32U blksize,
INT8U *perr)
{
OS_MEM *pmem;/*指向内存控制块的指针*/
INT8U *pblk;/*每块内存块的起始地址*/
void **plink;/*链接起始地址*/
INT32U loops;
INT32U i;/*内存包含的内存区数量*/
#if OS_CRITICAL_METHOD == 3u
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (addr == (void *)0) { /*当内存起始地址为0时*/
*perr = OS_ERR_MEM_INVALID_ADDR;/*错误显示为(非法地址,即地址为空指针,无效)*/
return ((OS_MEM *)0);
}
if (((INT32U)addr & (sizeof(void *) - 1u)) != 0u){
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (nblks < 2u) { /*内存块至少为2*/
*perr = OS_ERR_MEM_INVALID_BLKS;
return ((OS_MEM *)0);
}
if (blksize < sizeof(void *)) { /*每个内存块至少容得一个指针(链接指针)*/
*perr = OS_ERR_MEM_INVALID_SIZE;/*否则显示(内存块大小不足以容纳一个指针变量)*/
return ((OS_MEM *)0);
}
#endif
OS_ENTER_CRITICAL();/*进入中断*/
pmem = OSMemFreeList; /*得到空闲的内存分区*/
if (OSMemFreeList != (OS_MEM *)0)/*有空闲的内存分区*/
{
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;/*指向下一个空余链接控制块*/
}
OS_EXIT_CRITICAL();/*退出中断*/
if (pmem == (OS_MEM *)0) /*没有获得内存分区*/
{
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);
}
/*获得了内存分区*/
plink = (void **)addr;/*/链接起始地址=内存分区起始地址*/
pblk = (INT8U *)addr;
loops = nblks - 1u;/*循环次数*/
for (i = 0u; i < loops; i++)
{
pblk += blksize; /*每块内存的起始地址=内存分区起始地址+每块内存块大小 */
*plink = (void *)pblk;
plink = (void **)pblk;
}
*plink = (void *)0;/*最后一个链接指针指为空 */
pmem->OSMemAddr = addr; /* 存储内存分区开始地址 */
pmem->OSMemFreeList = addr; /*内存空闲列表指向内存分区起始地址*/
pmem->OSMemNFree = nblks;/* 分区中空闲内存块数量=需要的内存块数目 */
pmem->OSMemNBlks = nblks;/*总的内存块数量=需要的内存块数目*/
pmem->OSMemBlkSize = blksize;/*内存块大小 */
*perr = OS_ERR_NONE;
return (pmem);
}
其中一些语句解释如下:(个人理解)
1.
pmem = OSMemFreeList; /*得到空闲的内存分区*/
if (OSMemFreeList != (OS_MEM *)0)/*有空闲的内存分区*/
{
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;/*更新空闲内存列表*/
}
OS_EXIT_CRITICAL();/*退出中断*/
if (pmem == (OS_MEM *)0) /*没有获得内存分区*/
{
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);
}
这里想说一下:我们看到判断了两次,第一次是判断有没有空闲的内存分区。这个是为了更新空闲列表设置的。第二次是判断有没有获得内存分区。有空闲分区我们不一定会获得,所以需要进行确认。
2.for循环中:
for (i = 0u; i < loops; i++)
{
pblk += blksize; /*每块内存的起始地址=内存分区起始地址+每块内存块大小 */
*plink = (void *)pblk;
plink = (void **)pblk;
}
这里两行语句没有注释,因为觉得说不清楚。个人理解是:
\
1).pblk每次进行计算,保存的是下一个内存块起始地址。
2)plink指向pblk指向的那个地址;
3)plink本身作为下一个块的链接。
\
获得内存块void *OSMemGet (OS_MEM *pmem,INT8U *perr):\
/*$PAGE*/
/*2018/2/23
*********************************************************************************************************
* GET A MEMORY BLOCK
* 获得内存块
* Description : Get a memory block from a partition
*描述:从内存分区得到内存块
* Arguments : pmem is a pointer to the memory partition control block
*参数: --pmem:指向内存分区控制块的指针
* perr is a pointer to a variable containing an error message which will be set by this
* function to either:
* OS_ERR_NONE if the memory partition has been created correctly.
* OS_ERR_MEM_NO_FREE_BLKS if there are no more free memory blocks to allocate to caller
* OS_ERR_MEM_INVALID_PMEM if you passed a NULL pointer for 'pmem'
* --perr:包含错误码的指针:
OS_ERR_NONE:内存分区被成功创建
OS_ERR_MEM_NO_FREE_BLKS:没有空余的内存块可以分配
OS_ERR_MEM_INVALID_PMEM:pmem为空指针。
* Returns : A pointer to a memory block if no error is detected
* A pointer to NULL if an error is detected
返回值: 如果没有错误返回指向内存块的指针;
如果有错误返回空。
*********************************************************************************************************
*/
void *OSMemGet (OS_MEM *pmem,
INT8U *perr)
{
void *pblk;/*指向内存块的指针*/
#if OS_CRITICAL_METHOD == 3u
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pmem == (OS_MEM *)0) {
*perr = OS_ERR_MEM_INVALID_PMEM;
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();/*进入中断*/
if (pmem->OSMemNFree > 0u) /*有空闲的内存块*/
{
pblk = pmem->OSMemFreeList; /*pblk指向空闲的内存块 */
pmem->OSMemFreeList = *(void **)pblk;/*调整空闲内存列表*/
pmem->OSMemNFree--;/*空闲内存块数量减1*/
OS_EXIT_CRITICAL();/*退出中断*/
*perr = OS_ERR_NONE;
return (pblk); /* 返回内存块 */
}
/*如果没有空闲的内存块*/
OS_EXIT_CRITICAL();/*退出中断*/
*perr = OS_ERR_MEM_NO_FREE_BLKS; /*设置错误码*/
return ((void *)0); /*返回空*/
}
得到内存分区名称INT8U OSMemNameGet (OS_MEM *pmem,INT8U **pname,INT8U *perr):
/*$PAGE*/
/*2018/2/23
*********************************************************************************************************
* GET THE NAME OF A MEMORY PARTITION
* 得到内存分区名称
* Description: This function is used to obtain the name assigned to a memory partition.
*描述:该函数是用来获得内存分区的名称
* Arguments : pmem is a pointer to the memory partition
*参数: --pmem:指向内存分区的指针
* pname is a pointer to a pointer to an ASCII string that will receive the name of the memory partition.
* --pname:指向内存分区名称的指针
* perr is a pointer to an error code that can contain one of the following values:
* OS_ERR_NONE if the name was copied to 'pname'
* OS_ERR_MEM_INVALID_PMEM if you passed a NULL pointer for 'pmem'
* OS_ERR_PNAME_NULL You passed a NULL pointer for 'pname'
* OS_ERR_NAME_GET_ISR You called this function from an ISR
* --perr:指向错误码的指针:
OS_ERR_NONE:名字被复制到了pname;
OS_ERR_MEM_INVALID_PMEM:pmem为空指针
OS_ERR_PNAME_NULL:pname为空指针
OS_ERR_NAME_GET_ISR:从中断服务子程序中调用该函数
* Returns : The length of the string or 0 if 'pmem' is a NULL pointer.
返回值:如果pname为空指针返回0,否则返回名称的长度
*********************************************************************************************************
*/
#if OS_MEM_NAME_EN > 0u
INT8U OSMemNameGet (OS_MEM *pmem,
INT8U **pname,
INT8U *perr)
{
INT8U len;/*存储名称的长度*/
#if OS_CRITICAL_METHOD == 3u
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pmem == (OS_MEM *)0) {
*perr = OS_ERR_MEM_INVALID_PMEM;
return (0u);
}
if (pname == (INT8U **)0) {
*perr = OS_ERR_PNAME_NULL;
return (0u);
}
#endif
if (OSIntNesting > 0u) {
*perr = OS_ERR_NAME_GET_ISR;
return (0u);
}
OS_ENTER_CRITICAL();/(进入中断
*pname = pmem->OSMemName;/*将名称赋给pname*/
len = OS_StrLen(*pname);/*求出名称的长度*/
OS_EXIT_CRITICAL();/*退出中断*/
*perr = OS_ERR_NONE;
return (len);/*返回长度*/
}
#endif
给中断分区设置名称void OSMemNameSet (OS_MEM *pmem,INT8U *pname,INT8U *perr):(部分)
OS_ENTER_CRITICAL();/*进入中断*/
pmem->OSMemName = pname;/*设置名称*/
OS_EXIT_CRITICAL();/*退出中断*/
*perr = OS_ERR_NONE;
释放内存块INT8U OSMemPut (OS_MEM *pmem, void *pblk)(部分):
OS_ENTER_CRITICAL();/*进入中断*/
if (pmem->OSMemNFree >= pmem->OSMemNBlks)/*释放的块数大于分配的块数*/
{
OS_EXIT_CRITICAL();/*退出中断*/
return (OS_ERR_MEM_FULL);/*返回错误类型*/
}
/*释放的块数小于分配的块数,可以释放*/
*(void **)pblk = pmem->OSMemFreeList; /*将释放的块插入到空闲内存列表中*/
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++;/* 将空闲块数目加1 */
OS_EXIT_CRITICAL();/*退出中断*/
return (OS_ERR_NONE);
查询内存分区信息INT8U OSMemQuery (OS_MEM *pmem,OS_MEM_DATA *p_mem_data):(部分)
OS_ENTER_CRITICAL();/*进入中断*/
p_mem_data->OSAddr = pmem->OSMemAddr;
p_mem_data->OSFreeList = pmem->OSMemFreeList;
p_mem_data->OSBlkSize = pmem->OSMemBlkSize;
p_mem_data->OSNBlks = pmem->OSMemNBlks;
p_mem_data->OSNFree = pmem->OSMemNFree;
OS_EXIT_CRITICAL();
p_mem_data->OSNUsed = p_mem_data->OSNBlks - p_mem_data->OSNFree;
其实就是进行了最上面两个结构体内容的赋值。
初始化内存分区管理void OS_MemInit (void):\
/*$PAGE*/
/*2018/2/23
*********************************************************************************************************
* INITIALIZE MEMORY PARTITION MANAGER
* 初始化内存分区管理
* Description : This function is called by uC/OS-II to initialize the memory partition manager. Your
* application MUST NOT call this function.
*,描述:该函数由uc/os-ii调用,用来初始化内存分区管理。你的应用程序不能调用该函数。
* Arguments : none
*参数:无
* Returns : none
*返回值:无
* Note(s) : This function is INTERNAL to uC/OS-II and your application should not call it.
注释:该功能为内部函数
*********************************************************************************************************
*/
void OS_MemInit (void)
{
#if OS_MAX_MEM_PART == 1u/*如果内存分区只有一个*/
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); /* 清除内存分区表 */
OSMemFreeList = (OS_MEM *)&OSMemTbl[0]; /*指针指向空闲列表的开始 */
#if OS_MEM_NAME_EN > 0u
OSMemFreeList->OSMemName = (INT8U *)"?"; /* 名称初始化为未命名 */
#endif
#endif
#if OS_MAX_MEM_PART >= 2u/*如果不止一个分区*/
OS_MEM *pmem;/*指向内存分区的指针*/
INT16U i;
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); /*清除内存分区表 */
for (i = 0u; i < (OS_MAX_MEM_PART - 1u); i++)/*初始化空闲内存分区表*/
{
pmem = &OSMemTbl[i]; /*指向内存控制块(MCB)*/
pmem->OSMemFreeList = (void *)&OSMemTbl[i + 1u]; /*更新空闲分区列表 */
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";/*名称设为未命名*/
#endif
}
pmem = &OSMemTbl[i];
pmem->OSMemFreeList = (void *)0;/*初始化最后一个节点*/
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
OSMemFreeList = &OSMemTbl[0];/* 将空闲列表指针指向内存分区表的首地址*/
#endif
}
#endif
到这里os_mem.c文件就读完了。
===============================================================
===============================================================
\