AliOS-Things+STM32 (五) 堆内存管理(上)

开发过程中,写代码用得最多的就是对堆的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开启的情况。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值