一、开篇
对于linux内核来说,她有一个统一的入口,那就是head.o模块,在不同架构下该模块有着不同的文件名称,例如head.S、head_xxx.S;该文件的具体路径是(/arch/<ARCH>/kernel/head.S),其中<ARCH>是具体的架构名称。head.o模块用于完成和架构、CPU相关的初始化工作,为内核主体的执行做准备。除此之外,head.o模块的核心操作如下:
- 检查处理器和架构的有效性。
- 创建初始的页表表项。
- 启用处理器的内存管理单元(MMU)。
- 进行错误检测并报告。
- 跳转到内核主体的起始位置。(main.c中的
start_kernel()
函数)
head.o内核入口具有的特征:
- 一般由汇编语言来实现设计。
- 调试起来比较复杂。
- 该模块属于linux引导过程的早期阶段,地址的映射范围有限。
二、linux内核入口详情
本小节重点描述ARM和x86下的内核入口。
(2-1)ARM架构下的内核入口
在ARM架构下的内核入口由文件head.S
、head-common.S
文件描述,放置在(/arch/arm/kernel/)目录下。
汇编主线如下代码所示:
__HEAD
ENTRY(stext)
ARM_BE8(setend be ) @ ensure we are in BE8 mode
THUMB( adr r9, BSYM(1f) ) @ Kernel is always entered in ARM.
THUMB( bx r9 ) @ If this is a Thumb-2 kernel,
THUMB( .thumb ) @ switch to Thumb now.
THUMB(1: )
#ifdef CONFIG_ARM_VIRT_EXT
bl __hyp_stub_install
#endif
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
#ifdef CONFIG_ARM_LPAE
mrc p15, 0, r3, c0, c1, 4 @ read ID_MMFR0
and r3, r3, #0xf @ extract VMSA support
cmp r3, #5 @ long-descriptor translation table format?
THUMB( it lo ) @ force fixup-able long branch encoding
blo __error_lpae @ only classic page table format
#endif
#ifndef CONFIG_XIP_KERNEL
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
#else
ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
#endif
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
bl __vet_atags
#ifdef CONFIG_SMP_ON_UP
bl __fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
bl __fixup_pv_table
#endif
bl __create_page_tables
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_processor_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, =__mmap_switched @ address to jump to after
@ mmu has been enabled
adr lr, BSYM(1f) @ return (PIC) address
mov r8, r4 @ set TTBR1 to swapper_pg_dir
ldr r12, [r10, #PROCINFO_INITFUNC]
add r12, r12, r10
ret r12
1: b __enable_mmu
ENDPROC(stext)
.ltorg
#ifndef CONFIG_XIP_KERNEL
2: .long .
.long PAGE_OFFSET
#endif
由以上代码可知,ARM架构下的head.o
入口模块的大致执行流如下图所示:
在上图中:
- __enable_mmu:用于使能mmu。
- __lookup_processor_type:用于查找处理器类型。
- __create_page_tables:用于设置初始页表。这里只设置了让内核运行所需的最少量页表,意味着映射范围有限。
- __vet_atags:用于确定r2 atags指针的有效性。
- __mmap_switched:用于复制数据段、清BSS、保存处理器ID、保存机器类型、保存atags指针,保存cp15控制寄存器的值,最后调用 start_kernel ()函数来启动内核。
(2-2)x86架构下的内核入口
首先x86架构下的内核入口如下图所示(/arch/x86/kernel):
从上图中可知:
-
head32.c
、head_32.S
两个文件用于描述32位的linux内核入口。 -
head64.c
、head_64.S
两个文件用于描述64位的linux内核入口。 -
head.c
是公共的文件。x86架构下的
head.o
入口模块大致执行流如下图所示:
由上图可知,32位的linux内核入口核心是
i386_start_kernel()
函数,定义如下(/arch/x86/kernel/head32.c):asmlinkage __visible void __init i386_start_kernel(void) { cr4_init_shadow(); sanitize_boot_params(&boot_params); /* Call the subarch specific early setup function */ switch (boot_params.hdr.hardware_subarch) { case X86_SUBARCH_INTEL_MID: x86_intel_mid_early_setup(); break; case X86_SUBARCH_CE4100: x86_ce4100_early_setup(); break; default: i386_default_early_setup(); break; } start_kernel(); }
64位的linux内核入口核心函数是
x86_64_start_kernel()
,定义如下(/arch/x86/kernel/head64.c):asmlinkage __visible void __init x86_64_start_kernel(char * real_mode_data) { int i; /* * Build-time sanity checks on the kernel image and module * area mappings. (these are purely build-time and produce no code) */ BUILD_BUG_ON(MODULES_VADDR < __START_KERNEL_map); BUILD_BUG_ON(MODULES_VADDR - __START_KERNEL_map < KERNEL_IMAGE_SIZE); BUILD_BUG_ON(MODULES_LEN + KERNEL_IMAGE_SIZE > 2*PUD_SIZE); BUILD_BUG_ON((__START_KERNEL_map & ~PMD_MASK) != 0); BUILD_BUG_ON((MODULES_VADDR & ~PMD_MASK) != 0); BUILD_BUG_ON(!(MODULES_VADDR > __START_KERNEL)); BUILD_BUG_ON(!(((MODULES_END - 1) & PGDIR_MASK) == (__START_KERNEL & PGDIR_MASK))); BUILD_BUG_ON(__fix_to_virt(__end_of_fixed_addresses) <= MODULES_END); cr4_init_shadow(); /* Kill off the identity-map trampoline */ reset_early_page_tables(); clear_bss(); clear_page(init_level4_pgt); kasan_early_init(); for (i = 0; i < NUM_EXCEPTION_VECTORS; i++) set_intr_gate(i, early_idt_handler_array[i]); load_idt((const struct desc_ptr *)&idt_descr); copy_bootdata(__va(real_mode_data)); /* * Load microcode early on BSP. */ load_ucode_bsp(); /* set init_level4_pgt kernel high mapping*/ init_level4_pgt[511] = early_level4_pgt[511]; x86_64_start_reservations(real_mode_data); }
在x86_64_start_kernel()函数的末尾会调用
x86_64_start_reservations()
函数,其定义如下(/arch/x86/kernel/head64.c):void __init x86_64_start_reservations(char *real_mode_data) { /* version is always not zero if it is copied */ if (!boot_params.hdr.version) copy_bootdata(__va(real_mode_data)); reserve_ebda_region(); start_kernel(); }
三、结尾
linux内核的入口设计比较复杂。本文记录了关于arm和x86两种架构下的linux内核入口。主要叙述了与入口相关的描述文件和跳转到linux内核本体start_kernel()
函数的前半程实现机制和一些函数调用细节。linux内核的入口是分析linux内核启动流程的关键之处,站在linux内核的使用角度来说,学习和了解其中的一些细节,能帮助更好的深入理解linux内核。
小生,今儿记一笔,O(∩_∩)O哈哈~。
由于小生精力和知识有限,如遇文章存在有不妥之处,请多多批评或与我讨论,谢谢啦。
搜索关注【嵌入式小生】wx公众号获取更多精彩内容>>>>