Colors of Ray+Hue'


libvmmalloc initialization analysis


INTRO.

libvmmalloc은 애플리케이션의 코드 수정 없이 PM영역 메모리 할당 해제를 가능하게 한다. 훅을 걸기 위해서는 LD_PRELOAD를 사용해야 하며,  모든 메모리 할당/해제는 jemalloc을 바탕으로 동작한다. PM 영역의 파일시스템 path 및 pool(arena)의 크기를 지정해야 하며, 최초 라이브러리가 후킹 될때 path/pool 이 초기화 된다. 해당 풀을 직접 엑세스 하기 위해서는 반드시 page cache가 bypass 되어야 하므로, DAX를 사용하는 것이 바람직하다. 

초기화 과정은 크게 2가지로 나뉘는데, (i)PM 영역에 대한 메모리 매핑 설정, (ii) jemalloc의 풀 생성이다. 기본적으로 jemalloc을 포함한 모든 메모리 관리자는 커널로부터 메모리를 할당 받아 heap영역을 구성하게 되는데, 메모리 할당은 (s)brk/mmap을 통해 buddy의 free page list로부터 할당 받는다. 그러나, pmem.io에서 PM영역은 buddy에서 관리 되지 않고 file mapped IO를 통해 해당 memory address 를 엑세스를 허용하므로,  jemalloc을 비롯한 모든 malloc 의 초기 메모리 영역을 설정할 수 없다. 이를 위해 메모리 풀을 생성하기 위해 요구되는 과정을 init 함수에서 반드시 해줘야 한다. 다시 말해 je_pool_create에 요구되는 파라미터 (address, size)를 모두 셋업하여 jemalloc이 동작할 수 있는 기반을 마련한다. 

편의를 위해 오류체크등 부가적인 모든 소스코드를 생략한다.


최초 LD_PRELOAD에 의해 library가 load 될때 libvmmalloc_init가 자동으로 호출된다. 이하의 함수는 모두 libvmalloc_init의 함수이다.

가장 먼저 할일은 common_init 함수를 통해 log에 대한 메모리 크기를 페이지크기에 맞게 정렬함과 동시에 valgrind 에 관한 옵션을 설정한다. 

common_init(VMMALLOC_LOG_PREFIX, VMMALLOC_LOG_LEVEL_VAR, VMMALLOC_LOG_FILE_VAR, MMALLOC_MAJOR_VERSION, VMMALLOC_MINOR_VERSION); 

다음 jemalloc에 대한 헤더크기를 설정한다. 

Header_size = roundup(sizeof(VMEM), Pagesize); 

이후 PM 영역으로 사용할 디렉토리와 풀의 크기를 확인한다. getenv를 이용하여 환경변수를 통해 디렉토리 string과 크기를 정하고, 만일 풀의 크기가 최소 크기보다 작을 경우, 에러를 리턴한다. 최소 풀의 크기는 14MB 이다. 

if ((env_str = getenv(VMMALLOC_POOL_SIZE_VAR)) == NULL) {

abort();

} else {

long long v = atoll(env_str);

if (v < 0) {

abort();

}

size = (size_t)v;

}

if (size < VMMALLOC_MIN_POOL) {

abort();


#define VMMALLOC_MIN_POOL ((size_t)(1024 * 1024 * 14)) /* min pool size: 14MB */ 

이제 패스설정 및 풀 크기 확인등 초기과정이 완료 되었고, 해당 디렉토리에 파일 생성 및 실제 매핑에 관한 일을 한다. 관련된 모든 일은 libvmmalloc_create 에서 이루어진다. libvmalloc_create 함수는 풀을 생성한 후 유효한 값으로 채워진 vmp 구조체 포인터를 리턴한다. 

Vmp = libvmmalloc_create(Dir, size); 

이제 가장 중요한 libvmmalloc_create함수를 살펴본다. 먼저 풀이 몇페이지가 요구되는지 확인하고 (1), 헤당 디렉토리에 임시파일 (vmem.XXXXXX) 을 생성한다 (2). posix_fallocate를 통해 필요한 페이지에 대해 블록을 미리 할당한다 (3). posix_fallocate의 경우, 실제 블록이 할당 되는 것은 아니고, 파일 시스템에서 bitmap을 검색하여 empty블록을 검색, 해당 파일에 대한 inode에 블록을 할당한후 unwritten 상태로 세팅을 한다. 이후 util_map을 통해 실제 매핑이 이루어진다 (4). 매핑이 매핑 자체는 해당 풀에 대한 VM를 얻어내는 역할에 불과하며, 실제로 해당 메모리에 대한 엑세스는 발생하지 않는다. 이제 virtual memory pool (vmp) 구조체에 header, base addr, size 등을 모두 채워 넣는다 (5). 이제 jemalloc의 pool을 생성할 모든 준비가 끝났다. jemalloc library에서 제공되는 je_pool_create 의 alias 함수인 je_vmem_pool_create를 호출하여  해더를 제외한 메모리로 사용할 수 있는 base address 와 풀의 크기를 넘긴다 (6).  마지막으로 해당 메모리 영역을 다른 프로세스가 엑세스 할 수 없도록 보호한다 (7). 

static VMEM * libvmmalloc_create(const char *dir, size_t size)

{

1. size = roundup(size, Pagesize);

2. Fd = util_tmpfile(dir, "/vmem.XXXXXX");

3. if ((errno = posix_fallocate(Fd, 0, (off_t)size)) != 0)

4. if ((addr = util_map(Fd, size, 0, 4 << 20)) == NULL)

5.1 struct vmem *vmp = addr;

5.2 memset(&vmp->hdr, '\0', sizeof(vmp->hdr));

5.3 memcpy(vmp->hdr.signature, VMEM_HDR_SIG, POOL_HDR_SIG_LEN);

5.4 vmp->addr = addr;

5.5 vmp->size = size;

5.6 vmp->caller_mapped = 0;

6. if (je_vmem_pool_create((void *)((uintptr_t)addr + Header_size),

7. util_range_none(addr, sizeof(struct pool_hdr));

return vmp;


핵심은 jemalloc 뿐만 아니라 모든 메모리 할당 라이브러리가 PM영역을 사용할 수 없으므로, 기존의 라이브러리들이 메모리로써 사용할 수 있도록 기반 작업을 한다. 이후 hooking된 malloc이 해당 풀(arena)로부터 메모리를 할당 받을 수 있도록 한다. 

'NVM' 카테고리의 다른 글

LD_PRELOAD rootkits  (0) 2016.08.26
libvmmalloc  (0) 2016.08.24
libpmem library  (0) 2016.08.09
libnvdimm  (0) 2016.08.05
PCMSIM  (0) 2016.08.05