refill函数
分成两个部分
1.chunk_alloc函数负责切割出一大块内存空间
2.chunk_alloc()下面的代码负责对这片空间加工,切割成一个一个指定内存大小的内存块,并由free_list_link 连接起来。
/* Returns an object of size n, and optionally adds to size n free list.*/
/* We assume that n is properly aligned. */
/* We hold the allocation lock. */
/*返回一个大小为n的对象,可以选择添加到大小为n的空闲列表中
/*我们假设n是正确对齐的。* /
/*我们持有分配锁。*/
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n) //n已经8字节对齐
{
int nobjs = 20; //默认创建20个区块
char * chunk = chunk_alloc(n, nobjs);
//检查是否能够创建20个,返回创建实际空间的首地址,详细分析见下面。
//注意此处nobjs按引用传递,属于传入传出参数,返回值是实际能创建出的内存块
obj * __VOLATILE * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i;
if (1 == nobjs) return(chunk); //如果实际只要一块,直接返回
my_free_list = free_list + FREELIST_INDEX(n); //根据n指向相应区号表
/* Build free list in chunk */
/* 以下操作是把创建好的chunk空间分割成一个一个区块,每个区块由free_list_link相联系*/
result = (obj *)chunk; //分配好内存块的首地址
*my_free_list = next_obj = (obj *)(chunk + n); //区号链表指向第二个可用位置,因为第一个位置返回给容器使用
for (i = 1; ; i++) {
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + n); //(char*)保证+n个字节长度
if (nobjs - 1 == i) { //到达区块最大数时退出
current_obj -> free_list_link = 0;
break;
} else {
current_obj -> free_list_link = next_obj; //free_list_link 连接各个内存块
}
}
return(result); //返回分配好内存块的首地址
}
图来自侯捷老师:
chunk_alloc函数分析
if :优先判断备用池pool空间满足需求?
else:分配一大块空间
然后递归调用自己,每次备用池大小会发生变化,第二次必然跳出递归。
/* We allocate memory in large chunks in order to avoid fragmenting */
/* the malloc heap too much. */
/* We assume that size is properly aligned. */
/* We hold the allocation lock. */
/*我们以大的块分配内存,以避免碎片化*/
/* malloc堆太多。* /
/*我们假设大小是正确对齐的。* /
/*我们持有分配锁。*/
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
char * result;
size_t total_bytes = size * nobjs; //总共需要的字节数
size_t bytes_left = end_free - start_free; //内存池剩余的内存数,char*类型相减得字节数
if (bytes_left >= total_bytes) { //剩余空间满足要求,则切割相应大小空间使用
result = start_free;
start_free += total_bytes; //start_free 更新位置
return(result); //返回这段切割内存空间首地址
} else if (bytes_left >= size) { //不满足总块数的需求,但至少满足申请的一块内存的需求,核心思想:能拿多少块就拿多少
nobjs = bytes_left/size; //nobjs为实际从备用池切割的块数,注意,nobjs引用传递,随着函数结束传出。
total_bytes = size * nobjs; //实际总字节数
result = start_free;
start_free += total_bytes; //start_free 更新位置
return(result); //返回这段切割内存空间首地址
} else { //以上条件都不满足,malloc重新分配空间
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
//计算需要malloc的总空间,ROUND_UP(heap_size >> 4)是16取余后8字节对齐,属于追加量
// Try to make use of the left-over piece.
if (bytes_left > 0) { //备用池还剩空间,需要找人负责监管起来
obj * __VOLATILE * my_free_list =
free_list + FREELIST_INDEX(bytes_left);
/*static size_t FREELIST_INDEX(size_t bytes) {
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
向上取整思想 }*/
//为了防止内存碎片,需要找到负责这一小块内存的区号链表
//例如剩28字节空间,(28+7)/8-1=3,这块内存交给#3号(负责32字节)区号链表负责
((obj *)start_free) -> free_list_link = *my_free_list;
//*my_free_list存放当前区号链表指向的一块可用空间
*my_free_list = (obj *)start_free;
//当前区号链表指向的可用空间变为这块用不到的备用池空间
//下次使用此区号链表就会优先使用这块空闲区域
}
start_free = (char *)malloc(bytes_to_get); //重新分配一块空间
if (0 == start_free) { //如果分配失败,比如系统空间不足,爆内存
int i;
obj * __VOLATILE * my_free_list, *p;
// Try to make do with what we have. That can't hurt. We do not try smaller requests, since that tends to result in disaster on multi-process machines.
//试着用我们现有的东西将就一下。不能伤害。我们不尝试较小的请求,因为这往往会导致多进程机器的灾难。
for (i = size; i <= __MAX_BYTES; i += __ALIGN) { //8字节移动找区号链表,找可以借空间的区号链表大哥
my_free_list = free_list + FREELIST_INDEX(i); //i对应相应区号链表
p = *my_free_list; //p指向当前区号链表负责的一块可用空间
if (0 != p) { //大哥手里有钱(空间)
*my_free_list = p -> free_list_link; //这块空间被小弟借走了,区号链表指向下一块可用空间
start_free = (char *)p;
end_free = start_free + i;
//这块空间成为新的备用池
//因为是大哥(区号更大),那么在下面这个递归调用中
//bytes_left >= size是必然的,下次递归可以正常返回result。
return(chunk_alloc(size, nobjs));
// Any leftover piece will eventually make it to the
// right free list.
}
}
end_free = 0; // In case of exception.分配内存失败,交给第一级分配器
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
// This should either throw an exception or remedy the situation. Thus we assume it succeeded.
//这应该会抛出异常或纠正这种情况。因此我们认为它成功了。
}
//malloc正常成功时
heap_size += bytes_to_get; //总字节数更新
end_free = start_free + bytes_to_get; //备用池尾部位置更新
return(chunk_alloc(size, nobjs)); //递归调用自己,分配空间后下次递归就可以跳出,秒啊
}
}
ROUND_UP字节对齐函数,__ALIGN=8,返回8字节对齐
static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
分析几个if条件
(1)if (bytes_left >= total_bytes) //剩余空间满足要求,则切割相应大小空间使用
pool之前为2000字节,申请了20*88=1760个字节
(2)else if (bytes_left >= size) //不满足总块数的需求,但至少满足申请的一块内存的需求,核心思想:能拿多少块就拿多少
pool之前为640字节,申请了6410=640个字节
(3)else //以上条件都不满足,malloc重新分配空间
pool之前为80,申请1042*20+RoundUp(5200>>4)空间
一些成员的初始化(补充)
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;
template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * __VOLATILE
__default_alloc_template<threads, inst> ::free_list[
# ifdef __SUNPRO_CC
__NFREELISTS
# else
__default_alloc_template<threads, inst>::__NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
// The 16 zeros are necessary to make version 4.1 of the SunPro compiler happy. Otherwise it appears to allocate too little space for the array.
//要使SunPro 4.1版本的编译器满意,这16个零是必要的。否则,它似乎为数组分配的空间太少了。