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的代码难看性,自行下载吧,下一章有可能是第一次申请大内存或者释放小内存,看心情吧。
完结撒花