[커널 17차] 44주차

2021.07.04 01:11

ㅇㅇㅇ 조회 수:76

bootmem_init()
- sparse_init()에서 계속

 

sparse_init()
- memblocks_present() 이후부터 계속
- first_present_section_nr() 함수로 첫 번째 present section number를 pnum_begin으로 가져온다
- sparse_early_nid() 함수로 pnum_begin section의 nid를 nid_begin으로 가져온다
- CONFIG_HUGETLB_PAGE_SIZE_VARIABLE = false이므로 함수 set_pageblock_order()는 빈 함수이다
- pageblock_order는 매크로로 9로 정의되어 있다
- 매크로 for_each_present_section_nr()를 이용해서 present section number에 대해 루프를 수행한다
1) 현재 section의 nid를 가져온다
2) nid가 같으면 map_count를 1 증가시키고 다음 present section으로 넘어간다. 이 방법으로 nid가 같은 section 개수를 센다.
3) nid가 다르면 sparse_init_nid() 함수로 현재 nid에 해당하는 모든 section에 대해 2^18개의 page 구조체 메모리를 할당해서 가리키도록 한다. 즉 mem_section 포인터의 두 번째 entry가 하나의 section을 커버하고, 이는 2^18개의 page 구조체를 가리킨다.
4) 다음 nid에 대해서도 같은 작업을 수행한다
- vmemmap_populate_print_last() 함수는 arm64에서는 빈 함수이다


first_present_section_nr()
- next_present_section_nr(-1)로 첫 번째 present section number를 리턴한다

 

next_present_section_nr()
- 입력 인자로 들어온 section number의 다음 present section number를 리턴한다
- 다음 present section이 없으면 -1을 리턴한다

 

present_section_nr()
- section number를 입력으로 받아 해당 section이 present section인지 리턴한다
- section_mem_map의 SECTION_MARKED_PRESENT bitfield를 이용한다

 

sparse_early_nid()
- section_mem_map의 SECTION_NID_SHIFT를 이용해서 해당 section의 nid를 리턴한다
- section_mem_map의 nid는 sparse_init()에서만 유효하고, sparse_init()이 끝나면 지워진다

 

for_each_present_section_nr() 매크로
- next_present_section_nr() 매크로를 이용해서 present section number를 iterate한다
- section number가 -1이 나오거나 __highest_present_section_nr보다 크면 끝난다


sparse_init_nid()
- 한 노드에 대한 모든 section에 대해 page 구조체 메모리를 만들고 가리키게 한다
- 한 section(1GB) 당 총 2^18개의 page 구조체로 관리한다 (2^30/2^12 = 2^18)


- sparse_early_usemaps_alloc_pgdat_section() 함수로 각 section 관리를 위한 meta data인 mem_section_usage (이하 usage) 메모리 영역을 할당한다.
- usage는 node에 대한 정보를 가지고 있는 pgdat(node_data 전역 변수 배열 엔트리)과 동일한 section에 있어야 나중에 hot-plug 및 hot-remove에 유리하므로 메모리 요청시 가급적 같은 nid 및 section에 위치하도록 요청한다. 이를 위해 메모리 요청 함수에 pgdat 포인터도 같이 전달한다.
- usage 메모리는 한 section 당 mem_section_usage 구조체 크기 + usemap_size()로 주어지며 함수 mem_section_usage_size()로 계산된다.
- 구조체 mem_section_usage는 512bit + 64bit = 72 byte로 구성된다
- usemap_size()는 2048bit = 256 byte로 구성된다
- 따라서 총 (72B + 256B) x map_count 만큼의 usage 메모리를 요청한다
- 메모리 요청이 실패하면 failed 레이블로 이동한다

- sparse_buffer_init() 함수로 각 section이 관리해야할 page 구조체 메모리 영역을 요청한다
- 한 section 당 2^18개 page 구조체가 필요하고 같은 nid를 가지는 section을 한 번에 처리하고 있으므로 2^18개 page 구조체 x map_count 만큼의 메모리를 요청한다
- 요청시 nid도 전달하여 가급적 동일한 노드의 메모리를 할당받을 수 있도록 한다
- for_each_present_section_nr() 매크로로 각 present section에 대해 iteration을 수행한다
1) 각 section에 대해 pfn을 구한다
2) __populate_section_memmap() 함수를 이용하여 각 section에 대해 2^18개 page 구조체 정보를 저장할 메모리 가상주소를 받아온다. 이 가상주소는 sparse_buffer_init() 함수로 할당받은 물리 메모리에 연결되어 있다. pfn은 함수의 인자로 사용된다
3) 함수 check_usemap_section_nr()를 이용해서 한 section에 해당하는 pgdat, usage 정보가 동일한 section에 있는지 체크한다
4) sparse_init_one_section() 함수로 각 section의 mem_section이 2^18개 page 구조체 메모리의 시작 주소를 가리키도록 한다. 정확히는 mem_section->section_mem_map - section의 시작 pfn 값을 저장한다. 또한 mem_section->usage에 할당받은 usage 메모리 영역도 매핑한다
5) usage 포인터를 mem_section_usage_size() 만큼 이동시키고 위 과정을 반복한다

- sparse_buffer_fini() 함수로 남은 sparse buffer는 memblock_remove()로 제거한다

- failed 레이블에서는 현재 section부터 남은 section까지 section_mem_map을 모두 0으로 처리한다


sparse_early_usemaps_alloc_pgdat_section()
- usage 영역을 위한 메모리를 요청한다
- pgdat과 가급적 동일한 section의 메모리를 할당받기 위해 min addr를 pgdat의 물리주소를 section  단위로 align하여 정한다
- max addr는 min addr에 section 크기만큼(1GB) 더해서 같은 section의 메모리를 할당받을 수 있도록 요청한다
- nid도 pgdat과 동일한 nid로 요청한다
- memblock_alloc_try_nid()으로 메모리를 요청하고 실패하면 max addr 조건은 포기하고 재시도한다
- 결과를 리턴한다


usemap_size()
- 한 section 관리를 위한 부가정보인 mem_section_usage 메모리 중 구조체 메모리 외의 부가 메모리 사이즈를 계산한다
- 2048bit = 256byte이다
- 페이지 블록이 512개이고 (한 section당 페이지 2^18개이고 페이지 블록 하나당 페이지 2^9개, section 당 페이지 블록 2^9개이다) 페이지 블록당 필요한 부가 정보가 4비트이므로 (NR_PAGEBLOCK_BITS = 4), 총 2048bit가 필요하다


sparse_buffer_init()
- memblock_alloc_exact_nid_raw() 함수로 2^18개 page 구조체 x map_count 만큼의 메모리를 요청한다
- align은 section_map_size() 함수로 2^18개 page 구조체 크기만큼 align 요청한다
- min addr는 PAGE_OFFSET으로 linear mapping 시작 물리주소를 min addr로 요청한다
- max addr는 무관
- nid는 인자로 받은 nid memblock 영역을 요청한다
- 할당받은 메모리를 전역 변수 sparsemap_buf에 연결한다
- sparsemap_buf_end = sparsemap_buf + size


__populate_section_memmap()
- vmemmap을 사용하는 경우와 사용하지 않는 경우로 나누어진다

 

vmemmap을 사용하는 __populate_section_memmap()
- vmemmap을 사용하는 경우 sparse_buffer_init()으로 할당받은 물리 메모리 영역에 대해 linear mapping 가상 주소 외에도 vmemmap을 위한 가상 주소를 하나 더 매핑하여 사용하게 된다
- 본 함수에서 이 vmemmap 사용을 위한 가상 주소를 만들며 이를 위해 MMU 테이블을 추가 작성한다
- 각 section의 page 구조체 메모리가 저장된 vmemmap 가상 주소는 각 section 물리주소 pfn + vmemmap이 된다 (vmemmap은 arm64_memblock_init()에서 설정)
- pfn_to_page() 함수로 현재 section의 page 구조체 메모리가 저장될 vmemmap 가상 시작 주소를 구한다
- vmemmap 가상 시작 주소에 page 구조체 크기 x 2^18 을 더해서 vmemmap 가상 끝 주소를 구한다
- vmemmap_populate() 함수를 이용하여 vmemmap 가상 시작 주소부터 끝 주소까지 MMU 테이블을 작성한다
- 만들어진 vmemmap 가상 시작 주소를 리턴한다


pfn_to_page()
- 물리주소 pfn으로부터 해당 page에 대한 page 구조체의 가상 주소를 구한다
1) vmemmap을 쓰는 경우
- vmemmap + pfn으로 계산된다
2) vmemmap을 안 쓰는 경우
- pfn에서 section_mem_map을 구한다
- section_mem_map에 pfn을 더해서 page 포인터를 구한다

 

page_to_pfn()
- 반대로 page 구조체의 포인터에서 해당 페이지의 물리 pfn을 구한다
1) vmemmap을 쓰는 경우
- (unsigned long)(page - vmemmap)으로 구한다
2) vmemmap을 안 쓰는 경우
- page 포인터로부터 section_mem_map을 구한다
- page 포인터에서 section_mem_map을 빼서 pfn을 구한다

 

vmemmap_populate()
- pmd_addr_end() 함수로 다음 pmd entry (pmdp)로 이동하면서 1개 section에 해당하는 영역에 대해 MMU 테이블을 생성한다
- pmd가 2^21 크기이고 1개 section이 2^30이므로 2^9 = 512개의 pmd를 1개 section에 대해 매핑하게 된다
- 512번 루프를 돌면서 아래 과정을 수행한다
1) vmemmap_pgd_populate(), vmemmap_p4d_populate(), vmemmap_pud_populate() 함수로 가상주소 addr에서 pudp를 구한다. 이 함수들은 entry index를 계산해서 각각 pgdp, p4dp, pudp를 구한다.
2) pmd_offset() 함수로 가상주소 addr에 해댕하는 pmdp (pmd entry 가상주소)를 구한다
3) 함수 vmemmap_alloc_block_buf()로 sparse_buffer_init()에서 할당한 메모리(p)를 PMD size만큼 가져온다
4) 함수 pmd_set_huge()으로 해당 물리 메모리 주소(__pa(p))를 pmdp 가상주소에 기록한다
5) 가상 주소 addr를 1개 PMD 사이즈만큼 키운다
- 현재는 1개 section 크기가 PMD 사이즈의 배수이므로 align 문제가 없다
- 그러나 align 문제가 있어서 첫 번째 node의 page와 두 번째 node의 page가 동일한 pmd 내에 위치하게 되면 두 번째 node 차례에서 vmemmap_verify() 함수를 호출한다
- vmemmap_verify() 함수는 두 노드의 nid가 같은지 확인하고 다르면 경고를 출력한다 

 

pmd_offset()
- 가상주소 addr를 이용해서 pudp로부터 pmdp를 구한다

 

vmemmap_alloc_block_buf()
- 현재는 altmap이 NULL이다
- sparse_buffer_alloc()으로 1개 PMD 크기만큼 sparse_buffer_init()에서 할당한 메모리를 가져온다
- 메모리가 없으면 vmemmap_alloc_block() 함수로 새 메모리를 할당 받는다

 

sparse_buffer_alloc()
- sparsemap_buf에서 size만큼 align된 메모리 주소(ptr)를 가져와서 반환한다
- 예를 들어 size가 2MB이고 sparsemap_buf가 2MB align이 안 맞으면 2MB align이 맞도록 살짝 키운다
- 이러면 그 사이의 메모리가 남는데 이는 함수 sparse_buffer_free()로 제거한다
- sparsemap_buf는 align된 메모리 주소(ptr) + size로 업데이트한다

 

sparse_buffer_free()
- memblock_free_early() = memblock_free()로 memblock 물리 메모리를 해제한다

 

vmemmap_alloc_block()
- 현재 slab_allocator가 가용하지 않으므로 __earlyonly_bootmem_alloc() 함수로 메모리를 요청한다
- __earlyonly_bootmem_alloc() 함수는 memblock_alloc_try_nid_raw() 함수로 memblock 메모리를 요청한다

 

pmd_set_huge()
- pmdp에 물리주소 __pa(p)를 기록한다
- MMU 테이블을 바꾸는 것이므로 fixmap이 사용된다
- set_pmd() 함수를 부르고 set_pmd()는 set_swapper_pgd() 함수를 부른다
- set_swapper_pgd()에서 다음 과정을 거친다
1) swapper_pgdir_lock spinlock을 건다
2) pmdp 물리주소에 대해 임시 fixmap 가상주소를 받는다
3) fixmap 가상주소에 물리주소 __pa(p)를 쓴다
4) 임시 fixmap 가상주소를 해제한다
5) swapper_pgdir_lock spinlock을 해제한다


vmemmap을 사용하지 않는 __populate_section_memmap()
- 이 경우에는 바로 sparse_buffer_alloc() 함수를 사용하여 sparse_buffer_init()에서 할당한 메모리의 linear mapping 가상 주소를 1개 section = 2^18 개 page 구조체 크기 씩 받아온다
- 즉 별도록 vmemmap 가상 주소를 쓰지 않고 그냥 linear mapping 가상 주소를 쓴다
- 여기서도 sparse_buffer_alloc() 함수에서 메모리 받아오는 것이 실패하면 memblock으로 추가 메모리를 요청한다


check_usemap_section_nr()
- usage 정보가 들어있는 section과 pgdat 정보가 들어있는 section이 같은지 체크한다
- 같으면 문제 없으므로 리턴한다
- 아니면 이미 예전에 경고한 케이스인지 체크하고 맞으면 그냥 리턴한다
- 아니면 usage nid와 pgdat nid가 다르면 경고를 출력하고 리턴한다
- usage nid와 pgdat nid가 같으면 usage section과 pgdat section이 다르다는 경고를 출력하고 리턴한다


sparse_init_one_section()
- 현재 section의 section_mem_map에 SECTION_MAP_MASK로 하위 4bit을 제외한 값을 모두 지운다
- 이 과정에서 NID 값이 지워진다
- section_mem_map에 현재 section의 page 구조체가 존재하는 가상주소를 pfn을 빼고 기록한다. 이로써 각 section과 page 구조체 정보가 연결된다
- 이 외에 SECTION_HAS_MEM_MAP, SECTION_IS_EARLY 플래그도 기록한다.
- 현재 기준으로 하위 4bit 비트맵이 모두 ON이다
- section_mem_map->usage에 할당 받았던 usage 메모리 주소도 기록한다


sparse_buffer_fini()
- sparsemap_buf에 남은 메모리를 모두 sparse_buffer_free() 함수로 해제한다
- sparsemap_buf를 NULL 만들고 종료한다

 

관련 패치
- https://lore.kernel.org/lkml/20191030131122.8256-1-vincent.whitchurch@axis.com/
- https://github.com/torvalds/linux/commit/09dbcf422e9b791d2d43cad8c283d9bdaef019a9
- https://github.com/torvalds/linux/commit/48c906823f3927b981db9f0b03c2e2499977ee93

XE Login