【linux kernel】linux内核入口:head.o

一、开篇

对于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.Shead-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.chead_32.S两个文件用于描述32位的linux内核入口。

  • head64.chead_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公众号获取更多精彩内容>>>>

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值