1. infomation
•
uClibc 0.9.33.2 기준으로 설명. 32bit 기준.
•
•
malloc, free에 초점하여 소스코드 분석.
•
중요한 부분만 상세설명하며 매크로 함수 역시 중요한 부분만 상세 설명. 나머지는 간략 표시.
•
가독성을 위해 설명에 필요없는 라인(주석 포함)은 삭제.
•
libc/stdlib/malloc-standard/malloc.c, malloc.h
2. heap arena
struct malloc_state {
/* The maximum chunk size to be eligible for fastbin */
size_t max_fast; /* low 2 bits used as flags */
// 하위 2bits는 flags로 설정
// 첫 하위 1bit[FASTCHUNKS_BIT] -> any free 청크가 있는지 체크. 있다면 1로 세팅
// ㄴ오로지 malloc_consolidate시 에만 0으로 설정됨
// 그 다음 1bit[ANYCHUNKS_BIT] -> fastbin에 free청크가 존재한다면 1로 세팅.
/* Fastbins */
mfastbinptr fastbins[NFASTBINS]; // NFASTBINS==10, fastbins[10]
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2]; // NBINS==96, bins[192]
/* Bitmap of bins. Trailing zero map handles cases of largest binned size */
unsigned int binmap[BINMAPSIZE+1];
...
C
복사
libc/stdlib/malloc-standard/malloc.h
•
glibc의 heap 구조를 알고 있다는 전제로 설명.
•
malloc_state 구조는 다음과 같다. glibc와 유사하며 fastbin은 10개의 빈을 가진다.
•
fastbin의 최대 크기는 80이다
•
fastbin 다음에 top 청크의 주소와 last_remainder 청크가 온다.
•
그다음 unsorted bin, small bin, large bin 을 관리하는 bins[192]가 온다.
3. malloc -fastbin 검색
void* malloc(size_t bytes)
{
mstate av;
size_t nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */
mfastbinptr* fb; /* associated fastbin */
mchunkptr victim; /* inspected/selected chunk */
size_t size; /* its size */
int victim_index; /* its bin index */
mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */
unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */
mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */
void * sysmem;
void * retval;
av = get_malloc_state();
// main_arena 객체 가져오기.
checked_request2size(bytes, nb); // 매크로 함수. 아래 주석 참조
/*
#define checked_request2size(req, sz) \
if (REQUEST_OUT_OF_RANGE(req)) { \
errno = ENOMEM;// 12 \
return 0; \
} //요청 크기가 REQUEST_OUT_OF_RANGE 보다 크면 안됨.
//최대 요청 크기는 0x0xFFFFFFDF
(sz) = request2size(req);
// 요청크기 + 메타데이터(prev_size,size) 8bytes를 포함할 수 있는 크기 반환.
// ex) 요청크기 0x20 -> 0x28 반환. 상세 구조는 request2size 매크로 확인.
*/
/*
av->max_fast의 하위 1bit가 1이면 현재 free된 청크가 없다는 의미.
아직 free된 청크가 없는 경우,
top 청크에서 split 하여 할당.
*/
if (!have_anychunks(av)) {
if (av->max_fast == 0) /* initialization check */
__malloc_consolidate(av);
goto use_top;
}
C
복사
malloc.c
•
uClibc-0.9.33.2 를 사용하는 httpd main 함수의 시작 부분에서 av 값을 확인한 메모리 구조이다.
•
av→max_fast의 크기는 0x48로 설정되어 있다.
•
초기상태이므로 max_fast(0x48)의 하위 두 bits는 0으로 설정되어 있다
•
max_fast 다음 부터 4*10이 fastbin[0~10] 이다. (Top 전 까지)
◦
초기 max_fast는 0x48로 설정되어 있으며, 두 bits에 따른 표현 범위는 다음과 같다
◦
FASTCHUNKS_BIT : 0 ANYCHUNKS_BIT : 0 ⇒ 0x48
◦
FASTCHUNKS_BIT : 0 ANYCHUNKS_BIT : 1 ⇒ 0x49
◦
FASTCHUNKS_BIT : 1 ANYCHUNKS_BIT : 1 ⇒ 0x4b
◦
따라서 0x48 사이즈 기준 실제 사용하는 fastbin은 0~7이다.
▪
(각 사이즈별 크긴는 아래서 설명)
•
초기 bins 들은 아직 free된 청크가 없으므로 fd,bk가 동일한 값을 가지고 있다.
void* malloc(size_t bytes)
{
...
/*
If the size qualifies as a fastbin, first check corresponding bin.
#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2)
*/
if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
fb = &(av->fastbins[(fastbin_index(nb))]);
if ( (victim = *fb) != 0) {
*fb = victim->fd;
// fastbin에 들어있는 청크를 재할당 해주고 fd에 들어있는
// 다음 free 청크를 앞으로 땡겨야하기 때문에 fb에 해당 청크 주소 저장
check_remalloced_chunk(victim, nb); //재할당 하려는 청크 사이즈 유효성 검증
retval = chunk2mem(victim); // 청크헤더+8 주소 반환
goto DONE;
}
}
C
복사
•
nb(요청사이즈+헤더(8))가 fastbin 사이즈에 만족하면(≤ av→max_fast)
•
fastbin_index() 매크로 함수를 이용하여 fasbin 인덱스를 찾는다.
•
각 인덱스 별 fastbin 크기는 다음과 같다.(실제 사용 인덱스 0~7, fastbin은 단일 연결 리스트)
◦
fastbin[0] : 0x10 ~ 0x17
◦
fastbin[1] : 0x18 ~ 0x1f
◦
fastbin[2] : 0x20 ~ 0x27
◦
fastbin[3] : 0x28 ~ 0x2f
◦
fastbin[4] : 0x30 ~ 0x37
◦
fastbin[5] : 0x38 ~ 0x3f
◦
fastbin[6] : 0x40 ~ 0x47
◦
fastbin[7] : 0x48 ~ 0x4f
4. malloc - smallbin 검색
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
MIN_LARGE_SIZE = 256
#define in_smallbin_range(sz) ((unsigned long)(sz) < (unsigned long)MIN_LARGE_SIZE)
#define smallbin_index(sz) (((unsigned)(sz)) >> 3)
/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i) ((mbinptr)((char*)&((m)->bins[(i)<<1]) - ((sizeof(size_t))<<1)))
#define first(b) ((b)->fd)
#define last(b) ((b)->bk)
*/
if (in_smallbin_range(nb)) { // 최대 smallbin 크기는 255
idx = smallbin_index(nb);
bin = bin_at(av,idx); // 인덱스에 맞는 bin 주소 가져오기
if ( (victim = last(bin)) != bin) {
// 재할당 하려는 bin에 연결된 다른 free 청크들이 있다면 연결 해제
bck = victim->bk;
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = bin;
// A -> B -> C 구조에서 B를 재할당 하려면
// A -> C 로 free list 재구성
// B를 재할당 하기 때문에 C 청크의 inuse bit 설정
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim); //청크헤더+8 주소 반환
goto DONE;
}
}
else {
idx = __malloc_largebin_index(nb);
if (have_fastchunks(av))
__malloc_consolidate(av);
}
C
복사
•
요청 크기가 fastbin 크기가 아니거나, 현재 fastbin에 가용할 청크가 없다면 해당 루틴으로 내려온다.
•
따라서 bin[2] ~ bin[16] 크기는 fastbin[0]~fastbin[7] 크기와 동일하다.
•
smallbin 최대 크기는 255로 이보다 작은 값이면 smallbin_index를 계산한다.
•
fastbin 을 제외한 unsortedbin, smallbin, largebin은 이중연결 리스트로 bin의 각 인덱스 당 2개 씩 가진다.(fd,bk)
•
청크 최소 사이즈는 0x10이기 때문에 idx는 0x10>>3 : 2부터 시작한다.
•
각 인덱스 별 최소 smallbin 크기는 다음과 같다.(idx 0은 사용X)
◦
idx 2 ⇒ bin[2-3] : 0x10 // unsorted bin
◦
idx 3 ⇒ bin[4-5] : 0x18
◦
idx 4 ⇒ bin[6-7] : 0x20
◦
idx 5 ⇒ bin[8-9] : 0x28
◦
idx 6 ⇒ bin[10-11] : 0x30
◦
idx 7 ⇒ bin[12-13] : 0x38
◦
idx 8 ⇒ bin[14-15] : 0x40
◦
idx 9 ⇒ bin[16-17] : 0x48
◦
idx 10 ⇒ bin[18-19] : 0x50
◦
idx 11 ⇒ bin[20-21] : 0x58
◦
…
◦
idx 31 ⇒ bin[30-31] : 0xf8
/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
MIN_LARGE_SIZE = 256
#define in_smallbin_range(sz) ((unsigned long)(sz) < (unsigned long)MIN_LARGE_SIZE)
#define smallbin_index(sz) (((unsigned)(sz)) >> 3)
#define bin_at(m, i) ((mbinptr)((char*)&((m)->bins[(i)<<1]) - ((sizeof(size_t))<<1)))
#define first(b) ((b)->fd)
#define last(b) ((b)->bk)
#
*/
if (in_smallbin_range(nb)) { // 최대 smallbin 크기는 255
idx = smallbin_index(nb);
bin = bin_at(av,idx); // 인덱스에 맞는 bin 주소 가져오기
...
}
}
else {
// 요청 사이즈가 255보다 크다면 largebin에서 검색
// __malloc_largebin_index -> bin[32] 부터 각 bin당 64bytes 크기
idx = __malloc_largebin_index(nb);
if (have_fastchunks(av))
__malloc_consolidate(av);
}
C
복사
•
255보다 큰 값 일 경우 large bin의 index를 가져온다.
•
largebin 인덱스는 idx 32 부터 시작한다.
idx 32 => bin[64-65] : 0x100 ~ 0x13f
idx 33 => bin[66-67] : 0x140 ~ 0x17f
…
C
복사
•
largebin index 계산 후 fastbin에 청크가 존재할 경우 malloc_consolidate를 호출하여 fastbin 청크들의 병합을 수행한다.
•
병합된 fastbin 청크들은 unsorted bin에 위치한다.
5. malloc - unsortedbin 검색
/*
Process recently freed or remaindered chunks, taking one only if
it is exact fit, or, if this a small request, the chunk is remainder from
the most recent non-exact fit. Place other traversed chunks in
bins. Note that this step is the only place in any routine where
chunks are placed in bins.
#define unsorted_chunks(M) (bin_at(M, 1)) // -> bin[0]
#define bin_at(m, i) ((mbinptr)((char*)&((m)->bins[(i)<<1]) - ((sizeof(size_t))<<1)))
*/
while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
// unsortedbin에 가용 가능한 청크가 존재하는 경우. 소진 될 때 까지 반복
bck = victim->bk; // unsorted bin의 맨 뒤 청크
size = chunksize(victim);
/* If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
if (in_smallbin_range(nb) && // 요청 크기가 smallbin 크기고
bck == unsorted_chunks(av) && // unsorted bin에 한 개의 청크만 있고
victim == av->last_remainder && // 그 청크가 last_remainder라면
(unsigned long)(size) > (unsigned long)(nb + MINSIZE)) {
// unsorted bin에 있는 청크 크기가 요청크기+MINSIZE보다 크다면
// last_remainder 청크를 split 수행
/* split and reattach remainder */
remainder_size = size - nb; // split 후 남은 크기
remainder = chunk_at_offset(victim, nb); // split 한 청크
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
// split 하고 남은 청크를 새로운 unsorted bin으로 등록
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks(av);
// split 하고 남은 청크는 또한 last_remainder 청크가 된다.
set_head(victim, nb | PREV_INUSE);
set_head(remainder, remainder_size | PREV_INUSE);
set_foot(remainder, remainder_size);
check_malloced_chunk(victim, nb); // split하여 만든 청크 재할당
retval = chunk2mem(victim);
goto DONE;
}
/* remove from unsorted list */
// 요청크기가 smallbin 크기가 아니거나
// unsorted bin에 여러개의 청크가 있거나
// victim이 last_remainder가 아닐경우
// unsorted bin 탐색에 알맞는 청크 검색이 실패했다는 의미
// unsorted bin에서 제거한다
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
/* Take now instead of binning if exact fit */
// 제거한 거를 일단 처리를 해야하니까
// victim의 크기가 요청 크기와 딱 맞다면 해당 청크 반환
if (size == nb) {
set_inuse_bit_at_offset(victim, size);
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
}
/* place chunk in bin */
// 여기까지 왔다면 unsorted bin의 탐색 실패를 의미. 각자의 bin으로 집어넣기 수행
if (in_smallbin_range(size)) { // smallbin일 경우 smallbin에 넣기
victim_index = smallbin_index(size);
bck = bin_at(av, victim_index);
fwd = bck->fd;
}
else {//largebin일 경우 largebin에 넣기
victim_index = __malloc_largebin_index(size);
bck = bin_at(av, victim_index);
fwd = bck->fd;
if (fwd != bck) {
/* if smaller than smallest, place first */
//largebin 크기 정렬 수행
if ((unsigned long)(size) < (unsigned long)(bck->bk->size)) {
fwd = bck;
bck = bck->bk;
}
else if ((unsigned long)(size) >=
(unsigned long)(FIRST_SORTED_BIN_SIZE)) {
/* maintain large bins in sorted order */
size |= PREV_INUSE; /* Or with inuse bit to speed comparisons */
while ((unsigned long)(size) < (unsigned long)(fwd->size))
fwd = fwd->fd;
bck = fwd->bk;
}
}
}
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
}
C
복사
•
만약
◦
fastbin에 가용 가능한 청크가 없거나
◦
smallbin에 가용가능한 청크가 없거나
◦
largebin 요청 크기거나
•
할 경우 해당 로직으로 온다.
•
unsortedbin에 가용가능한 청크를 검색하고 존재한다면 분기문 안으로 들어온다.
6. malloc -Largebin 검색
/*
If a large request, scan through the chunks of current bin to
find one that fits. (This will be the smallest that fits unless
FIRST_SORTED_BIN_SIZE has been changed from default.) This is
the only step where an unbounded number of chunks might be
scanned without doing anything useful with them. However the
lists tend to be short.
*/
if (!in_smallbin_range(nb)) { //smallbin 크기가 아니면 largebin에서 검색
bin = bin_at(av, idx);
for (victim = last(bin); victim != bin; victim = victim->bk) {
size = chunksize(victim);
if ((unsigned long)(size) >= (unsigned long)(nb)) {
remainder_size = size - nb;
unlink(victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset(victim, size);
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
}
/* Split */ //split하여 남은 청크를 unsortedbin에 삽입
else {
remainder = chunk_at_offset(victim, nb);
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
remainder->bk = remainder->fd = unsorted_chunks(av);
set_head(victim, nb | PREV_INUSE);
set_head(remainder, remainder_size | PREV_INUSE);
set_foot(remainder, remainder_size);
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim); // 세팅 끝난 청크 반환
goto DONE;
}
}
}
}
C
복사
•
Largebin에서 검색하여 할당 가능한 청크를 검색한다.
•
요청 크기를 처리할 수 있는 청크가 존재할 경우 split하여 가용가능한 남은 청크 크기가 존재하면 unsortedbin에 넣어두고
•
split한 청크를 반환한다.
7. malloc -binmap 검색
/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.
The bitmap avoids needing to check that most blocks are nonempty.
*/
++idx;
bin = bin_at(av,idx);
block = idx2block(idx);
map = av->binmap[block];
bit = idx2bit(idx);
for (;;) {
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0) {
do {
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} while ( (map = av->binmap[block]) == 0);
bin = bin_at(av, (block << BINMAPSHIFT));
bit = 1;
}
/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0) {
bin = next_bin(bin);
bit <<= 1;
assert(bit != 0);
}
/* Inspect the bin. It is likely to be non-empty */
victim = last(bin);
/* If a false alarm (empty bin), clear the bit. */
if (victim == bin) {
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin(bin);
bit <<= 1;
}
else {
size = chunksize(victim);
/* We know the first chunk in this bin is big enough to use. */
assert((unsigned long)(size) >= (unsigned long)(nb));
remainder_size = size - nb;
/* unlink */
bck = victim->bk;
bin->bk = bck;
bck->fd = bin;
/* Exhaust */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset(victim, size);
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
}
/* Split */
else {
remainder = chunk_at_offset(victim, nb);
unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
remainder->bk = remainder->fd = unsorted_chunks(av);
/* advertise as last remainder */
if (in_smallbin_range(nb))
av->last_remainder = remainder;
set_head(victim, nb | PREV_INUSE);
set_head(remainder, remainder_size | PREV_INUSE);
set_foot(remainder, remainder_size);
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
}
}
}
C
복사
•
fastbin, smallbin,largebin,unsortedbin들에서 가용가능한 청크가 없다면
•
binmap을 참조하여 할당 가능한 청크를 검색한다.
7. malloc -Top 검색
use_top:
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).
We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhuasted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/
victim = av->top;
size = chunksize(victim);
if ((unsigned long)(size) >= (unsigned long)(nb + MINSIZE)) {
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
set_head(victim, nb | PREV_INUSE);
set_head(remainder, remainder_size | PREV_INUSE);
check_malloced_chunk(victim, nb);
retval = chunk2mem(victim);
goto DONE;
}
/* If no space in top, relay to handle system-dependent cases */
sysmem = __malloc_alloc(nb, av);
retval = sysmem;
DONE:
__MALLOC_UNLOCK;
return retval;
C
복사
•
그래도 못찾았다면 Top 청크에서 검색
•
Top 청크에서도 마땅한 청크가 없다면 메모리 추가 요청 후 할당