说明
alios thing(rhino内核)是rtos系统,不像Linux 有用户空间和内核空间的划分,内存的管理和使用都在同一个空间中,使用相对简单。 项目使用平台,虚拟内存和物理内存配置成一样,内存操作和直接操作物理内存没太多差别。
使用
函数名 描述 aos_malloc() 从系统heap分配内存给用户 aos_zalloc() 从系统heap分配内存给用户,并且将分配的内存初始化为0 aos_calloc() 从系统heap分配内存给用户,并且将分配的内存初始化为0 aos_realloc() 重新调整之前调用 aos_malloc(aos_calloc、aos_zalloc)所分配的内存块的大小 aos_free() 内存释放函数
以上是aos层接口,也可以使用POSIX接口(malloc/calloc/free等)和linux一样。 alios thing层级划分:内核(rhino)+ aos(aos 接口层) + POSIX(可移植操作系统接口)。
内核实现
AliOS Things内存管理采用类buddy伙伴算法,以及blk快速小内存申请算法相结合的策略。
Buddy(伙伴系统)
BLK(小内存快速申请算法)
源码:components/rhino/k_mm_blk.c 头文件:components/rhino/include/k_mm_blk.h
为什么需要该算法
如果采用Buddy算法从大块内存中申请/释放小内存,可能会导致大块内存需要多次拆分和合并,此操作会造成性能浪费。 小内存频繁申请释放的场景下,可能导致内存碎片。
源码解析
// Init a pool. 创建内存pool
kstat_t krhino_mblk_pool_init(mblk_pool_t *pool, const name_t *name,
void *pool_start, size_t pool_size);
// Memory block alloc from the pool. 从pool中申请内存
void *krhino_mblk_alloc(mblk_pool_t *pool, uint32_t size); //已加锁
void *krhino_mblk_alloc_nolock(mblk_pool_t *pool, uint32_t size); //未加锁
// Memory block free to the pool. 释放pool中的内存
kstat_t krhino_mblk_free(mblk_pool_t *pool, void *blk); //已加锁
kstat_t krhino_mblk_free_nolock(mblk_pool_t *pool, void *blk); //未加锁
// This function will get information of the pool 获取pool信息
kstat_t krhino_mblk_info(mblk_pool_t *pool, mblk_info_t *info); //已加锁
kstat_t krhino_mblk_info_nolock(mblk_pool_t *pool, mblk_info_t *info); //未加锁
// Check if this a pool block. 判断内存是从pool中申请的
#define krhino_mblk_check(pool, blk) \
((pool) != NULL \
&& ((uintptr_t)(blk) >= ((mblk_pool_t*)(pool))->pool_start) \
&& ((uintptr_t)(blk) < ((mblk_pool_t*)(pool))->pool_end))
// get blk size, should followed by krhino_mblk_check 获取内存大小,需要先确保内存是从pool中申请的
RHINO_INLINE size_t krhino_mblk_get_size(mblk_pool_t *pool, void *blk)
Pool 初始化
kstat_t krhino_init_mm_head(k_mm_head **ppmmhead, void *addr, size_t len)
{
....
#if (RHINO_CONFIG_MM_BLK > 0)
pmmhead->fix_pool = NULL;
mmblk_pool = k_mm_alloc(pmmhead, RHINO_CONFIG_MM_TLF_BLK_SIZE + MM_ALIGN_UP(sizeof(mblk_pool_t)));
if (mmblk_pool) {
stat = krhino_mblk_pool_init(mmblk_pool, "fixed_mm_blk",
(void *)((size_t)mmblk_pool + MM_ALIGN_UP(sizeof(mblk_pool_t))),
RHINO_CONFIG_MM_TLF_BLK_SIZE);
if (stat == RHINO_SUCCESS) {
pmmhead->fix_pool = mmblk_pool;
} else {
k_mm_free(pmmhead, mmblk_pool);
}
}
#endif
return RHINO_SUCCESS;
}
堆内存区域初始化后,申请一块固定大小内存,用来初始化pool。
内存申请
void *k_mm_alloc(k_mm_head *mmhead, size_t size)
{
....
#if (RHINO_CONFIG_MM_BLK > 0)
/* little blk, try to get from mm_pool */
if (mmhead->fix_pool != NULL && size <= RHINO_CONFIG_MM_BLK_SIZE) {
retptr = krhino_mblk_alloc_nolock((mblk_pool_t *)mmhead->fix_pool, size);
if (retptr) {
MM_CRITICAL_EXIT(mmhead, flags_cpsr);
return retptr;
}
}
#endif
....
}
每次申请内存,若申请的size小于或等于阈值时,先从pool中申请,申请失败再使用Buddy算法申请。
内存释放
void k_mm_free(k_mm_head *mmhead, void *ptr)
{
....
#if (RHINO_CONFIG_MM_BLK > 0)
if (krhino_mblk_check(mmhead->fix_pool, ptr)) {
(void)krhino_mblk_free_nolock((mblk_pool_t *)mmhead->fix_pool, ptr);
MM_CRITICAL_EXIT(mmhead, flags_cpsr);
return;
}
#endif
...
}
每次释放内存,先判断内存地址是否在pool内,在的话,调用pool内存释放接口。
核心函数
pool内存申请函数
void *krhino_mblk_alloc_nolock(mblk_pool_t *pool, uint32_t size)
{
uint32_t blk_type;
mblk_list_t *blk_list = NULL;
uintptr_t avail_blk = (uintptr_t)NULL;
if (pool == NULL) {
return NULL;
}
size = size < sizeof(uintptr_t) ? sizeof(uintptr_t) : size; //申请block大小至少是4字节(指针大小)
blk_type = MM_BLK_SIZE2TYPE(size);
while (blk_type < MM_BLK_SLICE_BIT) {
blk_list = &(pool->blk_list[blk_type]);
/* try to get from freelist */
if ((avail_blk = blk_list->free_head) != (uintptr_t)NULL) {
blk_list->free_head = *(uintptr_t *)avail_blk;
blk_list->freelist_cnt--;
break;
}
/* check if need new slice */
if (blk_list->slice_addr == 0 || blk_list->slice_offset == MM_BLK_SLICE_SIZE) {
if (pool->slice_cnt == MM_BLK_SLICE_NUM) {
blk_type++;
continue;
}
/* get new slice for this type blks */
blk_list->slice_addr = pool->pool_start + pool->slice_cnt * MM_BLK_SLICE_SIZE;
pool->slice_type[pool->slice_cnt] = blk_type;
blk_list->slice_offset = 0;
pool->slice_cnt++;
blk_list->slice_cnt++;
}
/* cut blk from slice */
avail_blk = blk_list->slice_addr + blk_list->slice_offset;
blk_list->slice_offset += blk_list->blk_size;
break;
};
if (blk_list) {
(avail_blk == (uintptr_t)0) ? blk_list->fail_cnt++ : blk_list->nofree_cnt++;
}
return (void *)avail_blk;
}
描述:pool初始化时会申请一块区域,并平均划分为多个小块,一小块称为一个切片(slice),算法使用数组保存各个blk_type的数据,blk_type对应的内存大小是位移生成的(1 << blk_type), 1,2,4,8,16,32,64,128…字节, 而切片大小是最大size的两倍。
pool内存释放函数
kstat_t krhino_mblk_free_nolock(mblk_pool_t *pool, void *blk)
{
uint32_t slice_idx;
uint32_t blk_type;
mblk_list_t *blk_list;
NULL_PARA_CHK(pool);
NULL_PARA_CHK(blk);
slice_idx = ((uintptr_t)blk - pool->pool_start) >> MM_BLK_SLICE_BIT;
if (slice_idx >= MM_BLK_SLICE_NUM) {
return RHINO_MM_FREE_ADDR_ERR;
}
blk_type = pool->slice_type[slice_idx];
if (blk_type >= MM_BLK_SLICE_BIT) {
return RHINO_MM_FREE_ADDR_ERR;
}
blk_list = &(pool->blk_list[blk_type]);
/* use the first 4 byte of the free block point to head of free list */
*((uintptr_t *)blk) = blk_list->free_head;
blk_list->free_head = (uintptr_t)blk;
blk_list->nofree_cnt--;
blk_list->freelist_cnt++;
return RHINO_SUCCESS;
}
freelist(空闲链表)的实现方式是: 在每个节点前4个字节保存下个节点的内存地址;内存释放时只需要在保存freelist首节点的内存地址到待释放内存中,并将待释放内存插入到链表首。
算法特征
内存重复使用 内存申请阈值,小于该值的内存申请先采用该算法,申请失败或者大于该阈值的内存申请采用Buddy算法。 内存申请时不涉及拆分,内存释放时,直接挂在到对应type的空闲链表内,释放不涉及合并。 算法性能较高,但是不可靠,由于不涉及到合并,当切片使用完后,其分配情况就固定了,可能出现所有的切片被少数几个blk_type使用完了,再分配其它大小的小内存则会申请失败。