리눅스 커널 분석은 4.16 버전을 기준으로 하였습니다.
memblock은 리눅스 커널 부트업 단계에서 사용되는 메모리 할당자입니다. memblock은 메모리를 3가지 타입으로 나누어 관리하고 있는데요, 각각 memory, reserved, physmem 타입입니다.
먼저 memblock 자료구조에 대해 확인해보시죠. memblock은 3개의 type을 위한 memblock_type 구조체를 가지고 있고, 각 타입에는 region이란 영역을 관리하기 위해 memblock_region 구조체 포인터를 가지고 있습니다. memory, reserved 영역은 총 128개의 region을 가질 수 있으며 모두 사용하게 되면 2배로 크기가 늘어나 설정되어 집니다.
[include/linux/memblock.h]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | struct memblock_region { phys_addr_t base; phys_addr_t size; unsigned long flags; #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP int nid; #endif }; struct memblock_type { unsigned long cnt; /* number of regions */ unsigned long max; /* size of the allocated array */ phys_addr_t total_size; /* size of all regions */ struct memblock_region *regions; char *name; }; struct memblock { bool bottom_up; /* is bottom up direction? */ phys_addr_t current_limit; struct memblock_type memory; struct memblock_type reserved; #ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP struct memblock_type physmem; #endif }; | cs |
arm64_memblock_init() 함수는 memblock이 관리하는 타입 중 reserved 영역에 대한 초기화를 진행하는 함수입니다. reserved영역에 대한 초기화는 아래 같은 설정을 진행합니다.
커널 영역
initrd영역
페이지 테이블 영역
DTB 영역 및 DTB 요청 영역
CMA-DMA영역
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | void __init arm64_memblock_init(void) { const s64 linear_region_size = -(s64)PAGE_OFFSET; /* Handle linux,usable-memory-range property */ fdt_enforce_memory_region(); /* Remove memory above our supported physical address size */ memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX); /* * Ensure that the linear region takes up exactly half of the kernel * virtual address space. This way, we can distinguish a linear address * from a kernel/module/vmalloc address by testing a single bit. */ BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1)); /* * Select a suitable value for the base of physical memory. */ memstart_addr = round_down(memblock_start_of_DRAM(), ARM64_MEMSTART_ALIGN); /* * Remove the memory that we will not be able to cover with the * linear mapping. Take care not to clip the kernel which may be * high in memory. */ memblock_remove(max_t(u64, memstart_addr + linear_region_size, __pa_symbol(_end)), ULLONG_MAX); if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) { /* ensure that memstart_addr remains sufficiently aligned */ memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size, ARM64_MEMSTART_ALIGN); memblock_remove(0, memstart_addr); } /* * Apply the memory limit if it was set. Since the kernel may be loaded * high up in memory, add back the kernel region that must be accessible * via the linear mapping. */ if (memory_limit != (phys_addr_t)ULLONG_MAX) { memblock_mem_limit_remove_map(memory_limit); memblock_add(__pa_symbol(_text), (u64)(_end - _text)); } if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) { /* * Add back the memory we just removed if it results in the * initrd to become inaccessible via the linear mapping. * Otherwise, this is a no-op */ u64 base = initrd_start & PAGE_MASK; u64 size = PAGE_ALIGN(initrd_end) - base; /* * We can only add back the initrd memory if we don't end up * with more memory than we can address via the linear mapping. * It is up to the bootloader to position the kernel and the * initrd reasonably close to each other (i.e., within 32 GB of * each other) so that all granule/#levels combinations can * always access both. */ if (WARN(base < memblock_start_of_DRAM() || base + size > memblock_start_of_DRAM() + linear_region_size, "initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) { initrd_start = 0; } else { memblock_remove(base, size); /* clear MEMBLOCK_ flags */ memblock_add(base, size); memblock_reserve(base, size); } } if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) { extern u16 memstart_offset_seed; u64 range = linear_region_size - (memblock_end_of_DRAM() - memblock_start_of_DRAM()); /* * If the size of the linear region exceeds, by a sufficient * margin, the size of the region that the available physical * memory spans, randomize the linear region as well. */ if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) { range = range / ARM64_MEMSTART_ALIGN + 1; memstart_addr -= ARM64_MEMSTART_ALIGN * ((range * memstart_offset_seed) >> 16); } } /* * Register the kernel text, kernel data, initrd, and initial * pagetables with memblock. */ memblock_reserve(__pa_symbol(_text), _end - _text); #ifdef CONFIG_BLK_DEV_INITRD if (initrd_start) { memblock_reserve(initrd_start, initrd_end - initrd_start); /* the generic initrd code expects virtual addresses */ initrd_start = __phys_to_virt(initrd_start); initrd_end = __phys_to_virt(initrd_end); } #endif early_init_fdt_scan_reserved_mem(); /* 4GB maximum for 32-bit only capable devices */ if (IS_ENABLED(CONFIG_ZONE_DMA32)) arm64_dma_phys_limit = max_zone_dma_phys(); else arm64_dma_phys_limit = PHYS_MASK + 1; reserve_crashkernel(); reserve_elfcorehdr(); high_memory = __va(memblock_end_of_DRAM() - 1) + 1; dma_contiguous_reserve(arm64_dma_phys_limit); memblock_allow_resize(); } | cs |
line3 : 64bit 자료형으로 선언된 linear_region_size 변수에 대해 64비트 커널에서 사용할 가상 주소 공간 크기의 절반에
해당하는 사이즈를 저장합니다.
line6 : dt property인 usable-memory를 기반으로 메모리 영역을 제한합니다. kernel dump를 위한 메모리 영역을 제한해 주기 위한 영역을 설정하기 위한 함수입니다.
[arch/arm64/mm/init.c]
early_init_dt_scan_usablemem() 함수를 iterator 하며 조건에 맞는 region 값을 얻어오게됩니다.
of_scan_flat_dt(early_init_dt_scan_usablemem, ®);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | static int __init early_init_dt_scan_usablemem(unsigned long node, const char *uname, int depth, void *data) { struct memblock_region *usablemem = data; const __be32 *reg; int len; if (depth != 1 || strcmp(uname, "chosen") != 0) return 0; reg = of_get_flat_dt_prop(node, "linux,usable-memory-range", &len); if (!reg || (len < (dt_root_addr_cells + dt_root_size_cells))) return 1; usablemem->base = dt_mem_next_cell(dt_root_addr_cells, ®); usablemem->size = dt_mem_next_cell(dt_root_size_cells, ®); return 1; } |
line 9: 지원하는 물리적 메모리보다 큰 영역은 memblock memory type 영역에서 제거하게 됩니다.
line 21 : 물리메모리(dram)의 시작 주소를 ARM64에 맞춰 정렬(?) 해주게 되고 해당 시작주소를 얻어옵니다.
line 29 : start부터 linear_region 크기를 넘어서는 영역에 대해 memblock에서 제거합니다. linear_region 영역을 넘어서는 메모리는 관리할 수 없기에 제거하게 됩니다.
line 31 ~ 34 : DRAM 크기가 linear_region 영역보다 크다면 linear_mapping에 대해 사용할 수 있는 영역 외에 나머지 영역을 제거합니다.
line 48~75 : initrd를 memblock reserved영역에 추가해주기 위한 logic입니다.
line 48 : CONFIG_BLK_DEV_INITRD가 설정되어 있고 initrd_start(부트엄 과정 중 dt에서 값을 읽어 저장함)에 값이 있다면 진행하게 됩니다.
line 71 ~ 73 : 기존에 존재하는 memblock의 memory 영역에 initrd_start와 initrd_end로 계산한 base와 size만큼 제거한 이후 다시 추가해줍니다. memblock_add는 memory 타입에 추가되는 것이므로 memblock_reserve()함수를 이용해 reserved 타입에 추가해줍니다. 이렇게 하여 initrd를 위한 reserved 타입의 region을 확보해줍니다.
line 76 ~ 91 : 보안 목적으로 커널 시작 주소를 램던하게 변경하고 변경되는 경우 memstart_addr을 구합니다.
line 97 : vmlinux.lds.S에 명시되어 있는 .text부터 .end 영역까지 커널영역을 reserved 타입 영역에 등록한다.
line 99 ~ 105 : initrd 여역을 reserved 타입 영역에 등록하고 initrd의 start와 end 물리주소를 가상메모리 주소로 변경하여 저장합니다.
line 108 : dtb에 관련된 영역을 reserved 에 추가합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void __init early_init_fdt_scan_reserved_mem(void) { int n; u64 base, size; if (!initial_boot_params) return; /* Process header /memreserve/ fields */ for (n = 0; ; n++) { fdt_get_mem_rsv(initial_boot_params, n, &base, &size); if (!size) break; early_init_dt_reserve_memory_arch(base, size, 0); } of_scan_flat_dt(__fdt_scan_reserved_mem, NULL); fdt_init_reserved_mem(); } | cs |
위 함수는 2가지를 담당하게 되는데, DTB Header의 off_mem_rsvmap 필드가 가리키는 memory reserve 블록에서 읽은 address와 size를 가진 enty를 등록하는 것과 DTB reserved 영역을 필요로 하는 node들을 위해 reserved 타입 영역에 등록해줍니다.
line 10~15 : fdt_get_mem_rsv로 initial_boot_params(dtb의 시작주소) 부터 시작해서 base와 size를 구해옵니다. 그 이후 early_init_dt_reserve_memory_arch() 함수를 이용해서 해당 base부터 size 크기만큼 reserved 타입 영역에 추가해줍니다.
line 17 ~ 18 : 두 번째로 for문 밖의 함수들에서 node들이 원하는 reserve 영역에 대해 추가해줍니다.
line 110~114 : DMA physical limit을 설정합니다.
line 116 : Kernel crash를 위한 메모리 공간을 reserved 타입에 추가합니다.
line 120 : DRAM의 end 물리 주소를 가상주소로 변환합니다.
line 122 : DMA영역을 reserved 타입 영역에 추가하고 CMA(Contiguous Memory Allocator) 영역에도 추가합니다.
arm64_memblock_init()을 분석은 여기서 끝내겠습니다. 추가적인 상세한 내용은 추후에 공부하다 보완하겠습니다. 감사합니다.
참고자료 : 코드로 알아보는 ARM 리눅스 커널 | 저 윤석훈, 문영일, 구본규, 유희재
'Linux > Kernel Analysis' 카테고리의 다른 글
bootmem_init() 부트 메모리 초기화 (0) | 2018.10.06 |
---|---|
void __init paging_init() - 커널 페이지 초기화 하기 (2) | 2018.09.29 |
[ARMv8] aarch64 프로세서 상태 레지스터(PSTATE) (0) | 2018.09.08 |
[커널분석] parse_early_param() (0) | 2018.09.08 |
[커널분석] static void __init setup_machine_fdt(phys_addr_t dt_phys) (0) | 2018.08.25 |