Dynamic memory allocator (glibc malloc)
Heap

Dynamic memory allocator (glibc malloc)

오늘은 Dynamic memory allocator를 정리할거다.

 

근데 다른 Dynamic memory allocator 중에서도 glibc의 malloc을 주로 정리할것이다.

 

우선 Dynamic memory allocator는 동적으로 할당된 메모리를 관리하기 위해서

사용하는 것이다.

 

Allocator는 크게 두 종류로 나뉜다.

 

1. Explicit allocator : 개발자가 공간의 할당 / 해제를 관리

EX) libc의 malloc과 free

 

2. Implicit allocator : 개발자는 공간의 할당만 담당하고 free는 내부적으로 처리

EX) Java의 GC, Lisp등

 

 

Explicit allocator에는 위 예시 말고도 여러가지 종류가 있다.

 

- dlmalloc -

리눅스 초창기에 사용된 기본 메모리 할당자. dlmalloc에서 동일한 시간에 2개의 스레드가 malloc을

호출할 경우, freelist 데이터는 모두 사용가능한 Thread에 둘러싸인 상태로 공유되기 때문에

오로지 하나의 쓰레드만 임계영역에 들어갈 수 있음.

 

- ptmalloc2 -

dlmalloc에서는 Threading 지원 기능이 추가됨. ptmalloc2는 glibc 소스 코드에 통합됨.

동일한 시간에 2개의 Thread가 malloc을 호출할 경우, 메모리는 각가의 쓰레드가 분배된

heap 영역을 일정하게 유지하고, 힙을 유지하기 위한 freelist 데이터 또한 분배되어 있기 때문에 즉시 할당.

 

- Jemalloc -

Jason Evnas가 만들었고, 페이스북이나 파이어폭스에서 주로 사용함.

단편화 방지 및 동시 확장성을 강조한 할당자.

 

- tcmalloc -

구글이 만든 성능 도구에 포함되어 있는 힙 메모리 할당자로서 크롬 및 많은

프로젝트에서 사용. 멀티 쓰레드 환경에서 memory pull을 사용하는 속도를 개선하기 위한 목적으로 만들어졌다.

 

 

 

- glibc malloc -

이제 진짜 본격적으로 이 글의 주제인 glibc의 malloc에 대해서 제대로 정리해보자

우선 glibc는 Explicit allocation 중 ptmalloc2를 사용한다.

 

위에서 설명한것처럼 ptmalloc2는 동일한 시간에 2개의 Thread가 malloc을 호출할 경우

메모리는 각각의 Thread가 분배된 힙 영역을 일정하게 유지하고, 힙을 유지하고 위한

freelist data structures 또한 분배되어 있기 때문에 allocation이 즉시 이루어진다.

 

이렇게 각각의 Thread 유지를 위해 분배된 heap과 freelist data structures의 행동을 per thread arena라고 부른다.

 

ptmalloc2에서는 Thread 관련 처리가 어떤 식으로 진행되는지 코드로 확인하자.

 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
 
void* threadFunc(void* arg) {
        printf("Before malloc in thread 1\n");
        getchar();
        char* addr = (char*) malloc(1000);
        printf("After malloc and before free in thread 1\n");
        getchar();
        free(addr);
        printf("After free in thread 1\n");
        getchar();
}
 
int main() {
        pthread_t t1;
        void* s;
        int ret;
        char* addr;
 
        printf("Welcome to per thread arena example::%d\n",getpid());
        printf("Before malloc in main thread\n");
        getchar();
        addr = (char*) malloc(1000);
        printf("After malloc and before free in main thread\n");
        getchar();
        free(addr);
        printf("After free in main thread\n");
        getchar();
        ret = pthread_create(&t1, NULL, threadFunc, NULL);
        if(ret)
        {
                printf("Thread creation error\n");
                return -1;
        }
        ret = pthread_join(t1, &s);
        if(ret)
        {
                printf("Thread join error\n");
                return -1;
        }
        return 0;
}

 

 

getchar() 하는 부분마다 bp를 걸고 하나하나 살펴보자

 

 

1. 첫번째 break point -> main thread에서 malloc 이전

main thread는 만들어졌으며, heap은 아무것도 할당이 되어 있지 않다.

 

2. 두번째 break point -> malloc 호출 이후 & free 호출 이후

새로운 thread는 생성되지 않았고 heap 영역이 생성된것을 볼 수 있다.

 

 

3. 세번째 break point -> main thread에서 free 호출 이후

free 이후에도 heap 영역이 그대로 할당이 되어 있는것을 볼 수 있다.

free가 호출되어 메모리가 해제된 경우에, 즉각적으로 OS에 반환되지 않는 것이다.

 

할당된 메모리의 일부분만 오로지 main area의 bin에 free된 chunk를 추가한다.

 

free 이후 사용자가 malloc을 하는 경우 바로 할당하는 것이 아닌

bin에서 비어있는 공간을 탐색하고 존재하면 그 비어있는 공간을 할당한다.

 

4. 네번째 break point -> second thread 생성, malloc 호출 이전

 

새로운 Thread가 생성이 되고 second thread에 대한 stack이 할당되었다.

 

second thread는 main thread와 code, data, heap 영역은 공유하지만

stack은 각 thread 별로 할당이 된다.

 

5. 다섯번째 break point -> second thread에서 free 호출

second thread에 heap 영역이 할당된 것을 볼 수 있다.

main thread에서는 heap 영역에 할당할때에 brk를 사용해서 할당하였지만

second thread에서는 mmap을 사용하여서 heap에 할당한다고 한다.

 

 

6. 여섯번째 break point -> second thread에서 free 호출

second thread 역시 main thread와 마찬가지로 힙에 할당한

메모리를 바로 반환하지 않고 bin에 추가한다.

 

위와 같은 방식으로 메인 쓰레드와 추가적인 쓰레드에서 malloc과 free가 일어난다.

glibc의 malloc의 소스코드에서 다음과 같은 3개의 데이터 구조체를 확인 할 수 있다.

 

1. heap info 구조체 (Heap_Header)

 

typedef struct _heap_info{
    mstate ar_ptr;
    struct _heap_info *prev;
    size_t size;
    size_t mprotect_size;
    char pad[(-6*SIZE_SZ)&MALLOC_ALIGN_MASK];
}

thread_arena (extra thread에 할당된 힙 영역의 인접한 공간)는 힙 영역의 공간의 부족하면

새로운 area를 추가로 할당 받으므로 여러개의 힙 area를 가질 수 있다.

 

하지만 main_arena(main thread에 할당된 힙 영역의 인접한 공간)은 여러 개의

힙 area를 가질 수 없다.

 

main_arena의 경우 힙 공간이 부족한 경우에는 메모리가 매핑된 영역까지 할당된다.

 

그래서 이런 힙 영역은 어떤 arena (main_arena or thread_arena)가 관리하는지,

힙 영역의 크기가 어느정도인지, 이전에 사용하던 메모리의 정보가 어디에 있는지를

저장해야 한다.

 

이런 정보를 저장하기 위한 struct가 바로 위에 heap_info이다.

힙에 대한 정보를 저장하여 Heap_Header라고도 한다.

 

 

2.  malloc_state 구조체 (Arena_Header)

struct malloc_state
{
  /* Serialize access  */
  mutex_t mutex;
 
  /* Flags (formerly in max_fast)  */
  int flags;
 
  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];
 
  /* topchunk의 base address */
  mchunkptr top;
 
  /* 가장 최근의 작은 요청으로부터 분리된 나머지 */
  mchunkptr last_remainder;
 
  /* 위에서 설명한대로 pack된 일반적인 bins */
  mchunkptr bins[NBINS *  - ];
 
  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];
 
  /* 연결 리스트 */
  struct malloc_state *next;
 
  /* 해제된 아레나를 위한 연결 리스트 */
  struct malloc_state *next_free;
 
  /* 현재 Arena의 시스템으로부터 메모리 할당  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

 

첫번째 구조체인 Heap_Header에서는 단순히 힙 영역에 대한 정보만을 저장하였다.

malloc_state struct는 각 arena에 하나씩 주어지고, free된 chunk를 관리하는 bin과

Top Chunk와 같은 arena에 대한 정보를 저장한다. 우리는 이를 Arena_Header라고 부른다

 

3. malloc_chunk 구조체 (Chunk_Header)

struct malloc_chunk {
    INTERNAL_SIZE_T prev_size;
    INTERNAL_SIZE_T size;

    struct malloc_chunk* fd;
    struct malloc_chunk* bk;

    struct malloc_chunk* fd_nextsize;
    struct malloc_chunk* bk_nextsize;
};

 

malloc_chunk struct는 이름에서도 알 수 있듯이 chunk에 대한 정보를 담고 있는 struct이다.

 

 1) prev_size : 바로 이전 chunk의 size를 저장

2) size : 현재 chunk size를 저장

3) fd, bk : malloc시 데이터가 들어가고, free시 fd,bk 포인터로 사용

4) fd_nextsize, bk_nextsize : large bin을 위해 사용되는 포인터

 

 

Reference : https://wogh8732.tistory.com/178

 

 

 

'Heap' 카테고리의 다른 글

ptmalloc free & bin  (0) 2021.10.20
Structure of Chunk  (0) 2021.10.16