这一节学习基础知识:所用到的数据结构。首先看内存池的接口:
- #ifndef _MEM_POOL_H
- #define _MEM_POOL_H
- static size_t __freelist_index(size_t bytes);
- static size_t __round_up(size_t bytes);
- static void *mem_malloc(size_t n);
- static void mem_free(void *p, size_t n);
- static void *mem_realloc(void* ptr, size_t new_sz, size_t old_sz)
- static void *refill(size_t n);
- static char *chunk_alloc(size_t size, int *nobjs);
- #endif
用到的常量与变量:
- #define __MAX_BYTES 128 /* 小型区块的上限 */
- #define __ALIGN 8 /* 小型区块的上调边界 */
- #define __NFREELISTS __MAX_BYTES / __ALIGN /* 链表个数 */
- obj *free_list[__NFREELISTS]; /* 自由链表 */
- static char *start_free; /* 内存池起始位置 */
- static char *end_free; /* 内存池结束位置 */
- static size_t heap_size; /* 内存池大小 */
这里值得一提的是共用体obj,定义如下:
- typedef union obj {
- union obj *free_list_link;
- char client_data[1];
- }obj;
一般情况下我们构建单链表时需要创建如下的一个结构体:
struct obj {
obj *next; /* 指向下一个这样的结构体 */
char *p; /* 指向真正可用空间 */
int size; /* 记录空间的大小 */
};
用户申请12字节的空间时使用时如下方式:
obj *pj = (obj *)malloc(12 + sizeof(struct obj));
pj->next = NULL;
pj->p = (char*)p + sizeof(struct obj);
pj->size = 12;
但是采用这种方式有个缺点就是我们需要花费额外的开销(记录指向下一个结点的指针和大小),我们可以通过直接定位链表在free_list数组中的位置来减掉 size 的开销,因为 free_list[0] 指向的是8 bytes的区块,free_list[1] 指向的是16 bytes的区块……。但仍需承担 next 和 p 指针的开销。
当我们采用
union obj {
union obj *free_list_link;
char client_data[1];
};
时,sizeof(obj)的大小为4,当然我们更不需负担这4个字节的开销。因为我们可以充分利用union的特性——同一时刻只存在一个变量。当我们构建空闲链表时,我们通过free_list_link指向下一个obj,当把这个obj分配出去的时候,我们直接返回client_data的地址。这样我们就不会在用户申请的空间上添加任何东西,达到了一物二用的结果。
接下来看几个简单的函数:
- static size_t __freelist_index(size_t bytes)
- {
- return (bytes + __ALIGN - 1) / __ALIGN - 1;
- }
- static size_t __round_up(size_t bytes)
- {
- return (bytes + __ALIGN - 1) & ~(__ALIGN - 1);
- }
__freelist_index的作用是函数根据区块的大小,决定使用第n号free-list。n从0算起。__round_up用于将bytes上调至 __ALIGN 的倍数。
接下来要进入几个主要的函数学习了。