glibc中malloc的代码包括了对线程同步,平台兼容性等问题的处理,但是本系列文章主要的研究对象ptmalloc。所以提供的代码都是经过简化,部分宏也会展开,能够说清楚ptmalloc的运行流程就可以了。
通过gdb调试,我们可以看到,第一个进入的函数是__libc_malloc
void *__libc_malloc(size_t bytes) { mstate ar_ptr; void *victim; void *(*hook) (size_t, const void *) = __malloc_hook; if (hook != NULL) { hook(bytes, 0); } ar_ptr = thread_arena; victim = _int_malloc(ar_ptr, bytes); return victim; }查看__malloc_hook的初始赋值
__malloc_hook = malloc_hook_ini;
继续跟踪malloc_hook_ini
static void * malloc_hook_ini (size_t sz, const void *caller) { __malloc_hook = NULL; ptmalloc_init (); return __libc_malloc (sz); } static void ptmalloc_init (void) { if (__malloc_initialized >= 0) return; __malloc_initialized = 0; thread_arena = &main_arena; }从malloc_hook_ini 可以看到,最后调用的就是__libc_malloc,往回看,因为hook被设置为NULL,所以接下来调用的就是_int_malloc,先放些宏出来,如果不想看可以直接跳过去看简化的_int_malloc代码
#define SIZE_SZ sizeof(size_t) /* * 如果size_t的大小是8,那么MALLOC_ALIGN_MASK就是8 * 2 -1 * 2进制是01111111,代码中的作用就是通过位运算将数值16bit对齐 */ #define MALLOC_ALIGN_MASK (SIZE_SZ * 2 - 1) /* * 最小块大小,每次申请返回的内存都是经过大小调整后加上MIN_CHUNK_SIZE * 这样,每次返回的内存前面就包含了两个size_t的数值 * mchunk_size和mchunk_prev_size * x86_64下,sizeof(size_t) = 8,那么MIN_CHUNK_SIZE = 16 */ #define MIN_CHUNK_SIZE \ (offsetof(struct malloc_chunk, fd_nextsize)) /* * (16 + 15) & ~15 = 16 */ #define MINSIZE \ ((MIN_CHUNK_SIZE + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) /* * 注意代码中的是-2,-2 * MINSIZE然后再强转后就是一个很大的数值了 */ #define REQUEST_OUT_OF_RANGE(req) \ ((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE)) #define request2size(req) \ (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \ MINSIZE : \ /* (req + 8 + 15) & ~15 */ ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK) #define checked_request2size(req, sz) \ if (REQUEST_OUT_OF_RANGE (req)) { \ __set_errno (ENOMEM); \ return 0; \ } \ (sz) = request2size (req);当size是10的话,参数nb的值就是32,想知道怎么算出来的直接看以上贴出来的宏就可以了
struct malloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; INTERNAL_SIZE_T mchunk_size; struct malloc_chunk* fd; struct malloc_chunk* bk; struct malloc_chunk* fd_nextsize; struct malloc_chunk* bk_nextsize; }; struct mstate { ... int flags; mchunkptr top; mchunkptr last_remainder; mchunkptr bins[254]; ... }; typedef struct malloc_chunk *mbinptr; typedef struct malloc_state *mstate; #define bin_at(m, i) \ (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) - MIN_CHUNK_SIZE static void malloc_init_state(mstate av) { int i; mbinptr bin; /* Establish circular links for normal bins */ for (i = 1; i < NBINS; ++i) { bin = bin_at (av, i); bin->fd = bin->bk = bin; } #if MORECORE_CONTIGUOUS if (av != &main_arena) #endif av->flags |= NONCONTIGUOUS_BIT; if (av == &main_arena) global_max_fast = 128; av->flags |= FASTCHUNKS_BIT; av->top = bin_at(av, 1); } static void * _int_malloc (mstate av, size_t bytes) { int32_t nb = 32; malloc_init_state(av); return sysmalloc(nb, av); }代码是很多,不过第一次申请也准备到达尽头了,最后一个函数sysmalloc
static struct malloc_par mp_ = { ... .top_pad = 0x20000, ... }; #define PREV_INUSE 0x1 static void * sysmalloc(size_t nb, mstate av) { size_t pagesize = 4096; /* * 可以看出第一次申请内存的时候 * ptmalloc会申请一块很大的内存 */ size_t size = nb + mp_.top_pad + MIN_SIZE; char *brk; /* size必须要pagesize的倍数 */ size = (size + pagesize - 1) & -pagesize; if (size > 0) { brk = sbrk(size); } if (brk != NULL) { av->system_mem += size; } if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem)) av->max_system_mem = av->system_mem; size_t remainder_size; mchunkptr p = av->top, remainder; if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) { remainder_size = size - nb; remainder = p + nb; av->top = remainder; /* * 要记得nb永远是16的倍数,所以数值的第一字节都是0xX0000 */ p->mchunk_size = nb | PREV_INUSE; remainder->mchunk_size = remainder_size | PREV_INUSE return p + MIN_CHUNK_SIZE; } }本来的代码有更多的分支,处理,判断,我上面的代码都简化了很多,如果想感受ptmalloc的代码难看性,自行下载吧,下一章有可能是第一次申请大内存或者释放小内存,看心情吧。
完结撒花