内存管理:
平时经常用到一些windows内存管理的软件,有一些内存管理的软件进行内存碎片的整理,在频繁分配和释放内存的地方会造成大量的内存碎片。内存碎片是如何形成的呢?书中是这样写的:在不断的分配和释放内存的过程中,一整块内存被分散的小块内存,称为内存碎片。比如假设有8块连续的内存空间被分配使用了,后来一些被释放,最后内存使用的情况可能如图,红色表示还在占用着,这是如果要使用3块内存就无法分配了,但是实际上内存块是有的,只是不连续。内存管理就是为了避免出现这种情况,因此一般都是一次性取出较多的内存块,将多个内存块(只能是4,8,16等固定字节数)串成单链表,组成内存分区。用的时候从内存分区块中取出一块,用完再放回去。这种方式的时间很短,效率很高,但是在同一个内存分区中每次请求的内存块只能是固定大小的字节。
内存池的创建:


void OSMemCreate (OS_MEM *p_mem,CPU_CHAR *p_name,void *p_addr,OS_MEM_QTY n_blks,OS_MEM_SIZE blk_size,OS_ERR *p_err)
{
#if OS_CFG_ARG_CHK_EN > 0uCPU_DATA align_msk;
#endifOS_MEM_QTY i;OS_MEM_QTY loops;CPU_INT08U *p_blk;void **p_link;CPU_SR_ALLOC();#ifdef OS_SAFETY_CRITICALif (p_err == (OS_ERR *)0) {OS_SAFETY_CRITICAL_EXCEPTION();return;}
#endif#ifdef OS_SAFETY_CRITICAL_IEC61508if (OSSafetyCriticalStartFlag == DEF_TRUE) {*p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;return;}
#endif#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0uif (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Not allowed to call from an ISR */*p_err = OS_ERR_MEM_CREATE_ISR;return;}
#endif#if OS_CFG_ARG_CHK_EN > 0u//内存块的首地址不能为空 if (p_addr == (void *)0) { /* Must pass a valid address for the memory part. */*p_err = OS_ERR_MEM_INVALID_P_ADDR;return;}//内存块的大小不能小于2 if (n_blks < (OS_MEM_QTY)2) { /* Must have at least 2 blocks per partition */*p_err = OS_ERR_MEM_INVALID_BLKS;return;}//控制块必须要打过一个指针的大小,否则放不下一个地址 if (blk_size < sizeof(void *)) { /* Must contain space for at least a pointer */*p_err = OS_ERR_MEM_INVALID_SIZE;return;}//检查内存分区首地址有无数据对齐,内存分区块的大小是4的倍数 align_msk = sizeof(void *) - 1u;if (align_msk > 0u) {if (((CPU_ADDR)p_addr & align_msk) != 0u){ /* Must be pointer size aligned */*p_err = OS_ERR_MEM_INVALID_P_ADDR;return;}if ((blk_size & align_msk) != 0u) { /* Block size must be a multiple address size */*p_err = OS_ERR_MEM_INVALID_SIZE;return;}}
#endif//将内存块串起来 p_link = (void **)p_addr; /* Create linked list of free memory blocks */p_blk = (CPU_INT08U *)p_addr;loops = n_blks - 1u;for (i = 0u; i < loops; i++) {p_blk += blk_size;*p_link = (void *)p_blk; /* Save pointer to NEXT block in CURRENT block */p_link = (void **)(void *)p_blk; /* Position to NEXT block */}*p_link = (void *)0; /* Last memory block points to NULL */OS_CRITICAL_ENTER();p_mem->Type = OS_OBJ_TYPE_MEM; /* Set the type of object */p_mem->NamePtr = p_name; /* Save name of memory partition */p_mem->AddrPtr = p_addr; /* Store start address of memory partition */p_mem->FreeListPtr = p_addr; /* Initialize pointer to pool of free blocks */p_mem->NbrFree = n_blks; /* Store number of free blocks in MCB */p_mem->NbrMax = n_blks;p_mem->BlkSize = blk_size; /* Store block size of each memory blocks */#if OS_CFG_DBG_EN > 0uOS_MemDbgListAdd(p_mem);
#endif//内存管理对象++ OSMemQty++;OS_CRITICAL_EXIT_NO_SCHED();*p_err = OS_ERR_NONE;
}
这里涉及数据对齐知识,比如一个32为数据总线的CPU要读取一个Byte的数据类型,CPU只会从4的倍数的开始地址一次性读取32为数据放到缓存里。然后根据需要剔除不要的部分,拼接合并后输送到内部寄存器里,读取一个字节的数据一次读写肯定是可以正确读取到所要的数据的,但如果读取多个字节的数据时,数据有分别在两个不同的4字节倍数的内存地址里,这是就需要两次内存读写访问和处理加长了数据读写的时间不利于快速读取,所以数据对齐有助于CPU快速的读取数据。对于程序中检测数据对齐和字节大小是否是4的倍数。比如32为CPU用sizeof(*void)结果是4,4-1为3二进制表示为0011B ,如果地址都是四字节对齐的那就是地址的最低两位就是0,即xxxxx00,再和0011B与就一定是0,同样其他对齐方式(4,8,16)也可以这样检测数据是否对齐。
内存管理变量结构体:
获取内存:
void *OSMemGet (OS_MEM *p_mem,OS_ERR *p_err) {void *p_blk;CPU_SR_ALLOC();#ifdef OS_SAFETY_CRITICALif (p_err == (OS_ERR *)0) {OS_SAFETY_CRITICAL_EXCEPTION();return ((void *)0);} #endif#if OS_CFG_ARG_CHK_EN > 0uif (p_mem == (OS_MEM *)0) { /* Must point to a valid memory partition */*p_err = OS_ERR_MEM_INVALID_P_MEM;return ((void *)0);} #endifCPU_CRITICAL_ENTER();//内存池用完了? if (p_mem->NbrFree == (OS_MEM_QTY)0) { /* See if there are any free memory blocks */CPU_CRITICAL_EXIT();*p_err = OS_ERR_MEM_NO_FREE_BLKS; /* No, Notify caller of empty memory partition */return ((void *)0); /* Return NULL pointer to caller */}//取出一个内存块,然后将第一个可用内存块地址向后移动一个//方便下次内存获取 p_blk = p_mem->FreeListPtr; /* Yes, point to next free memory block */p_mem->FreeListPtr = *(void **)p_blk; /* Adjust pointer to new free list */p_mem->NbrFree--; /* One less memory block in this partition */CPU_CRITICAL_EXIT();*p_err = OS_ERR_NONE; /* No error */return (p_blk); /* Return memory block to caller */ }
这个函数会返回内存块的地址。
内存释放:
void OSMemPut (OS_MEM *p_mem,void *p_blk,OS_ERR *p_err)
{CPU_SR_ALLOC();#ifdef OS_SAFETY_CRITICALif (p_err == (OS_ERR *)0) {OS_SAFETY_CRITICAL_EXCEPTION();return;}
#endif#if OS_CFG_ARG_CHK_EN > 0uif (p_mem == (OS_MEM *)0) { /* Must point to a valid memory partition */*p_err = OS_ERR_MEM_INVALID_P_MEM;return;}if (p_blk == (void *)0) { /* Must release a valid block */*p_err = OS_ERR_MEM_INVALID_P_BLK;return;}
#endifCPU_CRITICAL_ENTER();//内存池已满 if (p_mem->NbrFree >= p_mem->NbrMax) { /* Make sure all blocks not already returned */CPU_CRITICAL_EXIT();*p_err = OS_ERR_MEM_FULL;return;}//将下一个未使用内存块地址放入,将要放入内存池的内存块的//的控制块里 *(void **)p_blk = p_mem->FreeListPtr; /* Insert released block into free block list *///修改第一个未使用的内存块地址为将要放入内存池的内存块地址 p_mem->FreeListPtr = p_blk;p_mem->NbrFree++; /* One more memory block in this partition */CPU_CRITICAL_EXIT();*p_err = OS_ERR_NONE; /* Notify caller that memory block was released */
}
无论是内存的获取和释放只要了解了内存池的数据结构,其实就是一个单项链表的删除和插入操作。创建内存分区的时候实际上是把指定的一个内存区域,划分称大小固定的内存块然后串称单向链表,在内存分配时,就用类似消息池的操作一样从内存中划分出一定的内存块来使用。所以内存管理实际上就是对内存池的一个“舀”和“倒”的操作。