RT-Thread小内存管理初始化
结构体定义:
struct rt_small_mem_item:用于管理每一个内存块
struct rt_memory:所有内存管理模块的父类
struct rt_small_mem:作为整块传入空间的管理结构体
struct rt_small_mem_item
{
rt_ubase_t pool_ptr; /**< small memory object addr */
rt_size_t next; /**< next free item */
rt_size_t prev; /**< prev free item */
}
struct rt_memory
{
struct rt_object parent; /**< inherit from rt_object */
const char * algorithm; /**< Memory management algorithm name */
rt_ubase_t address; /**< memory start address */
rt_size_t total; /**< memory size */
rt_size_t used; /**< size used */
rt_size_t max; /**< maximum usage */
};
struct rt_small_mem
{
struct rt_memory parent; /**< inherit from rt_memory */
rt_uint8_t *heap_ptr; /**< pointer to the heap */
struct rt_small_mem_item *heap_end; /**< 标记内存空间的末尾 */
struct rt_small_mem_item *lfree; /**< 指向管理内存空间中第一个可用的块 */
rt_size_t mem_size_aligned; /**< aligned memory size */
};
其中需要注意的是结构体struct rt_small_mem_item
这里很容易忽略的一点就是它的next和prev指针都是**rt_size_t(unsigned long)**类型的。(我就是一开始忽略了,一直把它当结构体指针看,导致后面看代码老是感觉不对劲)。
宏定义:
/* 内存保护字初始化 */
#define HEAP_MAGIC 0x1ea0
/* 掩码 实际值:0xFFFFFFFE */
#define MEM_MASK ((~(rt_size_t)0) - 1)
/* 标记内存块为已使用状态 */
#define MEM_USED() ((((rt_base_t)(small_mem)) & MEM_MASK) | 0x1)
/* 标记内存块为未使用状态 */
#define MEM_FREED() ((((rt_base_t)(small_mem)) & MEM_MASK) | 0x0)
/* 判断内存块的使用状态 */
#define MEM_ISUSED(_mem) \
(((rt_base_t)(((struct rt_small_mem_item *)(_mem))->pool_ptr)) & (~MEM_MASK))
/* 获取整个内存池的管理结构体 */
#define MEM_POOL(_mem) \
((struct rt_small_mem *)(((rt_base_t)(((struct rt_small_mem_item *)(_mem))->pool_ptr)) & (MEM_MASK)))
/* 获取内存块的空间大小 */
#define MEM_SIZE(_heap, _mem) \
(((struct rt_small_mem_item *)(_mem))->next - ((rt_ubase_t)(_mem) - \
(rt_ubase_t)((_heap)->heap_ptr)) - RT_ALIGN(sizeof(struct rt_small_mem_item), RT_ALIGN_SIZE))
/* 对齐后最小的块大小 */
#define MIN_SIZE_ALIGNED RT_ALIGN(MIN_SIZE, RT_ALIGN_SIZE)
/* 内存池管理结构体的空间大小 */
#define SIZEOF_STRUCT_MEM RT_ALIGN(sizeof(struct rt_small_mem_item), RT_ALIGN_SIZE)
大部分宏都还是比较好理解的,这里稍微解释一下我花了比较多时间理解的几个宏。
内存块使用状态标记以及内存状态判断
#define MEM_USED() ((((rt_base_t)(small_mem)) & MEM_MASK) | 0x1)
#define MEM_FREED() ((((rt_base_t)(small_mem)) & MEM_MASK) | 0x0)
#define MEM_ISUSED(_mem) \
(((rt_base_t)(((struct rt_small_mem_item *)(_mem))->pool_ptr)) & (~MEM_MASK))
其中small_mem是内存块的对齐后的起始地址,使用内存块起始地址的最后一位去标记内存状态其实是有妙用的。
一般来说我们在标记状态时会专门设置一个变量,例如:int flag或者uint8_t flag。也就是说我们至少要花费一个字节去存储状态。而RT-Thread并没有特地为内存状态去创建一个变量,它将内存状态以及内存的起始地址一同保存在了结构体rt_small_mem_item的pool_ptr变量中。我们在需要通过内存块去找到内存起始地址时只需要pool_ptr & MEM_MASK即可,对应宏MEM_POOL。一个变量两个用处,是不是很神奇?
当然,这种使用方法是有一个前提的,即我们使用的内存地址是8地址对齐的,它保证了地址的最后三位都是0,从而避免了出现内存起始地址的最后一位为1导致出错的情况。
代码实现:
我们先结合图片了解内存在初始化过程中的使用情况然后再带着理解去看代码就容易多了:
刚开始将指定内存块传入初始化函数:
为内存池管理结构体预留好空间并对齐地址之后:
初始化完成:
我们在阅读时需要将heap_ptr当作一个uint8_t的数组。
rt_smem_t rt_smem_init(const char *name,
void *begin_addr,
rt_size_t size)
{
struct rt_small_mem_item *mem;
struct rt_small_mem *small_mem;
rt_ubase_t start_addr, begin_align, end_align, mem_size;
small_mem = (struct rt_small_mem *)RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE); // 将传入的地址进行对齐
start_addr = (rt_ubase_t)small_mem + sizeof(*small_mem); // 为内存池管理结构体预留空间
begin_align = RT_ALIGN((rt_ubase_t)start_addr, RT_ALIGN_SIZE); // 对预留空间之后的地址进行再对齐
end_align = RT_ALIGN_DOWN((rt_ubase_t)begin_addr + size, RT_ALIGN_SIZE); // 计算出内存空间的末尾地址
/* 检查预留空间之后的剩余空间大小
* 然后再为起始内存块和结尾内存块预留出块结构体。
*/
if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&
((end_align - 2 * SIZEOF_STRUCT_MEM) >= start_addr))
{
/* calculate the aligned memory size */
mem_size = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
}
else
{
rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",
(rt_ubase_t)begin_addr, (rt_ubase_t)begin_addr + size);
return RT_NULL;
}
// 内存池管理结构体的初始化
rt_memset(small_mem, 0, sizeof(*small_mem));
rt_object_init(&(small_mem->parent.parent), RT_Object_Class_Memory, name);
small_mem->parent.algorithm = "small";
small_mem->parent.address = begin_align;
small_mem->parent.total = mem_size;
small_mem->mem_size_aligned = mem_size;
/* point to begin address of heap */
small_mem->heap_ptr = (rt_uint8_t *)begin_align;
LOG_D("mem init, heap begin address 0x%x, size %d",
(rt_ubase_t)small_mem->heap_ptr, small_mem->mem_size_aligned);
/* initialize the start of the heap */
mem = (struct rt_small_mem_item *)small_mem->heap_ptr;
mem->pool_ptr = MEM_FREED();
mem->next = small_mem->mem_size_aligned + SIZEOF_STRUCT_MEM;
mem->prev = 0;
#ifdef RT_USING_MEMTRACE
rt_smem_setname(mem, "INIT");
#endif /* RT_USING_MEMTRACE */
/* initialize the end of the heap */
// 当指向heap_end时,说明内存池的空间已经使用完了。
small_mem->heap_end = (struct rt_small_mem_item *)&small_mem->heap_ptr[mem->next];
small_mem->heap_end->pool_ptr = MEM_USED();
small_mem->heap_end->next = small_mem->mem_size_aligned + SIZEOF_STRUCT_MEM;
small_mem->heap_end->prev = small_mem->mem_size_aligned + SIZEOF_STRUCT_MEM;
#ifdef RT_USING_MEMTRACE
rt_smem_setname(small_mem->heap_end, "INIT");
#endif /* RT_USING_MEMTRACE */
/* initialize the lowest-free pointer to the start of the heap */
small_mem->lfree = (struct rt_small_mem_item *)small_mem->heap_ptr;
return &small_mem->parent;
}
对于小内存管理的了解先讲解到这里,大家先消化一下,后面还会继续编写内存申请以及内存释放的解析。