01 · QUESTION
무엇을 확인할 것인가
start_kernel() 이전과 setup_arch() 안에서 어떤 정보가 physical pointer로 들어오고, 언제 memblock과 일반 virtual address로 안전하게 사용할 수 있는가?
architecture entry가 최소 MMU/stack을 준비하고 boot parameter를 보존한 뒤 setup_arch()가 memory map, command line, CPU, interrupt/timer와 firmware tables를 검증한다. 결과는 memblock reservations와 generic kernel subsystem 초기화의 입력이 된다.
firmware-owned memory, bootloader temporary data, kernel image/initrd/DTB, reserved-memory와 일반 RAM을 구분한다. early mapping이 없는 physical pointer를 일반 C pointer처럼 dereference하면 안 된다.
02 · CONTRACT
공통 규약과 아키텍처별 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | Image header entry, x0 DTB와 EFI stub/ACPI 선택 | DTB 8-byte alignment/크기, RAM 밖 위치, initrd overlap 또는 reserved-memory 누락은 early abort와 allocator overwrite를 만든다. | x0 DTB PA, Image load PA/size, memblock memory/reserved, EFI descriptor, ACPI RSDP와 initrd start/end를 확인한다. |
| x86-64 | boot_params zero page, BIOS/e820 또는 EFI와 ACPI | truncated boot_params, 겹친 e820 entry, initrd가 low/high limit 밖에 있거나 setup_data chain loop가 있으면 RAM 오인과 overwrite가 발생한다. | boot_params PA, e820 raw/sanitized map, setup_data, initrd, EFI descriptors, ACPI RSDP, KASLR physical/virtual offset을 본다. |
| RISC-V | a0 hartid, a1 DTB와 SBI firmware contract | boot hart mismatch, DTB의 hart mapping/ISA string 오류, unsupported SATP mode 또는 SBI version 차이는 secondary hart, timer와 MMU 초기화 실패를 만든다. | a0/a1, boot_cpu_hartid, DTB totalsize, memblock ranges, SATP.MODE/root PPN, SBI spec/extensions와 initrd를 확인한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- Image header entry, x0 DTB와 EFI stub/ACPI 선택
- state
- boot protocol은 x0에 DTB physical address를 넘기고 MMU off, cache 조건을 규정한다. EFI stub을 거치면 EFI memory map과 system table도 보존한다.
setup_arch()가 early FDT를 scan하고 memblock, ACPI 여부와 CPU topology를 확정한다. - checkpoint
- x0 DTB PA, Image load PA/size, memblock memory/reserved, EFI descriptor, ACPI RSDP와 initrd start/end를 확인한다.
x86-64
- mechanism
- boot_params zero page, BIOS/e820 또는 EFI와 ACPI
- state
- boot protocol의
boot_params에 e820 memory map, command line, initrd와 framebuffer 정보가 들어온다. EFI boot path는 EFI memory map을 병합하고setup_arch()가 e820을 sanitize해 memblock에 반영하며 ACPI/SMBIOS를 찾는다. - checkpoint
- boot_params PA, e820 raw/sanitized map, setup_data, initrd, EFI descriptors, ACPI RSDP, KASLR physical/virtual offset을 본다.
RISC-V
- mechanism
- a0 hartid, a1 DTB와 SBI firmware contract
- state
- entry ABI는 a0에 boot hart id, a1에 DTB physical address를 넘긴다. EFI stub 경로도 가능하지만 일반 platform은 DTB와 SBI를 사용한다.
setup_arch()가 FDT memory/CPU/ISA 정보를 읽고 memblock, SATP mode와 paging을 확정한다. - checkpoint
- a0/a1, boot_cpu_hartid, DTB totalsize, memblock ranges, SATP.MODE/root PPN, SBI spec/extensions와 initrd를 확인한다.
setup_arch()가 memory map, command line, CPU, interrupt/timer와 firmware tables를 검증한다. 결과는 memblock reservations와 generic kernel subsystem 초기화의 입력이 된다.setup_machine_fdt()가 DTB fixmap 매핑에 성공하면 먼저 예약하고 초기 scan으로 검증한다(실패 시 정지). 뒤의 arm64_memblock_init()은 linear mapping·mem= 한계를 먼저 적용하고 접근 가능한 initrd를 조건부 재편입·예약한다. 그 다음 paging_init()이 page table을 준비한다. EFI runtime mapping은 별도 경로다.parse_boot_params()로 입력을 받고 early_reserve_memory()에서 먼저 예약한 뒤 e820__memory_setup()으로 memory map을 정한다. mapping과 KASLR은 해당 후속 경로의 조건을 따로 확인한다.parse_dtb()로 초기 DT 자료를 읽은 뒤 sbi_init()·efi_init()을 거쳐 paging_init()으로 간다. DT/ACPI의 후속 펼침은 paging 뒤에 갈라진다. SBI extension 결과는 timer·IPI·RFENCE·reset backend 선택에 쓰인다.setup_machine_fdt(): DTB fixmap 매핑 성공 시 예약→초기 scan 검증→arm64_memblock_init(): linear mapping·mem= 한계 처리 뒤 접근 가능한 initrd를 조건부 재편입·예약→paging_init(): page table 준비; 이후 ACPI 또는 DT 경로 진행parse_boot_params(): bootloader 입력 확인→early_reserve_memory(): 선행 예약→e820__memory_setup(): BIOS/EFI·Xen 경로에 따른 memory map 설정; 뒤에서 mapping 초기화parse_dtb(): 초기 DT 자료 scan→sbi_init()·efi_init(): firmware 경로 준비→paging_init(): page table 준비; 이후 ACPI 또는 DT 경로 진행04 · SOURCE
Linux 6.18.37 원본 코드와 주요 구문 해설
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 원문 전체 발췌를 보존하고, 개별 해설은 근거가 있는 구문에만 붙였습니다.
arm64 · Linux 6.18.37
Image header entry, x0 DTB와 EFI stub/ACPI 선택
boot protocol은 x0에 DTB physical address를 넘기고 MMU off, cache 조건을 규정한다. EFI stub을 거치면 EFI memory map과 system table도 보존한다. setup_arch()가 early FDT를 scan하고 memblock, ACPI 여부와 CPU topology를 확정한다.
원본 코드: arch/arm64/kernel/setup.c:273-363
273
274u64 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = INVALID_HWID };
275
276u64 cpu_logical_map(unsigned int cpu)
277{
278 return __cpu_logical_map[cpu];
279}
280
281void __init __no_sanitize_address setup_arch(char **cmdline_p)
282{
283 setup_initial_init_mm(_text, _etext, _edata, _end);
284
285 *cmdline_p = boot_command_line;
286
287 kaslr_init();
288
289 early_fixmap_init();
290 early_ioremap_init();
291
292 setup_machine_fdt(__fdt_pointer);
293
294 /*
295 * Initialise the static keys early as they may be enabled by the
296 * cpufeature code and early parameters.
297 */
298 jump_label_init();
299 parse_early_param();
300
301 dynamic_scs_init();
302
303 /*
304 * The primary CPU enters the kernel with all DAIF exceptions masked.
305 *
306 * We must unmask Debug and SError before preemption or scheduling is
307 * possible to ensure that these are consistently unmasked across
308 * threads, and we want to unmask SError as soon as possible after
309 * initializing earlycon so that we can report any SErrors immediately.
310 *
311 * IRQ and FIQ will be unmasked after the root irqchip has been
312 * detected and initialized.
313 */
314 local_daif_restore(DAIF_PROCCTX_NOIRQ);
315
316 /*
317 * TTBR0 is only used for the identity mapping at this stage. Make it
318 * point to zero page to avoid speculatively fetching new entries.
319 */
320 cpu_uninstall_idmap();
321
322 xen_early_init();
323 efi_init();
324
325 if (!efi_enabled(EFI_BOOT)) {
326 if ((u64)_text % MIN_KIMG_ALIGN)
327 pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!");
328 WARN_TAINT(mmu_enabled_at_boot, TAINT_FIRMWARE_WORKAROUND,
329 FW_BUG "Booted with MMU enabled!");
330 }
331
332 arm64_memblock_init();
333
334 paging_init();
335
336 acpi_table_upgrade();
337
338 /* Parse the ACPI tables for possible boot-time configuration */
339 acpi_boot_table_init();
340
341 if (acpi_disabled)
342 unflatten_device_tree();
343
344 bootmem_init();
345
346 kasan_init();
347
348 request_standard_resources();
349
350 early_ioremap_reset();
351
352 if (acpi_disabled)
353 psci_dt_init();
354 else
355 psci_acpi_init();
356
357 arm64_rsi_init();
358
359 init_bootcpu_ops();
360 smp_init_cpus();
361 smp_build_mpidr_hash();
362
363#ifdef CONFIG_ARM64_SW_TTBR0_PAN주요 구문 해설
원본 91줄은 그대로 표시하며, 검토한 40개 구문에 설명을 붙였습니다.
u64 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = INVALID_HWID };논리 CPU 번호별 하드웨어 MPIDR을 저장할 배열 전체를 INVALID_HWID로 초기화합니다. 아직 열거하지 않은 CPU가 유효한 하드웨어 번호 0으로 오인되지 않게 합니다.
u64 cpu_logical_map(unsigned int cpu)논리 CPU 번호를 인자로 받아 대응하는 하드웨어 식별값을 돌려주는 함수입니다. 변환 정보는 부팅 중 __cpu_logical_map에 채워집니다.
return __cpu_logical_map[cpu];요청한 논리 CPU 슬롯의 MPIDR 값을 반환합니다. 번호를 새로 계산하지 않고 펌웨어 열거 과정에서 등록한 대응표를 조회합니다.
void __init __no_sanitize_address setup_arch(char **cmdline_p)ARM64의 플랫폼·메모리·펌웨어 초기화 진입점입니다. cmdline_p로 사용할 명령줄을 넘겨주며, __init은 초기화 후 해제될 코드이고 __no_sanitize_address는 이 초기 경로의 주소 검사 삽입을 막습니다.
setup_initial_init_mm(_text, _etext, _edata, _end);초기 커널 mm에 코드 시작·끝, 데이터 끝, 전체 이미지 끝 주소를 기록합니다. 이후 메모리 관리와 커널 이미지 범위 추적의 기본값이 됩니다.
kaslr_init();nokaslr 옵션과 이미 적용된 커널 주소 이동량을 검사하여 KASLR 활성 상태를 기록합니다. 충분한 무작위화 seed가 없으면 비활성 상태로 남으며, 이 함수 안에서 커널을 새 주소로 옮기지는 않습니다.
early_fixmap_init();초기 fixmap 페이지 테이블을 준비합니다. 정상적인 동적 매핑 체계가 준비되기 전에도 FDT나 장치의 특정 물리 주소를 고정 가상 주소로 볼 수 있게 합니다.
early_ioremap_init();초기 ioremap의 임시 매핑 슬롯을 준비합니다. 부팅 중 펌웨어 표와 장치 레지스터를 읽는 코드가 이 제한된 매핑 창을 사용할 수 있게 합니다.
setup_machine_fdt(__fdt_pointer);entry에서 보존한 DTB physical pointer를 검증하고 machine model, chosen, memory 정보를 early parse한다.
jump_label_init();static key에 연결된 분기 패치 정보를 초기화합니다. 바로 뒤의 CPU 기능 처리와 초기 부팅 인자가 static key를 바꾸기 전에 준비되어 있어야 합니다.
parse_early_param();커널 명령줄 중 early_param으로 등록된 옵션을 처리합니다. 일반 드라이버 초기화 전에 결정해야 하는 초기 설정을 반영합니다.
dynamic_scs_init();앞선 초기 부팅 단계가 동적 Shadow Call Stack을 선택했다면 dynamic_scs_enabled static key를 켭니다. 복귀 주소를 별도 스택에 보관하는 기능의 사용 상태를 커널에 반영하며, 이 함수가 PAC 명령을 직접 변환하는 것은 아닙니다.
local_daif_restore(DAIF_PROCCTX_NOIRQ);DAIF를 일반 프로세스 문맥의 IRQ/FIQ 차단 상태로 바꿉니다. 부팅 때 모두 막아 둔 예외 중 필요한 오류·디버그 처리를 허용하되, IRQ 컨트롤러 초기화 전에는 IRQ/FIQ를 계속 막습니다.
cpu_uninstall_idmap();초기 identity mapping을 TTBR0에서 제거하고 예약된 테이블로 바꿉니다. 더 이상 필요 없는 부팅용 물리 주소 매핑을 추측 실행이 참조하지 않게 합니다.
xen_early_init();Xen 게스트로 실행 중인 경우 초기 Xen 환경을 준비합니다. 네이티브 부팅과 같은 단계에서 시작하지만 실제 처리는 Xen 사용 여부와 구성에 따릅니다.
efi_init();EFI로 전달된 시스템 표·메모리 정보 등 ARM64 EFI 상태를 초기화합니다. EFI 부팅 정보를 커널이 관리하는 데이터와 연결하는 단계입니다.
if (!efi_enabled(EFI_BOOT)) {EFI stub을 거치지 않고 부팅했을 때 부트로더가 지켜야 하는 이미지 정렬과 MMU 초기 상태를 검사합니다. EFI 경로는 이 검사를 건너뜁니다.
if ((u64)_text % MIN_KIMG_ALIGN)커널 시작 _text가 MIN_KIMG_ALIGN 배수에 놓였는지 나머지로 검사합니다. 어긋났으면 아래에서 부트로더 정렬 오류를 출력합니다.
pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!");EFI 외의 부팅 경로에서 커널 이미지가 요구 정렬을 어겼음을 경고합니다. 로그를 남기는 줄이며 이 호출 자체가 정렬을 고치거나 실행을 중단하지는 않습니다.
WARN_TAINT(mmu_enabled_at_boot, TAINT_FIRMWARE_WORKAROUND,커널에 들어올 때 MMU가 켜져 있었다면 경고하고 펌웨어 문제를 나타내는 taint를 기록합니다. Linux가 기대한 MMU-off 진입 조건을 어긴 부팅 환경을 진단합니다.
FW_BUG "Booted with MMU enabled!");MMU가 켜진 채 진입했다는 펌웨어 오류 메시지를 WARN_TAINT에 전달하는 마지막 인자입니다. 이 문자열이 MMU를 끄는 명령은 아닙니다.
arm64_memblock_init();RAM range에서 kernel, DTB, initrd와 reserved-memory를 제외해 early physical allocator의 소유권을 확정한다.
paging_init();최종 kernel linear mapping과 page-table hierarchy를 구성해 이후 일반 virtual memory 초기화 기반을 만든다.
acpi_table_upgrade();초기 ramdisk에서 제공한 ACPI 표 교체 자료를 찾아 반영합니다. 다음 ACPI 초기 파싱이 교체된 표를 사용할 수 있도록 먼저 실행합니다.
acpi_boot_table_init();부팅에 필요한 ACPI 표를 파싱하여 플랫폼 정보를 준비합니다. ARM64에서는 ACPI 사용 여부에 따라 뒤의 장치 트리·PSCI 초기화 경로도 달라집니다.
if (acpi_disabled)ACPI를 사용하지 않을 때만 DTB를 device_node 트리로 풉니다. 플랫폼 기술을 ACPI에서 읽는 경로에서는 이 장치 트리 초기화를 생략합니다.
unflatten_device_tree();ACPI를 사용하지 않으면 평탄한 DTB를 커널이 탐색할 device_node 트리로 만듭니다. 이후 장치 검색과 드라이버가 노드·속성 객체를 따라갈 수 있게 합니다.
bootmem_init();초기 물리 메모리 배치에서 메모리 노드·zone과 페이지 관리 정보를 준비합니다. memblock으로 모은 RAM 정보를 일반 페이지 할당기 초기화에 연결합니다.
kasan_init();KASAN 메모리 오류 검사에 필요한 ARM64 상태를 초기화합니다. Generic·소프트웨어 태그 방식은 실제 메모리 배치에 맞는 shadow 매핑을 준비하며 하드웨어 태그 방식과 같은 구현으로 보아서는 안 됩니다.
request_standard_resources();RAM과 커널 코드·데이터 같은 주요 물리 메모리 범위를 자원 트리에 등록합니다. 자원 충돌 확인과 /proc/iomem 등의 표시가 이 정보를 사용합니다.
early_ioremap_reset();after_paging_init 표시를 켜 이후 early_ioremap이 페이징 초기화 이후용 fixmap 연산을 선택하게 합니다. 이미 만든 매핑들을 이 호출에서 모두 지우거나 다시 배치하는 것은 아닙니다.
if (acpi_disabled)플랫폼 정보가 DT 경로인지 확인하여 PSCI 설정의 입력 형식을 고릅니다. 참이면 장치 트리의 method·호출 정보를 사용합니다.
psci_dt_init();장치 트리에서 PSCI의 호출 통로와 지원 정보를 읽어 CPU 전원 관리 연산을 연결합니다. 이 호출 시점에 CPU 전원을 실제로 끄는 것은 아닙니다.
elseACPI가 활성인 경우의 PSCI 초기화 분기입니다. 장치 트리 대신 ACPI가 제공한 전원 관리 인터페이스 정보를 사용합니다.
psci_acpi_init();ACPI 정보로 PSCI 인터페이스를 초기화합니다. DT를 쓰는 위 경로와 같은 전원 관리 기능을 다른 펌웨어 기술 방식에서 구성합니다.
arm64_rsi_init();SMC 통로와 RSI 버전을 검사하고 Realm 설정을 읽어 보호·공유 메모리의 속성과 매핑 연산을 준비합니다. 성공하면 RSI 사용 표시를 켜며, 지원하지 않는 환경에서는 일찍 반환합니다.
init_bootcpu_ops();부트 CPU에 적용할 CPU 동작 테이블을 선택합니다. 펌웨어가 지원하는 기동·종료 방식에 맞춰 후속 CPU 관리가 사용할 연산을 준비합니다.
smp_init_cpus();펌웨어 정보를 바탕으로 시스템의 CPU들을 열거하고 논리 CPU와 하드웨어 식별값의 대응을 준비합니다. 아직 이 한 줄이 모든 보조 CPU를 실행시키는 것은 아닙니다.
smp_build_mpidr_hash();MPIDR의 CPU별 차이 비트와 이동량을 계산합니다. suspend/resume에서 하드웨어 식별값으로 저장 상태 슬롯을 빠르게 찾을 수 있게 하며 해시 인덱스 자체가 논리 CPU 번호는 아닙니다.
#ifdef CONFIG_ARM64_SW_TTBR0_PAN소프트웨어 PAN은 커널의 불필요한 사용자 메모리 접근을 TTBR0 전환으로 차단하는 기능입니다. 켜진 빌드에서는 init task의 사용자 표를 예약된 빈 표로 초기화하여 uaccess가 잘못 열려도 주소 변환 오류가 나게 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
x86-64 · Linux 6.18.37
boot_params zero page, BIOS/e820 또는 EFI와 ACPI
boot protocol의 boot_params에 e820 memory map, command line, initrd와 framebuffer 정보가 들어온다. EFI boot path는 EFI memory map을 병합하고 setup_arch()가 e820을 sanitize해 memblock에 반영하며 ACPI/SMBIOS를 찾는다.
원본 코드: arch/x86/kernel/setup.c:878-982
878 * systems (with a traditional BIOS) as well as on EFI systems.
879 */
880/*
881 * setup_arch - architecture-specific boot-time initializations
882 *
883 * Note: On x86_64, fixmaps are ready for use even before this is called.
884 */
885
886void __init setup_arch(char **cmdline_p)
887{
888#ifdef CONFIG_X86_32
889 memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));
890
891 /*
892 * copy kernel address range established so far and switch
893 * to the proper swapper page table
894 */
895 clone_pgd_range(swapper_pg_dir + KERNEL_PGD_BOUNDARY,
896 initial_page_table + KERNEL_PGD_BOUNDARY,
897 KERNEL_PGD_PTRS);
898
899 load_cr3(swapper_pg_dir);
900 /*
901 * Note: Quark X1000 CPUs advertise PGE incorrectly and require
902 * a cr3 based tlb flush, so the following __flush_tlb_all()
903 * will not flush anything because the CPU quirk which clears
904 * X86_FEATURE_PGE has not been invoked yet. Though due to the
905 * load_cr3() above the TLB has been flushed already. The
906 * quirk is invoked before subsequent calls to __flush_tlb_all()
907 * so proper operation is guaranteed.
908 */
909 __flush_tlb_all();
910#else
911 printk(KERN_INFO "Command line: %s\n", boot_command_line);
912 boot_cpu_data.x86_phys_bits = MAX_PHYSMEM_BITS;
913#endif
914
915#ifdef CONFIG_CMDLINE_BOOL
916#ifdef CONFIG_CMDLINE_OVERRIDE
917 strscpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
918#else
919 if (builtin_cmdline[0]) {
920 /* append boot loader cmdline to builtin */
921 strlcat(builtin_cmdline, " ", COMMAND_LINE_SIZE);
922 strlcat(builtin_cmdline, boot_command_line, COMMAND_LINE_SIZE);
923 strscpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);
924 }
925#endif
926 builtin_cmdline_added = true;
927#endif
928
929 strscpy(command_line, boot_command_line, COMMAND_LINE_SIZE);
930 *cmdline_p = command_line;
931
932 /*
933 * If we have OLPC OFW, we might end up relocating the fixmap due to
934 * reserve_top(), so do this before touching the ioremap area.
935 */
936 olpc_ofw_detect();
937
938 idt_setup_early_traps();
939 early_cpu_init();
940 jump_label_init();
941 static_call_init();
942 early_ioremap_init();
943
944 setup_olpc_ofw_pgd();
945
946 parse_boot_params();
947
948 x86_init.oem.arch_setup();
949
950 /*
951 * Do some memory reservations *before* memory is added to memblock, so
952 * memblock allocations won't overwrite it.
953 *
954 * After this point, everything still needed from the boot loader or
955 * firmware or kernel text should be early reserved or marked not RAM in
956 * e820. All other memory is free game.
957 *
958 * This call needs to happen before e820__memory_setup() which calls the
959 * xen_memory_setup() on Xen dom0 which relies on the fact that those
960 * early reservations have happened already.
961 */
962 early_reserve_memory();
963
964 iomem_resource.end = (1ULL << boot_cpu_data.x86_phys_bits) - 1;
965 e820__memory_setup();
966 parse_setup_data();
967
968 copy_edd();
969
970 setup_initial_init_mm(_text, _etext, _edata, (void *)_brk_end);
971
972 /*
973 * x86_configure_nx() is called before parse_early_param() to detect
974 * whether hardware doesn't support NX (so that the early EHCI debug
975 * console setup can safely call set_fixmap()).
976 */
977 x86_configure_nx();
978
979 parse_early_param();
980
981 if (efi_enabled(EFI_BOOT))
982 efi_memblock_x86_reserve_range();주요 구문 해설
원본 105줄은 그대로 표시하며, 검토한 43개 구문에 설명을 붙였습니다.
void __init setup_arch(char **cmdline_p)x86의 부팅 명령줄·CPU·펌웨어·물리 메모리 설정을 수행하는 아키텍처 초기화 함수입니다. cmdline_p로 최종 명령줄 포인터를 공통 부팅 코드에 돌려줍니다.
#ifdef CONFIG_X86_3232비트 x86 커널은 임시 부팅 CPU 정보를 옮기고 지금까지의 커널 매핑을 정식 swapper 페이지 표에 복사한 뒤 TLB를 정리합니다. 64비트 빌드는 다른 초기 진입 경로에서 이 준비를 하므로 아래 #else를 사용합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));32비트 부팅 경로가 준비한 CPU 정보를 boot_cpu_data에 복사합니다. 이후 공통 초기화가 부트 CPU의 같은 정보 구조체를 사용하도록 합니다.
clone_pgd_range(swapper_pg_dir + KERNEL_PGD_BOUNDARY,부팅용 페이지 테이블의 커널 부분을 swapper_pg_dir의 커널 영역으로 복사합니다. 첫 인자는 복사 대상의 KERNEL_PGD_BOUNDARY 이후 위치입니다.
initial_page_table + KERNEL_PGD_BOUNDARY,initial_page_table에서 복사할 커널 PGD 영역의 시작을 지정합니다. 사용자 영역 엔트리가 아닌 동일한 커널 경계 이후의 엔트리를 가져옵니다.
KERNEL_PGD_PTRS);KERNEL_PGD_PTRS개 엔트리를 복사하도록 길이를 지정합니다. 다음 CR3 적재가 사용할 커널 페이지 테이블 부분을 완성하는 마지막 인자입니다.
load_cr3(swapper_pg_dir);초기 매핑을 옮겨 둔 swapper_pg_dir를 CR3에 적재합니다. 부트스트랩용 테이블에서 커널이 계속 사용할 페이지 테이블로 전환하는 단계입니다.
__flush_tlb_all();페이지 테이블 전환 뒤 로컬 TLB를 정리합니다. 원문은 Quark X1000의 PGE 보고 문제 때문에 이 호출 자체가 무효화하지 못할 수 있어 앞선 CR3 변경이 필요한 경우도 설명합니다.
#else64비트 x86 경로에서는 32비트용 페이지 표 복사 대신 명령행을 출력하고 초기 물리 주소 폭 상한을 설정합니다. 이후의 공통 setup_arch 초기화는 계속 이어집니다.
printk(KERN_INFO "Command line: %s\n", boot_command_line);부트로더가 넘긴 커널 명령줄을 정보 수준 로그에 출력합니다. 옵션을 실제로 해석하는 작업은 뒤의 파싱 단계가 수행합니다.
boot_cpu_data.x86_phys_bits = MAX_PHYSMEM_BITS;초기 CPU 정보의 물리 주소 폭을 이 커널 구성이 다룰 수 있는 최대 폭으로 설정합니다. 뒤의 실제 CPU 탐지 전 사용할 초기 한계값입니다.
#endif888행에서 시작한 선택 구간을 마칩니다. 32비트 x86 커널은 임시 부팅 CPU 정보를 옮기고 지금까지의 커널 매핑을 정식 swapper 페이지 표에 복사한 뒤 TLB를 정리합니다. 64비트 빌드는 다른 초기 진입 경로에서 이 준비를 하므로 아래 #else를 사용합니다.
#ifdef CONFIG_CMDLINE_BOOL커널에 명령행을 내장하는 설정입니다. 켜지면 내장 문자열을 부트로더 명령행과 합칠지 완전히 대체할지 아래 설정으로 결정하고, 꺼지면 부트로더가 준 명령행을 유지합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
#ifdef CONFIG_CMDLINE_OVERRIDE내장 명령행으로 부트로더 입력을 덮어쓰는 설정입니다. 켜지면 builtin_cmdline만 복사하고, 꺼지면 내장 문자열 뒤에 부트로더 문자열을 덧붙여 사용합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
strscpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);CMDLINE_OVERRIDE 구성에서는 내장 명령줄을 boot_command_line에 길이 제한을 두어 복사합니다. 부트로더의 명령줄 대신 빌드 때 정한 문자열을 사용합니다.
#else내장 명령행으로 완전히 덮어쓰지 않는 구성입니다. 내장 문자열이 비어 있지 않으면 공백과 부트로더 명령행을 뒤에 붙여 최종 문자열로 사용합니다.
if (builtin_cmdline[0]) {내장 명령줄이 비어 있지 않을 때만 부트로더 명령줄 앞에 덧붙입니다. 빈 문자열이면 부트로더가 준 명령줄을 그대로 유지합니다.
strlcat(builtin_cmdline, " ", COMMAND_LINE_SIZE);내장 명령줄 끝에 공백을 덧붙입니다. 이어 붙일 부트로더 옵션이 마지막 내장 옵션과 한 단어로 합쳐지지 않게 합니다.
strlcat(builtin_cmdline, boot_command_line, COMMAND_LINE_SIZE);내장 명령줄 뒤에 부트로더 명령줄을 추가합니다. 전체 버퍼 크기를 넘지 않도록 COMMAND_LINE_SIZE로 제한합니다.
strscpy(boot_command_line, builtin_cmdline, COMMAND_LINE_SIZE);결합을 마친 명령줄을 공통 boot_command_line 버퍼로 복사합니다. 이후 초기 옵션 처리가 결합된 내용을 보게 합니다.
#endif916행에서 시작한 선택 구간을 마칩니다. 내장 명령행으로 부트로더 입력을 덮어쓰는 설정입니다. 켜지면 builtin_cmdline만 복사하고, 꺼지면 내장 문자열 뒤에 부트로더 문자열을 덧붙여 사용합니다.
builtin_cmdline_added = true;내장 명령줄 처리를 이미 마쳤음을 표시합니다. 뒤의 초기 명령줄 구성에서 같은 내장 옵션을 다시 추가하지 않게 합니다.
#endif915행에서 시작한 선택 구간을 마칩니다. 커널에 명령행을 내장하는 설정입니다. 켜지면 내장 문자열을 부트로더 명령행과 합칠지 완전히 대체할지 아래 설정으로 결정하고, 꺼지면 부트로더가 준 명령행을 유지합니다.
strscpy(command_line, boot_command_line, COMMAND_LINE_SIZE);작업용 command_line 버퍼에 최종 부팅 명령줄을 복사합니다. 다음 줄에서 호출자에게 이 버퍼의 포인터를 돌려줍니다.
olpc_ofw_detect();OLPC의 Open Firmware가 있는지 먼저 확인합니다. 이 펌웨어가 fixmap 위치 조정에 영향을 줄 수 있어 ioremap 영역을 사용하기 전에 수행합니다.
idt_setup_early_traps();부팅 초기에 필요한 예외 처리 진입점을 IDT에 설치합니다. CPU 탐지와 메모리 설정 중 예외를 처리할 최소 기반을 갖춥니다.
early_cpu_init();부트 CPU의 종류와 기능, 초기 CPU별 처리를 수행합니다. 이후 기능 선택과 패치가 사용할 CPU 정보를 확정하는 단계입니다.
jump_label_init();static key에 따른 분기 패치 정보를 준비합니다. 초기 옵션과 CPU 기능에 따라 빠른 경로를 선택할 기반입니다.
static_call_init();static call의 호출 지점과 대상 정보를 초기화합니다. 간접 호출 대신 패치 가능한 직접 호출을 사용할 준비를 합니다.
early_ioremap_init();정상 ioremap 이전에 사용하는 임시 물리 매핑 슬롯을 준비합니다. 초기 펌웨어 표와 MMIO를 접근할 때 사용합니다.
setup_olpc_ofw_pgd();OLPC Open Firmware가 필요한 페이지 테이블 구성을 준비합니다. 앞의 탐지 결과에 따라 펌웨어와 커널의 매핑을 맞춥니다.
parse_boot_params();boot_params에서 루트 장치, 화면·EDID 정보, 부트로더 종류·버전, EFI 부팅 여부 등을 커널 상태로 옮깁니다. 펌웨어와 부트로더가 남긴 고정 구조체를 이후 초기화에서 사용할 값으로 해석하는 단계입니다.
x86_init.oem.arch_setup();x86_init의 OEM별 아키텍처 설정 콜백을 실행합니다. 플랫폼이 제공한 초기화 연산을 공통 setup_arch 흐름의 이 지점에 연결합니다.
early_reserve_memory();덮어쓰면 안 되는 초기 메모리 구간을 memblock에 미리 예약합니다. RAM 목록을 추가한 뒤 발생할 할당이 부팅 자료를 침범하지 못하도록 먼저 수행합니다.
iomem_resource.end = (1ULL << boot_cpu_data.x86_phys_bits) - 1;물리 주소 비트 수로 표현 가능한 마지막 주소를 iomem 자원 트리의 상한으로 정합니다. 폭이 N이면 주소 범위 끝은 2의 N승에서 1을 뺀 값입니다.
e820__memory_setup();firmware/bootloader memory map을 정리해 usable RAM과 reserved type의 기준을 만든다.
parse_setup_data();부트로더가 연결 목록으로 넘긴 확장 setup_data 항목들을 처리합니다. 고정 boot_params만으로 전달하지 못한 추가 플랫폼 정보를 반영합니다.
copy_edd();부트로더의 EDD 디스크 정보를 커널 보관 영역에 복사합니다. 부팅용 임시 정보 영역이 사라져도 디스크 식별 자료를 유지하기 위한 처리입니다.
setup_initial_init_mm(_text, _etext, _edata, (void *)_brk_end);커널 코드·데이터 범위와 초기 brk 영역 끝을 init_mm에 기록합니다. x86의 초기 할당 영역까지 커널 메모리 범위에 포함합니다.
x86_configure_nx();CPU의 NX 지원과 사용 가능 상태를 설정합니다. 바로 뒤의 초기 옵션 처리에서 fixmap을 만들 수 있으므로 하드웨어가 실행 금지 비트를 지원하는지 먼저 반영합니다.
parse_early_param();early_param으로 등록된 커널 명령줄 옵션을 처리합니다. 일반 초기화 전에 콘솔·메모리 등의 초기 선택을 적용합니다.
if (efi_enabled(EFI_BOOT))EFI를 통해 부팅한 경우에만 EFI 관련 메모리를 memblock에 예약합니다. 비EFI 부팅에서는 의미 없는 EFI 메모리 예약을 건너뜁니다.
efi_memblock_x86_reserve_range();EFI 부팅 시 EFI 관련 메모리 범위를 memblock에 예약합니다. 펌웨어 정보와 필요한 EFI 메모리가 초기 할당에 재사용되지 않도록 합니다.
RISC-V · Linux 6.18.37
a0 hartid, a1 DTB와 SBI firmware contract
entry ABI는 a0에 boot hart id, a1에 DTB physical address를 넘긴다. EFI stub 경로도 가능하지만 일반 platform은 DTB와 SBI를 사용한다. setup_arch()가 FDT memory/CPU/ISA 정보를 읽고 memblock, SATP mode와 paging을 확정한다.
원본 코드: arch/riscv/kernel/setup.c:308-396
308 if (!using_ext)
309 pr_err("Queued spinlock without Zabha or Ziccrse");
310 else
311 pr_info("Queued spinlock %s: enabled\n", using_ext);
312}
313
314extern void __init init_rt_signal_env(void);
315
316void __init setup_arch(char **cmdline_p)
317{
318 parse_dtb();
319 setup_initial_init_mm(_stext, _etext, _edata, _end);
320
321 *cmdline_p = boot_command_line;
322
323 early_ioremap_setup();
324 sbi_init();
325 jump_label_init();
326 parse_early_param();
327
328 efi_init();
329 paging_init();
330
331 /* Parse the ACPI tables for possible boot-time configuration */
332 acpi_boot_table_init();
333
334 if (acpi_disabled) {
335#if IS_ENABLED(CONFIG_BUILTIN_DTB)
336 unflatten_and_copy_device_tree();
337#else
338 unflatten_device_tree();
339#endif
340 }
341
342 misc_mem_init();
343
344 init_resources();
345
346#ifdef CONFIG_KASAN
347 kasan_init();
348#endif
349
350#ifdef CONFIG_SMP
351 setup_smp();
352#endif
353
354 if (!acpi_disabled) {
355 acpi_init_rintc_map();
356 acpi_map_cpus_to_nodes();
357 }
358
359 riscv_init_cbo_blocksizes();
360 riscv_fill_hwcap();
361 apply_boot_alternatives();
362 init_rt_signal_env();
363
364 if (IS_ENABLED(CONFIG_RISCV_ISA_ZICBOM) &&
365 riscv_isa_extension_available(NULL, ZICBOM))
366 riscv_noncoherent_supported();
367 riscv_set_dma_cache_alignment();
368
369 riscv_user_isa_enable();
370 riscv_spinlock_init();
371
372 if (!IS_ENABLED(CONFIG_RISCV_ISA_ZBB) || !riscv_isa_extension_available(NULL, ZBB))
373 static_branch_disable(&efficient_ffs_key);
374}
375
376bool arch_cpu_is_hotpluggable(int cpu)
377{
378 return cpu_has_hotplug(cpu);
379}
380
381void free_initmem(void)
382{
383 if (IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) {
384 set_kernel_memory(lm_alias(__init_begin), lm_alias(__init_end), set_memory_rw_nx);
385 if (IS_ENABLED(CONFIG_64BIT))
386 set_kernel_memory(__init_begin, __init_end, set_memory_nx);
387 }
388
389 free_initmem_default(POISON_FREE_INITMEM);
390}
391
392static int dump_kernel_offset(struct notifier_block *self,
393 unsigned long v, void *p)
394{
395 pr_emerg("Kernel Offset: 0x%lx from 0x%lx\n",
396 kernel_map.virt_offset,주요 구문 해설
원본 89줄은 그대로 표시하며, 검토한 56개 구문에 설명을 붙였습니다.
if (!using_ext)queued spinlock에 사용할 Zabha 또는 Ziccrse 확장을 선택하지 못했는지 검사합니다. 지원 조합이 없는데 queued 방식으로 남아 있는 구성을 경고하는 조건입니다.
pr_err("Queued spinlock without Zabha or Ziccrse");필요한 ISA 확장 없이 queued spinlock을 사용하려는 구성을 오류 로그로 알립니다. 이 로그 한 줄이 ISA를 추가하거나 잠금 구현을 자동 교체하지는 않습니다.
elsequeued spinlock을 뒷받침할 ISA 확장을 찾은 경우의 분기입니다. 아래에서 선택한 확장 이름과 사용 상태를 출력합니다.
pr_info("Queued spinlock %s: enabled\n", using_ext);queued spinlock에 사용할 수 있는 ISA 확장 이름을 로그로 알립니다. 이 줄은 선택 결과를 표시하며 확장 기능을 새로 켜는 명령은 아닙니다.
extern void __init init_rt_signal_env(void);벡터 상태를 포함한 시그널 프레임 크기와 최소 시그널 스택 크기를 계산할 함수의 선언입니다. 인자와 반환값이 없으며 __init은 초기화용 함수임을 표시합니다. 실제 실행은 뒤의 setup_arch 안에서 이루어집니다.
void __init setup_arch(char **cmdline_p)RISC-V의 명령줄·SBI·DT/ACPI·메모리·ISA 기능을 초기화하는 함수입니다. 공통 커널 초기화가 사용할 명령줄 포인터도 cmdline_p로 제공받습니다.
parse_dtb();부팅 때 전달되거나 내장된 DTB를 파싱합니다. CPU·메모리·명령줄 등 뒤의 초기화가 사용할 장치 트리 정보를 얻습니다.
setup_initial_init_mm(_stext, _etext, _edata, _end);커널 코드 시작·끝, 데이터 끝, 이미지 끝을 init_mm에 기록합니다. 초기 커널 주소 공간이 차지하는 범위를 메모리 관리에 알립니다.
early_ioremap_setup();final page table 이전에 firmware table과 MMIO physical range를 임시로 접근할 fixmap 기반을 준비한다.
sbi_init();SBI 펌웨어 인터페이스의 버전·확장과 연산을 초기화합니다. 커널이 timer, IPI, hart 전원 관리 등을 펌웨어에 요청할 통로를 준비합니다.
jump_label_init();static key에 따른 분기 패치 정보를 초기화합니다. 뒤에서 ISA 기능과 초기 옵션에 따라 분기를 활성화할 수 있게 합니다.
parse_early_param();초기에 처리해야 하는 커널 명령줄 옵션을 해석합니다. 일반 드라이버 초기화 전에 필요한 선택을 반영합니다.
efi_init();EFI 시스템 표와 메모리 등 부팅 정보를 RISC-V 커널 상태로 가져옵니다. EFI를 통해 부팅한 경우의 플랫폼 자료를 준비합니다.
paging_init();선택된 Sv39/Sv48/Sv57 mode로 final kernel mapping을 만들고 memory management 다음 단계로 넘긴다.
acpi_boot_table_init();부팅 시 필요한 ACPI 표를 초기화합니다. ACPI 사용 여부에 따라 다음의 DT 트리 생성과 CPU 정보 경로가 달라집니다.
if (acpi_disabled) {ACPI가 꺼져 있으면 DTB에서 장치 정보를 만들도록 분기합니다. 내장 DTB 여부에 따라 원본 수명을 보장할 복사 과정이 추가됩니다.
#if IS_ENABLED(CONFIG_BUILTIN_DTB)DTB를 커널 이미지 안에 포함한 빌드에서는 장치 트리 원본을 복사하면서 트리 구조로 풉니다. 내장 DTB가 아니면 부트로더가 넘긴 원본을 기준으로 unflatten만 수행합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
unflatten_and_copy_device_tree();내장 DTB를 복사한 뒤 device_node 트리로 풉니다. 초기 이미지에 들어 있는 원본 DTB의 수명에 의존하지 않는 트리 자료를 유지하기 위한 경로입니다.
#elseDTB를 커널에 내장하지 않은 경우입니다. 부트로더가 전달한 DTB를 unflatten하여 커널의 장치 트리 노드 구조로 만들고 내장 원본을 별도로 복사하는 경로는 쓰지 않습니다.
unflatten_device_tree();외부에서 제공된 평탄한 DTB를 device_node 트리로 변환합니다. 드라이버가 노드와 속성을 탐색할 수 있는 형태로 만드는 단계입니다.
#endif335행에서 시작한 선택 구간을 마칩니다. DTB를 커널 이미지 안에 포함한 빌드에서는 장치 트리 원본을 복사하면서 트리 구조로 풉니다. 내장 DTB가 아니면 부트로더가 넘긴 원본을 기준으로 unflatten만 수행합니다.
misc_mem_init();초기 메모리 검사와 NUMA·sparsemem·메모리 zone 초기화를 수행하고 crashkernel 영역을 예약합니다. 물리 메모리 배치를 일반 페이지 관리가 사용할 상태로 만드는 단계입니다.
init_resources();RAM과 커널 이미지 등 시스템 메모리 자원 범위를 등록합니다. 자원 트리와 /proc/iomem에서 실제 메모리 배치를 추적할 수 있게 합니다.
#ifdef CONFIG_KASANKASAN은 커널의 범위 밖 접근이나 해제된 메모리 접근 등을 찾는 검사 기능입니다. 이 RISC-V 경로는 KASAN shadow 메모리의 정식 매핑을 초기화하며, 설정이 없으면 검사 지원 초기화와 그 메모리 부담도 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
kasan_init();KASAN을 사용하는 빌드에서 메모리 접근 오류 검사에 필요한 shadow 상태를 초기화합니다. 단순히 오류 메시지를 출력하는 기능이 아니라 접근 가능 여부를 추적할 기반입니다.
#endif346행에서 시작한 선택 구간을 마칩니다. KASAN은 커널의 범위 밖 접근이나 해제된 메모리 접근 등을 찾는 검사 기능입니다. 이 RISC-V 경로는 KASAN shadow 메모리의 정식 매핑을 초기화하며, 설정이 없으면 검사 지원 초기화와 그 메모리 부담도 없습니다.
#ifdef CONFIG_SMP여러 hart를 사용하는 SMP 빌드에서는 setup_smp로 사용 가능한 CPU와 시작 준비를 구성합니다. 단일 CPU 빌드에서는 보조 hart를 위한 이 초기화 경로를 생략합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 선택하며 런타임 조건 분기가 아닙니다.
setup_smp();SMP 구성에서 부트 hart와 보조 hart의 논리 CPU 대응 등 CPU 기동에 필요한 정보를 준비합니다. 이 호출 자체를 모든 CPU의 시작 완료로 보아서는 안 됩니다.
#endif350행에서 시작한 선택 구간을 마칩니다. 여러 hart를 사용하는 SMP 빌드에서는 setup_smp로 사용 가능한 CPU와 시작 준비를 구성합니다. 단일 CPU 빌드에서는 보조 hart를 위한 이 초기화 경로를 생략합니다.
if (!acpi_disabled) {ACPI를 사용하는 시스템에서만 CPU별 RINTC 자료와 NUMA 대응을 초기화합니다. DT 기반 시스템은 앞에서 얻은 노드 정보를 사용합니다.
acpi_init_rintc_map();ACPI RINTC 자료에서 RISC-V CPU의 로컬 인터럽트 컨트롤러 대응 정보를 만듭니다. 장치 트리 대신 ACPI로 CPU를 기술한 시스템의 초기화입니다.
acpi_map_cpus_to_nodes();ACPI가 제공하는 CPU·NUMA 노드 관계를 커널에 반영합니다. CPU가 어느 메모리 노드에 속하는지 이후 배치 정책이 사용할 수 있게 합니다.
riscv_init_cbo_blocksizes();펌웨어가 알려 준 cache-block operation의 블록 크기를 초기화합니다. 캐시 관리 명령이 어느 크기 단위로 작동하는지 이후 DMA·사용자 기능 설정이 참조합니다.
riscv_fill_hwcap();CPU ISA 확장을 모아 커널의 기능 비트와 사용자 HWCAP 정보를 구성합니다. 다음 alternatives 패치가 실제 사용 가능한 기능을 기준으로 동작하도록 합니다.
apply_boot_alternatives();CPU 기능과 우회 조건에 맞춰 부팅용 alternatives 명령들을 적용합니다. 소스에 있는 후보 중 실제 머신에서 사용할 명령 경로를 선택합니다.
init_rt_signal_env();벡터 상태를 포함한 시그널 컨텍스트 크기와 최소 시그널 스택 크기를 계산합니다. 이 최소 크기는 프로그램 시작 시 AT_MINSIGSTKSZ로 전달되므로 ISA와 벡터 크기 탐지 뒤에 정합니다.
if (IS_ENABLED(CONFIG_RISCV_ISA_ZICBOM) &&Zicbom 지원 코드를 빌드했는지 먼저 검사합니다. 다음 하드웨어 기능 검사까지 둘 다 참이어야 표준 캐시 관리 명령으로 비일관성 DMA를 지원할 수 있습니다.
riscv_isa_extension_available(NULL, ZICBOM))시스템의 공통 ISA 기능에 Zicbom이 있는지 검사하는 조건의 마지막 부분입니다. 빌드 지원만 있고 실제 hart에 기능이 없으면 DMA 지원 표시를 켜지 않습니다.
riscv_noncoherent_supported();Zicbom이 빌드와 하드웨어에서 모두 지원되면 비일관성 DMA용 캐시 처리를 제공할 수 있음을 알립니다. 캐시가 자동 일관성을 갖게 바꾸는 작업은 아닙니다.
riscv_set_dma_cache_alignment();비일관성 DMA를 지원하지 않는 시스템이면 dma_cache_alignment를 1로 낮춥니다. 캐시 정리를 위해 버퍼 사이를 캐시 라인 단위로 분리할 필요가 없는 경우의 불필요한 정렬 비용을 줄입니다.
riscv_user_isa_enable();모든 CPU에서 Zicboz 또는 Zicbom을 사용할 수 있으면 현재 스레드의 envcfg에 사용자 캐시 명령 허용 비트를 설정합니다. 일부 hart에만 있는 확장은 허용하지 않고 경고합니다.
riscv_spinlock_init();지원 ISA 확장에 맞춰 RISC-V 스핀 잠금 구현의 사용 경로를 초기화합니다. queued spinlock에 필요한 확장 조건도 이 선택에 관여합니다.
if (!IS_ENABLED(CONFIG_RISCV_ISA_ZBB) || !riscv_isa_extension_available(NULL, ZBB))Zbb 코드가 빌드되지 않았거나 시스템에 Zbb가 없으면 빠른 ffs 경로를 비활성화합니다. 둘 중 하나라도 빠졌을 때 미지원 비트 조작 명령을 선택하지 않도록 합니다.
static_branch_disable(&efficient_ffs_key);Zbb를 사용할 수 없으면 효율적인 find-first-set 경로의 static key를 끕니다. 지원하지 않는 비트 탐색 명령을 선택하지 않도록 기본 경로로 돌립니다.
bool arch_cpu_is_hotpluggable(int cpu)주어진 논리 CPU가 hotplug를 지원하는지 알려 주는 아키텍처 함수입니다. cpu는 하드웨어 hart ID가 아니라 Linux CPU 번호입니다.
return cpu_has_hotplug(cpu);해당 CPU에 기동·종료를 위한 hotplug 연산이 있는지 검사한 결과를 반환합니다. 모든 RISC-V CPU가 무조건 분리 가능하다고 가정하지 않습니다.
void free_initmem(void)부팅 초기화에만 쓰고 더 이상 필요하지 않은 init 영역을 일반 메모리로 돌려주는 함수입니다. 해제 전 실행 권한을 정리하는 단계도 포함합니다.
if (IS_ENABLED(CONFIG_STRICT_KERNEL_RWX)) {커널 코드와 쓰기 가능한 메모리 권한을 분리하는 STRICT_KERNEL_RWX 구성이면 init 영역의 실행 권한부터 없앱니다. 해제 후 일반 데이터 페이지에 실행 권한이 남지 않게 합니다.
set_kernel_memory(lm_alias(__init_begin), lm_alias(__init_end), set_memory_rw_nx);초기화 영역의 linear-map 별칭을 읽기·쓰기는 가능하고 실행은 불가한 속성으로 바꿉니다. 초기화 메모리를 일반 페이지로 돌려주기 전에 기존 실행 권한을 정리합니다.
if (IS_ENABLED(CONFIG_64BIT))64비트 커널에서는 linear-map 별칭 외에 원래 커널 가상 매핑도 실행 금지로 바꿉니다. 32비트에는 이 추가 별칭 처리 경로가 없습니다.
set_kernel_memory(__init_begin, __init_end, set_memory_nx);64비트 구성에서는 초기화 영역의 원래 커널 매핑에도 실행 금지를 적용합니다. 같은 물리 페이지의 별칭 한쪽에 실행 권한이 남지 않도록 합니다.
free_initmem_default(POISON_FREE_INITMEM);초기화용 메모리를 해제하여 일반 할당에 돌려줍니다. POISON_FREE_INITMEM 패턴을 사용해 해제 후 잘못된 참조를 드러내기 쉽게 합니다.
static int dump_kernel_offset(struct notifier_block *self,패닉 notifier가 커널의 주소 이동량을 출력할 콜백을 정의합니다. self는 notifier 등록 객체이며 실제 정보를 출력하는 코드는 아래에 있습니다.
unsigned long v, void *p)notifier 콜백의 이벤트 값 v와 전달 포인터 p를 받는 서명을 마칩니다. 이 콜백은 이 인자보다 전역 kernel_map의 부팅 주소 정보를 사용합니다.
pr_emerg("Kernel Offset: 0x%lx from 0x%lx\n",비상 로그 수준으로 커널 가상 주소 이동량과 기준 주소를 출력합니다. 패닉 때 얻은 주소를 원래 커널 심볼과 대응시키는 진단 자료입니다.
kernel_map.virt_offset,출력할 첫 값으로 kernel_map.virt_offset을 넘깁니다. 문자열의 첫 16진수 자리에 들어갈 커널 가상 주소 이동 정보입니다.
05 · WORKED EXAMPLE
숫자로 검산하기
1GiB RAM에서 early ownership ledger 만들기
RAM 0x80000000-0xbfffffff, kernel 32MiB at 0x80200000, DTB 256KiB at 0x88000000, initrd 64MiB at 0x90000000이라고 가정한다.
- RAMmemblock.memory에 전체 0x40000000 bytes를 넣되 firmware reserved range를 먼저 제외한다.
- kernel0x80200000-0x821fffff를 reserve해 allocator가 실행 중인 image를 덮지 못하게 한다.
- DTB/initrdDTB 0x88000000-0x8803ffff와 initrd 0x90000000-0x93ffffff를 parse/사용 완료까지 reserve한다.
- handofffree usable RAM만 buddy allocator에 넘기고 DTB copy나 initrd unpack이 끝난 시점에 각각 reclaim 여부를 결정한다.
결론부팅 주소 그림은 위치만 보여주면 부족하다. 각 범위의 현재 owner, 접근 주소 종류와 해제 조건을 함께 표시해야 한다.
06 · DEEP DIVE
주요 동작 상세 분석
공통 커널 코드와 아키텍처별 함수의 역할 구분
architecture entry가 최소 MMU/stack을 준비하고 boot parameter를 보존한 뒤 setup_arch()가 memory map, command line, CPU, interrupt/timer와 firmware tables를 검증한다. 결과는 memblock reservations와 generic kernel subsystem 초기화의 입력이 된다.
firmware-owned memory, bootloader temporary data, kernel image/initrd/DTB, reserved-memory와 일반 RAM을 구분한다. early mapping이 없는 physical pointer를 일반 C pointer처럼 dereference하면 안 된다.
arm64: Image header entry, x0 DTB와 EFI stub/ACPI 선택
boot protocol은 x0에 DTB physical address를 넘기고 MMU off, cache 조건을 규정한다. EFI stub을 거치면 EFI memory map과 system table도 보존한다. setup_arch()가 early FDT를 scan하고 memblock, ACPI 여부와 CPU topology를 확정한다.
setup_machine_fdt()가 DTB fixmap 매핑에 성공하면 먼저 예약하고 초기 scan으로 검증한다(실패 시 정지). 뒤의 arm64_memblock_init()은 linear mapping·mem= 한계를 먼저 적용하고 접근 가능한 initrd를 조건부 재편입·예약한다. 그 다음 paging_init()이 page table을 준비한다. EFI runtime mapping은 별도 경로다. 디버깅할 때는 x0 DTB PA, Image load PA/size, memblock memory/reserved, EFI descriptor, ACPI RSDP와 initrd start/end를 확인한다.
x86-64: boot_params zero page, BIOS/e820 또는 EFI와 ACPI
boot protocol의 boot_params에 e820 memory map, command line, initrd와 framebuffer 정보가 들어온다. EFI boot path는 EFI memory map을 병합하고 setup_arch()가 e820을 sanitize해 memblock에 반영하며 ACPI/SMBIOS를 찾는다.
parse_boot_params()로 입력을 받고 early_reserve_memory()에서 먼저 예약한 뒤 e820__memory_setup()으로 memory map을 정한다. mapping과 KASLR은 해당 후속 경로의 조건을 따로 확인한다. 디버깅할 때는 boot_params PA, e820 raw/sanitized map, setup_data, initrd, EFI descriptors, ACPI RSDP, KASLR physical/virtual offset을 본다.
RISC-V: a0 hartid, a1 DTB와 SBI firmware contract
entry ABI는 a0에 boot hart id, a1에 DTB physical address를 넘긴다. EFI stub 경로도 가능하지만 일반 platform은 DTB와 SBI를 사용한다. setup_arch()가 FDT memory/CPU/ISA 정보를 읽고 memblock, SATP mode와 paging을 확정한다.
parse_dtb()로 초기 DT 자료를 읽은 뒤 sbi_init()·efi_init()을 거쳐 paging_init()으로 간다. DT/ACPI의 후속 펼침은 paging 뒤에 갈라진다. SBI extension 결과는 timer·IPI·RFENCE·reset backend 선택에 쓰인다. 디버깅할 때는 a0/a1, boot_cpu_hartid, DTB totalsize, memblock ranges, SATP.MODE/root PPN, SBI spec/extensions와 initrd를 확인한다.
객체를 사용하고 해제하는 주체와 시점을 확인한다
DTB·initrd·EFI map·ACPI table은 진입 경로마다 예약·복사·해제 지점이 다르다. arm64의 DTB는 fixmap 매핑 성공 시 scan에 앞서 예약하고, initrd는 memory limit 처리 뒤 접근 가능할 때 예약한다. firmware 영역을 반환할 때는 실제 consumer와 메모리 속성을 확인한다.
주소나 레지스터 값만 확인하면 갱신되지 않은 상태를 놓칠 수 있다. 상태를 만드는 코드, 다른 코드에 전달하는 시점, 사용하는 코드, 해제하는 시점을 함께 기록한다.
최대 지연 시간은 명령 하나의 실행 시간만으로 결정되지 않는다
boot latency는 firmware enumeration, decompression, page-table construction, ACPI/DT parse와 secondary CPU bring-up으로 나뉜다. 오류 분석에는 timestamp보다 마지막 유효 memory ownership 변화가 중요하다.
평균값뿐 아니라 인터럽트를 비활성화한 시간, 다른 CPU의 응답 시간, 펌웨어 호출 시간, 재시도 횟수를 따로 확인해야 최대 지연의 원인을 찾을 수 있다.
임시 매핑 슬롯은 장치의 총개수가 아닙니다
Linux v6.18.37 ARM64의 FIX_BTMAPS_SLOTS는 7입니다. 이는 동시에 점유하는 임시 매핑 슬롯 수입니다. __early_ioremap()이 빈 prev_map을 찾고, 올바른 주소·크기로 early_iounmap()을 마치면 슬롯이 다시 비워집니다. 장치 하나가 여러 매핑을 쓸 수도 있으므로 장치 총개수로 바꾸어 읽지 않습니다.
슬롯당 창은 SZ_256K / PAGE_SIZE개의 페이지이며, 페이지 정렬 뒤 범위가 창 안에 들어가야 합니다. early_ioremap()의 I/O 속성과 early_memremap()의 일반 메모리 속성도 다릅니다. 이 수치와 속성을 다른 아키텍처에 그대로 적용하지 않습니다. v6.18.37 슬롯 할당·반납 · ARM64 fixmap 정의
English
ARM64 Linux v6.18.37 provides seven concurrent temporary mapping slots, not a seven-device lifetime quota. Successful unmapping with the matching address and size clears the slot for reuse. A device can need several mappings. Each window has SZ_256K / PAGE_SIZE pages; the aligned range must fit. early_ioremap uses I/O attributes, whereas early_memremap uses normal-memory attributes. Do not transplant these architecture-specific limits elsewhere.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | DTB 8-byte alignment/크기, RAM 밖 위치, initrd overlap 또는 reserved-memory 누락은 early abort와 allocator overwrite를 만든다. | x0 DTB PA, Image load PA/size, memblock memory/reserved, EFI descriptor, ACPI RSDP와 initrd start/end를 확인한다. |
| x86-64 | truncated boot_params, 겹친 e820 entry, initrd가 low/high limit 밖에 있거나 setup_data chain loop가 있으면 RAM 오인과 overwrite가 발생한다. | boot_params PA, e820 raw/sanitized map, setup_data, initrd, EFI descriptors, ACPI RSDP, KASLR physical/virtual offset을 본다. |
| RISC-V | boot hart mismatch, DTB의 hart mapping/ISA string 오류, unsupported SATP mode 또는 SBI version 차이는 secondary hart, timer와 MMU 초기화 실패를 만든다. | a0/a1, boot_cpu_hartid, DTB totalsize, memblock ranges, SATP.MODE/root PPN, SBI spec/extensions와 initrd를 확인한다. |
08 · LAB
재현과 계측 절차
- earlycon과 memblock=debug로 firmware map 입력부터 최종 reserved ledger까지 architecture별 로그를 비교한다.
- DTB/initrd를 의도적으로 kernel과 겹치게 배치해 boot protocol과 early validation이 어느 단계에서 거부하는지 확인한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 위치
- arm64arch/arm64/kernel/setup.c:273-363
- x86-64arch/x86/kernel/setup.c:878-982
- RISC-Varch/riscv/kernel/setup.c:308-396
- arm64 DTB 예약setup_machine_fdt: 예약 뒤 scan
- arm64 memory limit와 initrdarm64_memblock_init: 한계 적용 뒤 조건부 예약
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.