1. Linux Memory model
리눅스에서는 메모리 모델이 총 3가지 타입이 존재한다.
- FLATMEM
- DISCONTIGMEM
- SPARSEMEM
이렇게 3가지로 나뉘게 되며 나누는 기준은 아래와 같다.
위와 같이 메모리 카드가 존재할 때 빨간 박스로 체크된 부분을 bank라고 한다. 즉 모든 메모리는 여러 bank로 구성되어있다고 생각하면 된다.
여기서 bank를 관리하는 방법에 따라 위에서 언급한 Memory model의 타입이 결정된다.
FLATMEM, DISCONTIGMEM, SPARSEMEM은 아래와 같이 메모리를 관리한다.
1) FLATMEM
FLATMEM의 경우 모든 bank가 연속적으로 이어져있는 하나의 area로 이루어져있다.
할당가능한 메모리 영역을 구분하기 위한 일종의 block된 DMZ와 같은 역할을 하는 area를 Hole이라고 하는데 FLATMEM의 경우 hole이 없고 연속적으로 0x0000 ~ 0xFFFF까지 모두 할당가능한 하나의 메모리 area를 가진다.
가장 기본적인 형태로 UMA메모리 관리기법 사용이 가능하다.
2) DISCONTIGMEM
FLATMEM과 유사한 방법이나 hole이 존재하는 차이를 가지고 있다. 즉 Memory내 bank들이 연속적이지 않고 bank별로
Bank1 = 0x0000~0x1FFF
Bank2 = 0x3000~0xFFFF
와 같이 각각 bank별로 가지고 있는 주소 값이 연속적이지 않다.
위와 같은 메모리 모델은 UMA, NUMA 방식의 메모리 관리기법 사용이 가능하다.
DISCONTIGMEM방식에서 비연속적인 메모리를 관리하기 위해 사용한 방법이 바로 page개념이다. PFN(page frame number) 즉 각 page마다 고유 number를 명시하고 아래와 같이 page frame과 구조체를 연결해주는 convert동작이 필요하다.
간단히 다시 정리하면 비연속적으로 메모리를 할당하기 때문에 실제 물리 주소를 가리키는 page frame이 존재하고 code에서 할당 및 접근을 진행할 때 구조체 단위로 진행하며 각각의 구조체를 page frame으로 연결시켜서 physical address에 접근하도록 하는 방법이다. 이렇게 함으로써 비연속적으로 data가 메모리에 저장되어있더라도 찾아갈 수 있는 것이다.
3) SPARSEMEM(ARM64)
SPARSEMEM의 경우 hole이 존재하는 것은 DISCONTIGMEM과 같으나 SPARSEMEM의 가장 큰 특징은 section별로 메모리를 나누어서 관리한다는 것이다.
기본적으로 구조체와 physical memory의 section을 연결시켜주는 convert 과정을 거침으로써 비연속적인 메모리 구조 할당과 접근을 지원하는 방법은 맞으나 page가아닌 section별로 관리를 진행한다.
또한 section별로 동적으로 hot-plug in/out이 가능하다. 이러한 동적인 기능을 바탕으로 SPARSEMEM은 활성화된 section에 대해서만 그때 그때 physical address와 연결하여 구조체로 접근하는 사용자 code입장에서는 연속적으로 접근하는 것과 같은 성능을 낸다.
SPARSEMEM의 section처리 방식은
CONFIG_SPARSEMEM_STATIC
CONFIG_SPARSEMEM_EXTREME
2가지 방식으로 존재하며 section개수가 적을 때는 정적으로 할당해서 사용하는 CONFIG_SPARSEMEM_STATIC을 사용하고
Section개수가 많을 때는 동적으로 2단계 table등의 방법을 지원하는 CONFIG_SPARSEMEM_EXTREME을 사용한다.
즉 위와 같이 mem_section table단계를 거쳐 하나의 section의 address값을 찾아갈 수 있다.
/mm/sparse.c
void __init sparse_init(void) { unsigned long pnum_end, pnum_begin, map_count = 1; int nid_begin; memblocks_present(); pnum_begin = first_present_section_nr(); nid_begin = sparse_early_nid(__nr_to_section(pnum_begin)); /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */ set_pageblock_order(); for_each_present_section_nr(pnum_begin + 1, pnum_end) { int nid = sparse_early_nid(__nr_to_section(pnum_end)); if (nid == nid_begin) { map_count++; continue; } /* Init node with sections in range [pnum_begin, pnum_end) */ sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count); nid_begin = nid; pnum_begin = pnum_end; map_count = 1; } /* cover the last node */ sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count); vmemmap_populate_print_last(); } |
SPARSEMEM 사용을 위한 init function이다.
가장 먼저 현재 사용하고 있는 memblock에 대한 조사를 memblocks_present()로 가져온다. NUMA라면 node별로 시작,끝 주소등을 싹 가져온다.
이후 first_present_section_nr()로 가장 첫 번째 section을 가져온다.
그리고 CONFIG_HUGETLB_PAGE_SIZE_VARIABLE 값을 바탕으로 section별로 initialize작업을 반복문을 통해서 진행한다.
static void __init sparse_init_nid(int nid, unsigned long pnum_begin, unsigned long pnum_end, unsigned long map_count) { struct mem_section_usage *usage; unsigned long pnum; struct page *map; usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid), mem_section_usage_size() * map_count); if (!usage) { pr_err("%s: node[%d] usemap allocation failed", __func__, nid); goto failed; } sparse_buffer_init(map_count * section_map_size(), nid); for_each_present_section_nr(pnum_begin, pnum) { unsigned long pfn = section_nr_to_pfn(pnum); if (pnum >= pnum_end) break; map = __populate_section_memmap(pfn, PAGES_PER_SECTION, nid, NULL); if (!map) { pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.", __func__, nid); pnum_begin = pnum; sparse_buffer_fini(); goto failed; } check_usemap_section_nr(nid, usage); sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage, SECTION_IS_EARLY); usage = (void *) usage + mem_section_usage_size(); } sparse_buffer_fini(); return; failed: /* We failed to allocate, mark all the following pnums as not present */ for_each_present_section_nr(pnum_begin, pnum) { struct mem_section *ms; if (pnum >= pnum_end) break; ms = __nr_to_section(pnum); ms->section_mem_map = 0; } |
실제적으로 section별로 initialize를 진행하는 funtion으로 해당 funtion에서 sparse_early_usemaps_alloc_pgdat_section() 를 통해서
각 section에대한 Memblock alloc을 할당 진행하고 section에 대한 할당이 진행된 다음에는
Pfn을 통해서 page와 연결할 수 있도록 memory map작업을 진행한다.
__populate_section_memmap()이 바로 할당된 section에 대하여 memory map을 진행하는 funtion이다.
- Ref
https://www.codenong.com/cs106323088/
https://zhuanlan.zhihu.com/p/220068494
코드로 알아보는 Arm64 Linux kernel
https://elixir.bootlin.com/linux/v5.14.16/source/mm/sparse.c
'OS > Linux' 카테고리의 다른 글
[Linux Kernel] Kernel 분석(v5.14.16) - Buddy (1) (0) | 2022.02.26 |
---|---|
[Linux Kernel] Kernel 분석(v5.14.16) - Memory Zone & Sparse (0) | 2022.02.13 |
[Linux Kernel] NUMA 메모리 관리 기법 (0) | 2022.01.31 |
[Linux Kernel] Kernel 분석(v5.14.16) - memblock (0) | 2022.01.23 |
[Linux Kernel] Kernel 분석(v5.14.16) - I/O mapping (0) | 2021.12.26 |