Linux内核启动start_kernel之前逻辑分析(汇编)

在bootloader的帮助下,内核被载入到内存中,内核映像被加载到内存并获得控制权之后,内核启动流程开始。通常,内核映像以压缩形式存储,并不是一个可以执行的内核。因此内核的首要工作是自解压内核映像。

内核编译生成vmlinux后,通常会对其进行压缩,得到zImage(小内核,小于512KB)或者bzImage(大内核,大于512KB),在它们的头部嵌有解压缩程序。真正的内核执行映像其实是在编译时生arch/${ARCH}/boot/文件夹中的bin文件。

以arm为例

Linxu镜像的格式:

(1)Image:直接生成并未压缩的内核,一般用于PC机。

(2)zImage:Image的压缩版,采用gzip进行压缩,比Image体积小,但启动时需要进行自解压,嵌入式系统中一般采用此方法。

(3)uImage:是u-boot专用的一种内核镜像格式,它是在zImage的基础上又添加了一个长度为0x40的标签头,在u-boot启动时会去掉此头信息,仍按zImage启动,头信息主要用于区分不同格式的内核镜像。

(4)xipImage:片上执行的未压缩内核

(5)bootpImage:将内核与根文件系统制作在一起的镜像。

内核的压缩和解压缩代码都在文件夹arch/arm/boot/compressed下,编译完毕后将产生head.o, misc.o , piggy.gzip.o, vmlinux, decompress.o这几个文件。

(1)head.o是内核的头部文件,负责初始设置。

(2)misc.o将主要负责内核的解压工作。

(3)piggy.gzip.o是一个压缩的内核(kernel/vmlinux)

(4)vmlinux是没有(zImage是压缩过后内核)压缩过的内核。它是由piggy.gzip、head.o、misc.o组成的。

(5)decompress.o是为支持很多其它的压缩格式而新引入的。

压缩过的kernel入口第一个源代码位置在arch/arm/boot/compressed/head.S,它将调用函数decompress_kernel(),这个函数在arch/arm/boot/compressed/misc.c中。decompress_kernel又调用arch_decomp_setup进行设置,decompress_kernel又调用gunzip()将内核放于指定的位置。

而gunzip位于kernel/lib/inflate.c, inflate.c是从gzip源程序中分离出来的,

启动流程分析:

1、通过linux/arch/arm/boot/compressed目录下的Makefile寻找到下面代码段:

$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
		$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
		$(bswapsdi2) $(efi-obj-y) FORCE
	@$(check_for_multiple_zreladdr)
	$(call if_changed,ld)
	@$(check_for_bad_syms)

2、从这里我们可以得知链接脚本vmlinux.lds,所以分析vmlinux.lds,根据其中ENTRY(stext)得到内核入口函数为stext,

面stext在linux/arch/arm/kernel/head.S中

3、head.S中内核引导阶段

ENTRY(stext)
	。
	。
	。
	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'                           //不支持则打印错误信息
 
          。
	。
	。
	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使能后跳转地址
						@ mmu has been enabled
	adr	lr, BSYM(1f)			@ return (PIC) address
	mov	r8, r4				@ set TTBR1 to swapper_pg_dir
 ARM(	add	pc, r10, #PROCINFO_INITFUNC	)
 THUMB(	add	r12, r10, #PROCINFO_INITFUNC	)
 THUMB(	mov	pc, r12				)
1:	b	__enable_mmu                       

4、查找标签__mmap_switched所在位置:linux/arch/arm/kernel/head-common.S

__mmap_switched:
	/*
	 * The following fragment of code is executed with the MMU on in MMU mode,
	 * and uses absolute addresses; this is not position independent.
	 *
	 *  r0  = cp#15 control register
	 *  r1  = machine ID
	 *  r2  = atags/dtb pointer
	 *  r9  = processor ID
	 */
	//保存设备信息、设备树及启动参数存储地址
	。
	。
	。
	b	start_kernel

从start_kernel函数开始,内核进入C语言部分,完成内核大部分初始化工作。

vmlinux.lds.S

vmlinux.lds.S是如何组织内核的每个函数存放在内核镜像文件的位置,我们知道在编译内核生成内核文件的时候,其实这个过程分两步,一个是编译,另一个是链接,vmlinux.lds.S要做的就是告诉编译器如何链接编译好的各个内核.o文件。

小知识:链接器中的entry 
链接器 按以下优先顺序设入口点,找到即停止 
1. -e 命令行选项 
2. 脚本中的entry(symbol)命令 
3. 如定义了start的值,取其值为入口点 
4. 如果存在.text section,使用.text section的第一字节的位置值 
5. 使用值0

所以

OUTPUT_ARCH(arm)
ENTRY(stext)

表明我们指定的stext作为程序的入口点。

1、.=PAGE_OFFSET+TEXT_OFFSET

arch/arm/include/asm中的memory.h文件定义了:

#define PAGE_OFFSET                UL(CONFIG_PAGE_OFFSET)

CONFIG_PAGE_OFFSET是在内核配置里面配置的,PAGE_OFFSET代表的是内核image的起始虚拟地址,在arch/arm/Makefile中定义:

textofs-y        := 0x00008000
TEXT_OFFSET := $(textofs-y)

TEXT_OFFSET代表的是内核的image存放在内存中地址,注意这个地址为相对内存的起始地址的偏移量,是个相对的偏移量不是实际的存放内存物理地址。而且这个偏移量必须为0xXXXX8000,XXXX为任意值。

2、简单例子

SECTIONS
{
. = 0×10000; /**把定位器符号置为0x10000(若不指定,则该符号的初始值为0)*/
.text : { *(.text) } /* 将所有输入文件的.text section合并成一个.text section,该section的地址由定位器符号的值指定*/
. = 0×8000000; /* 把定位器符号置为0x8000000*/
.data : { *(.data) } /* 将所有输入文件的.data section合并成一个.data section,该section的地址被置为0x8000000*/
.bss : { *(.bss) } /* 将所有输入文件的.bss section合并成一个.bss section,该section的地址被置为0x8000000+.data section的大小*/
/*连接器每读完一个section描述后,将定位器符号的值增加该section的大小,这里没有考虑对齐约束*/
}

.是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置,该符号只能在SECTIONS命令内使用。

3、简单命令

INCLUDE filename: 包含其他名为filename的链接脚本,可以嵌套使用,最大深度为10
INPUT(files):将括号内的文件做为链接过程的输入文件。ld首先在当前目录下寻找该文件,如果没找到,则在由-L指定的搜索路径下搜索。
GROUP(files):指定需要重复搜索符号定义的多个输入文件。file必须是库文件,且file文件作为一组被ld重复扫描,直到不再有新的未定义的引用出现。
OUTPUT(filename):定义输出文件的名字。同ld的-o选项,不过-o选项的优先级更高。
SEARCH_DIR(path):定义搜索路径。同ld的-L选项,不过由-L指定的路径要比它定义的优先被搜索。
STARTUP(filename):指定filename为第一个输入文件
OUTPUT_FORMAT(DEFAULT,BIG,LITTLE):定义三种输出文件的格式,若有命令行选项-EB,则使用第2个BFD格式,若有命令行选项-EL,则使用第3鼐BFD格式,否则默认选第一个BFD格式。
TARGET(BFDNAME):设置输入文件的BFD格式。
ASSERT(EXP,MESSAGE):如果EXP不为真,终止连接过程
EXTERN(SYMBOL SYMBOL...):在输出文件中增加未定义的符号,如同连接器选项-u
FORCE_COMMON_ALLOCATION:为common symbol(通用符号)分配空间,即使用了-r连接选项也为其分配
NOCROSSREFS(SECTION SECTION...):检查列出的输出secion,如果发现他们之间有相互引用,则报错。对于某些系统,特别是内存较紧张的嵌入式系统,某些section是不能同时存在内存中的,所以他们之间不能相互引用
OUtPUT_ARCH(arch):设置输出文件的machine architecture

.4、SECTIOINS命令

SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section:如何将输入section合并为输出section,如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA)。

(1)输出section描述

SECTION-NAME [ADDRESS] [(TYPE)] : [AT(LMA)]
{
OUTPUT-SECTION-COMMAND
OUTPUT-SECTION-COMMAND
…
} [>REGION] [AT>LMA_REGION] [:PHDR HDR ...] [=FILLEXP]

SECTION-NAME左右的空白、圆括号、冒号是必须的,ADDRESS是一个表达式,它的值用于设置VMA,如果没有该选项且有REGION选项,那么连接器将根据REGION设置VMA,哪果也没有REGION选项,那么连接器将根据定位符‘.’的值设置该section的VMA,将定位符号的值调整到满足输出section对齐要求后的值。

TYPE设置输出section的类型,如果没有指定TYPE类型,那么连接器根据输出section引用的输入section的类型设置该输出section类型。

默认情况下,LMA等于VMA,但可以通过AT(LMA)项画指定LMA。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值