开发过程中,写代码用得最多的就是对堆的malloc和free了, 在alios这样的小系统中,我们看下是如何处理堆内存的。
跟堆内存相关的有如下几个宏:
- RHINO_CONFIG_MM_TLF
- RHINO_CONFIG_MM_BLK
- RHINO_CONFIG_MM_TLF_BLK_SIZE
- RHINO_CONFIG_MM_DEBUG
其中RHINO_CONFIG_MM_BLK和RHINO_CONFIG_MM_TLF_BLK_SIZE涉及到小块内存管理优化, 我们下一章再研究,RHINO_CONFIG_MM_DEBUG只是调试信息,为简便起见,这章先只研究打开RHINO_CONFIG_MM_TLF宏的状态。
void aos_heap_set()
{
g_mm_region[0].start = (uint8_t*)&Image$$RW_IRAM1$$ZI$$Limit;
g_mm_region[0].len =
(g_iram1_start + g_iram1_total_size - (size_t)&Image$$RW_IRAM1$$ZI$$Limit);
}
其中Image\$\$RW_IRAM1\$\ Z I ZI ZI$Limit为链接器导出的符号,代表ZI段的结束,也就是程序执行区的RAM结束后的地址,反过来也就是我们执行区的RAM未使用的区域的起始地址。g_iram1_start为0x20000000,g_iram1_total_size为0x00005000,分别表示ram的起始地址和长度,所以,上面代码执行完后,g_mm_region[0].start就是可用空闲区域的ram起始地址, len为可用ram的大小。
接着就到krhino_init->k_mm_init()->krhino_init_mm_head.移除RHINO_CONFIG_MM_DEBUG和RHINO_CONFIG_MM_BLK相关的代码后,krhino_init_mm_head如下:
kstat_t krhino_init_mm_head(k_mm_head **ppmmhead, void *addr, size_t len )
{
k_mm_list_t *nextblk;
k_mm_list_t *firstblk;
k_mm_head *pmmhead;
void *orig_addr;
NULL_PARA_CHK(ppmmhead);
NULL_PARA_CHK(addr);
/* check paramters, addr and len need algin
* 1. the length at least need RHINO_CONFIG_MM_TLF_BLK_SIZE for fixed size memory block
* 2. and also ast least have 1k for user alloced
*/
orig_addr = addr;
addr = (void *) MM_ALIGN_UP((size_t)addr);
len -= (size_t)addr - (size_t)orig_addr;
len = MM_ALIGN_DOWN(len);
if ( len == 0
|| len < MIN_FREE_MEMORY_SIZE + RHINO_CONFIG_MM_TLF_BLK_SIZE
|| len > MM_MAX_SIZE) {
return RHINO_MM_POOL_SIZE_ERR;
}
pmmhead = (k_mm_head *)addr;
/* Zeroing the memory head */
memset(pmmhead, 0, sizeof(k_mm_head));
#if (RHINO_CONFIG_MM_REGION_MUTEX > 0)
krhino_mutex_create(&pmmhead->mm_mutex, "mm_mutex");
#else
krhino_spin_lock_init(&pmmhead->mm_lock);
#endif
/* 1 */
firstblk = init_mm_region((void *)((size_t)addr + MM_ALIGN_UP(sizeof(k_mm_head))),
MM_ALIGN_DOWN(len - sizeof(k_mm_head)));
pmmhead->regioninfo = (k_mm_region_info_t *)firstblk->mbinfo.buffer;
nextblk = MM_GET_NEXT_BLK(firstblk);
*ppmmhead = pmmhead;
/* 2 */
/* release free blk */
k_mm_free(pmmhead, nextblk->mbinfo.buffer);
#if (K_MM_STATISTIC > 0)
pmmhead->free_size = MM_GET_BUF_SIZE(nextblk);
pmmhead->used_size = len - MM_GET_BUF_SIZE(nextblk);
pmmhead->maxused_size = pmmhead->used_size;
#endif
return RHINO_SUCCESS;
}
这里面先分析/*1*/init_mm_region以及/*2*/k_mm_free
RHINO_INLINE k_mm_list_t *init_mm_region(void *regionaddr, size_t len)
{
k_mm_list_t *midblk, *lastblk, *firstblk;
k_mm_region_info_t *region;
/* first mmblk for region info */
firstblk = (k_mm_list_t *) regionaddr;
firstblk->prev = NULL;
firstblk->buf_size = MM_ALIGN_UP(sizeof(k_mm_region_info_t))
| RHINO_MM_ALLOCED | RHINO_MM_PREVALLOCED;
/*last mmblk for stop merge */
lastblk = (k_mm_list_t *)((char *)regionaddr + len - MMLIST_HEAD_SIZE);
/*middle mmblk for heap use */
midblk = MM_GET_NEXT_BLK(firstblk);
midblk->buf_size = ((char *)lastblk - (char *)midblk->mbinfo.buffer)
| RHINO_MM_ALLOCED | RHINO_MM_PREVALLOCED;
midblk->mbinfo.free_ptr.prev = midblk->mbinfo.free_ptr.next = 0;
/*last mmblk for stop merge */
lastblk->prev = midblk;
/* set alloced, can't be merged */
lastblk->buf_size = 0 | RHINO_MM_ALLOCED | RHINO_MM_PREVFREE;
region = (k_mm_region_info_t *)firstblk->mbinfo.buffer;
region->next = 0;
region->end = lastblk;
return firstblk;
}
init_mm_region从ppmmhead+sizeof(k_mm_head)的地址开始初始化一片区域,分三个部分firstblk,midblk以及lastblk,如下图所示
regionaddr和len为init_mm_region的入参,传入的内存被初始化成regionaddr开始的三块区域。接着回到krhino_init_mm_head函数,执行到 k_mm_free(pmmhead, nextblk->mbinfo.buffer);时, 各个对象的位置如下:
接着分析k_mm_free(pmmhead, nextblk->mbinfo.buffer),执行k_mm_free是为了将初始化blk时,占用住的整个堆内存释放给pmmhead,正式管理起来。
void k_mm_free(k_mm_head *mmhead, void *ptr)
{
k_mm_list_t *free_b, *next_b, *prev_b;
cpu_cpsr_t flags_cpsr;
(void)flags_cpsr;
if (!ptr || !mmhead) {
return;
}
MM_CRITICAL_ENTER(mmhead, flags_cpsr);
free_b = MM_GET_THIS_BLK(ptr);
/* free_b 指向之前初始化好的midblk*/
free_b->buf_size |= RHINO_MM_FREE;
stats_removesize(mmhead, MM_GET_BLK_SIZE(free_b));
/* stats_removesize 表示将free_b的空间加到pmmhead管理的free_size中去*/
/* if the blk after this freed one is freed too, merge them */
next_b = MM_GET_NEXT_BLK(free_b);
/* 获得free_b下一个blk也就是之前初始化的lastblk*/
if (next_b->buf_size & RHINO_MM_FREE) { /*lastblk为最后一个blk,buf_size为0,跳过*/
k_mm_freelist_delete(mmhead, next_b);
free_b->buf_size += MM_GET_BLK_SIZE(next_b);
}
/* if the blk before this freed one is freed too, merge them */
/*这里虽然free_b->buf_size不为0,但是由于是设置成,RHINO_MM_PREVALLOCED的,所以还是跳过*/
if (free_b->buf_size & RHINO_MM_PREVFREE) {
prev_b = free_b->prev;
k_mm_freelist_delete(mmhead, prev_b);
prev_b->buf_size += MM_GET_BLK_SIZE(free_b);
free_b = prev_b;
}
/* after merge, free to list */
k_mm_freelist_insert(mmhead, free_b);
next_b = MM_GET_NEXT_BLK(free_b);
next_b->prev = free_b;
next_b->buf_size |= RHINO_MM_PREVFREE;
MM_CRITICAL_EXIT(mmhead, flags_cpsr);
}
这里开始看k_mm_freelist_insert,现在需要正式将free_b放入pmmhead里的freelist里管理了:
static void k_mm_freelist_insert(k_mm_head *mmhead, k_mm_list_t *blk)
{
int32_t level;
level = size_to_level(MM_GET_BUF_SIZE(blk));
if ( level < 0 || level >= MM_BIT_LEVEL ) {
return;
}
blk->mbinfo.free_ptr.prev = NULL;
blk->mbinfo.free_ptr.next = mmhead->freelist[level];
if (mmhead->freelist[level] != NULL) {
mmhead->freelist[level]->mbinfo.free_ptr.prev = blk;
}
mmhead->freelist[level] = blk;
/* freelist not null, so set the bit */
mmhead->free_bitmap |= (1 << level);
}
这部分主要研究size_to_level(MM_GET_BUF_SIZE(blk),MM_GET_BUF_SIZE(blk)获得的是free_b的bufsize,比如,我这边环境上,free_b->buf_size也就是之前midblk的buf_size为0x3748,这里MM_GET_BUF_SIZE(blk)之后为0x3749,这是因为加上了之前或上的RHINO_MM_FREE。
size_to_level内容如下:
static int32_t size_to_level(size_t size)
{
size_t cnt = 32 - krhino_clz32(size);
if ( cnt < MM_MIN_BIT ) {
return 0;
}
if ( cnt > MM_MAX_BIT) {
return -1;
}
return cnt - MM_MIN_BIT;
}
krhino_clz32 最后会调用到RHINO_BIT_CLZ -> __clz(x),该指令用于计算最高符号位与第一个1之间的0的个数,比如clz(0)=32,clz(1)=31,我们这里的clz(0x3749)=18。 那么size_to_level中的cnt=14,而MM_MIN_BIT=6(表示最低一级的size大小为小于64),所以返回的是cnt - MM_MIN_BIT=8。回到k_mm_freelist_insert, 将blk挂到mmhead->freelist[8]->mbinfo.free_ptr的链表头上,将mmhead->freelist[8]也指向这个blk,将mmhead->free_bitmap的第8bit置1,表示空余块链表上有不超过(1<<(8+6))大小的空闲内存块可分配。k_mm_freelist_insert执行完后返回k_mm_free函数, 将next_b->prev指向free_b,next_b->buf_size设置成RHINO_MM_PREVFREE, next_b也就是一开始初始化的lastblk。最后回到krhino_init_mm_head,执行完k_mm_free后, pmmhead设置free_size为最开始midblk的buf_size=0x3748, used_size和maxused_size为总的传入len长度0x3820-0x3748=0xd8。至此,堆内存初始化完毕。可以看到本次初始化对内存,消耗掉的空间为MM_ALIGN_UP(sizeof(k_mm_head))大小0xB8 + firstblk大小16+midblk头部大小8+lastblk大小8 = 0xd8,实际获得的空闲可用空间为midblk->buf_size=0x3748,或上RHINO_MM_FREE,为midblk->buf_size=0x3749。
接下来,我们再在上面的基础上,malloc一个size为1的空间,也就是执行以下malloc(1),执行流程如下:
用户态调用malloc(size)-> aos_malloc(size) -> krhino_mm_alloc(size) -> k_mm_alloc(g_kmm_head, size);这个过程中size始终为1, 我们直接分析k_mm_alloc(g_kmm_head, 1):
void *k_mm_alloc(k_mm_head *mmhead, size_t size)
{
void *retptr;
k_mm_list_t *get_b, *new_b, *next_b;
int32_t level;
size_t left_size;
size_t req_size = size;
cpu_cpsr_t flags_cpsr;
(void)flags_cpsr;
(void)req_size;
if (!mmhead) {
return NULL;
}
if (size == 0) {
return NULL;
}
MM_CRITICAL_ENTER(mmhead, flags_cpsr);
retptr = NULL;
/*size 从1对齐为8*/
size = MM_ALIGN_UP(size);
/*再由8 变为MM_MIN_SIZE=32*/
size = size < MM_MIN_SIZE ? MM_MIN_SIZE : size;
/* size_to_level最后调用clz之前解释过,level 最终会是0*/
if ((level = size_to_level(size)) == -1) {
goto ALLOCEXIT;
}
/* try to find in same level */
get_b = mmhead->freelist[level];
while ( get_b != NULL ) {
if ( MM_GET_BUF_SIZE(get_b) >= size ) {
break;
}
get_b = get_b->mbinfo.free_ptr.next;
}
if ( get_b == NULL ) {
/* try to find in higher level */
/*get_b为空,寻找更大的空闲块,分析如下*/
get_b = find_up_level(mmhead, level);
if ( get_b == NULL ) {
/* do not find availalbe freeblk */
goto ALLOCEXIT;
}
}
/*将(1<<8)的空闲块从mmhead上删除*/
k_mm_freelist_delete(mmhead, get_b);
next_b = MM_GET_NEXT_BLK(get_b);
/* Should the block be split? */
if (MM_GET_BUF_SIZE(get_b) >= size + MMLIST_HEAD_SIZE + MM_MIN_SIZE) {
left_size = MM_GET_BUF_SIZE(get_b) - size - MMLIST_HEAD_SIZE;
get_b->buf_size = size | (get_b->buf_size & RHINO_MM_PRESTAT_MASK);
new_b = MM_GET_NEXT_BLK(get_b);
new_b->prev = get_b;
new_b->buf_size = left_size | RHINO_MM_FREE | RHINO_MM_PREVALLOCED;
next_b->prev = new_b;
k_mm_freelist_insert(mmhead, new_b);
} else {
next_b->buf_size &= (~RHINO_MM_PREVFREE);
}
get_b->buf_size &= (~RHINO_MM_FREE); /* Now it's used */
retptr = (void *)get_b->mbinfo.buffer;
if (retptr != NULL) {
stats_addsize(mmhead, MM_GET_BLK_SIZE(get_b), req_size);
}
ALLOCEXIT:
MM_CRITICAL_EXIT(mmhead, flags_cpsr);
return retptr ;
}
find_up_level(mmhead, level); 这里会从mm_head中找level=0的块,find_up_level如下:
static k_mm_list_t *find_up_level(k_mm_head *mmhead, int32_t level)
{
uint32_t bitmap;
/*bitmap = (1<<8) & 0xfffffffe = 1<<8*/
bitmap = mmhead->free_bitmap & (0xfffffffful << (level + 1));
level = krhino_ctz32(bitmap);
if ( level < MM_BIT_LEVEL ) {
return mmhead->freelist[level];
}
return NULL;
}
RHINO_INLINE uint8_t krhino_ctz32(uint32_t x)
{
uint8_t n = 0;
if (x == 0) {
return 32;
}
if ((x & 0X0000FFFF) == 0) {
x >>= 16;
n += 16;
}
if ((x & 0X000000FF) == 0) {
x >>= 8;
n += 8;
}
if ((x & 0X0000000F) == 0) {
x >>= 4;
n += 4;
}
if ((x & 0X00000003) == 0) {
x >>= 2;
n += 2;
}
if ((x & 0X00000001) == 0) {
n += 1;
}
return n;
}
这里bitmap = mmhead->free_bitmap & (0xfffffffful << (level + 1)); 和 level = krhino_ctz32(bitmap);就能够在mmhead已有的空闲块中,找到满足level需要分配的最小大小种类的空闲块。由于刚初始化完,第一次malloc,所以找到的只可能是刚分配的(1<<8)的空闲块,level值为8,所以find_up_level返回刚分配的(1<<8)的空闲块。后续执行k_mm_freelist_delete:
static void k_mm_freelist_delete(k_mm_head *mmhead, k_mm_list_t *blk)
{
int32_t level;
/*level 依然为8*/
level = size_to_level(MM_GET_BUF_SIZE(blk));
if ( level < 0 || level >= MM_BIT_LEVEL ) {
return;
}
if (blk->mbinfo.free_ptr.next != NULL) {
blk->mbinfo.free_ptr.next->mbinfo.free_ptr.prev = blk->mbinfo.free_ptr.prev;
}
if (blk->mbinfo.free_ptr.prev != NULL) {
blk->mbinfo.free_ptr.prev->mbinfo.free_ptr.next = blk->mbinfo.free_ptr.next;
}
if (mmhead->freelist[level] == blk) {
/* first blk in this freelist */
mmhead->freelist[level] = blk->mbinfo.free_ptr.next;
if (mmhead->freelist[level] == NULL) {
/* freelist null, so clear the bit */
/*由于这个块是唯一的一块,所以将free_bitmap 去掉第8位上的1*/
mmhead->free_bitmap &= (~(1 << level));
}
}
blk->mbinfo.free_ptr.prev = NULL;
blk->mbinfo.free_ptr.next = NULL;
}
执行k_mm_freelist_delete完成后, 从pmmhead的freelist[8]上取到了大小为0x3749的大块,继续执行k_mm_alloc函数里的接下来的分配操作:
/* Should the block be split? */
if (MM_GET_BUF_SIZE(get_b) >= size + MMLIST_HEAD_SIZE + MM_MIN_SIZE) {
/*left_size=0x3748-32-8=0x3721*/
left_size = MM_GET_BUF_SIZE(get_b) - size - MMLIST_HEAD_SIZE;
/* get_b->buf_size = 32 | (0x3749 & 2) =32*/
get_b->buf_size = size | (get_b->buf_size & RHINO_MM_PRESTAT_MASK);
/*new_b的起始地址为get_b的起始地址+MM_LIST_HEAD+MM_GET_BUF_SIZE(get_b)
=0x200018A8+8+32=0x2000018d0*/
new_b = MM_GET_NEXT_BLK(new_b);
new_b->prev = get_b;
/*new_b->buf_size=0x3721| 1 | 0=0x3721*/
new_b->buf_size = left_size | RHINO_MM_FREE | RHINO_MM_PREVALLOCED;
next_b->prev = new_b;
k_mm_freelist_insert(mmhead, new_b);
} else {
next_b->buf_size &= (~RHINO_MM_PREVFREE);
}
get_b->buf_size &= (~RHINO_MM_FREE); /* Now it's used */
retptr = (void *)get_b->mbinfo.buffer;
if (retptr != NULL) {
stats_addsize(mmhead, MM_GET_BLK_SIZE(get_b), req_size);
}
这里是判断这个free的大块有没有必要被切成两部分,如果使用完掉32byte大小后,剩余空间大于等于MMLIST_HEAD_SIZE + MM_MIN_SIZE,那么就有必要执行分割动作,因为已经有冗余空间MMLIST_HEAD_SIZE 做内存管理,而且空间也大于MM_MIN_SIZE这个最小可分配内存空间。 0x3748肯定远远大于MMLIST_HEAD_SIZE (8bytes) + MM_MIN_SIZE(32bytes),所以做切分(见注释),切分出一块0x8+32大小的block给malloc的用户,其余组成一个新的block,再次执行 k_mm_freelist_insert还给pmmhead,然后,返回给malloc用户retptr ,指向的是32长度的buf地址。
综上,我们可以看到,在未打开RHINO_CONFIG_MM_BLK的情况下, 即使malloc(1),我们也会耗费8+32=40字节内存,下一篇我们会讨论RHINO_CONFIG_MM_BLK开启的情况。