ptmalloc - 第一次申請小內存
glibc中malloc的代碼包括了對線程同步,平臺兼容性等問題的處理,但是本系列文章主要的研究對象ptmalloc。所以提供的代碼都是經過簡化,部分宏也會展開,能夠說清楚ptmalloc的運行流程就可以了。
要點
- 用例
- 第一次申請小內存
用例
int main(void)
{
char *mem = malloc(10);
free(mem);
return -1;
}
第一次申請小內存
通過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的代碼難看性,自行下載吧,下一章有可能是第一次申請大內存或者釋放小內存,看心情吧。
完結撒花