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

+ Recent posts