可以结合《hi3536uboot引导内核全过程》一文一起看
1、uImage生成过程
(1)vmlinux
根目录下vmlinux为kernel未经过任何处理的原始可执行文件。根据arch/arm/kernel/vmlinux.lds连接文件生成:
. = PAGE_OFFSET + TEXT_OFFSET; =0xC0000000 + 0x8000 内核运行时虚拟起始地址(3G/1G内核情况,如果2G/2G则为0x80000000+0x8000)
#definePAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
textofs-y := 0x00008000
TEXT_OFFSET:= $(textofs-y) (arch/arm/Makefile文件中)
(2)uImage依赖关系
在arch/arm/boot/Makefile:
$(obj)/uImage: $(obj)/zImage
$(obj)/zImage: $(obj)/compressed/vmlinux
$(obj)/compressed/vmlinux:$(obj)/Image
$(obj)/Image:vmlinux
在arch/arm/boot/compressed/Makefile:
$(obj)/vmlinux:$(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.lzma.o $(addprefix $(obj)/,$(OBJS))
$(obj)/piggy.lzma:$(obj)/../Image
$(obj)/piggy.lzma.o: $(obj)/piggy.lzma
compressed/vmlinux:根据当前目录下的vmlinux.lds生成,其中有一个重要字段:
. =TEXT_START; (=0,why?
arch/arm/boot/compressed/Makefile
/* Decompressors running fromRAM should not define ZTEXTADDR. Decompressors running directly from ROM or Flash must define ZTEXTADDR*/
TEXT_START = $(ZTEXTADDR) = 0;
BSS_START = $(ZBSSADDR) =ALIGN(8)
)
.piggydata: {
*(.piggydata)
}
存放的就是上面的piggy.lzma,其arch/arm/boot/compressed/piggy.lzma.S内容如下:
.section.piggydata,#alloc
.globl input_data
input_data:
.incbin"arch/arm/boot/compressed/piggy.lzma"
.globl input_data_end
input_data_end:
在自解压判断和解压时就会用到上面红色地址。
(3)uImage生成流程
//根据vmlinux.lds生成
根目录vmlinux ——>
//去掉vmlinux调试信息
Arch/arm/boot/Image ——>
//lzma压缩末尾4个字节代表Image大小,汇编代码通过input_data_end获取
Arch/arm/boot/compressed/piggy.lzma——>
//根据vmlinux.lds,将压缩内核、引导代码、自解压代码等重新组成新的vmlinux
Arch/arm/boot/compressed/piggy.lzma.o+head.o+misc.o+ decompress.o——>
Arch/arm/boot/compressed/vmlinux——>
//将新的vmlinux通过objcopy生成zImage
Arch/arm/boot/zImage——>
//添加64字节头生成uImage
Arch/arm/boot/uImage
arch/arm/mach-hi3536/Makefile.boot
zreladdr-y := 0x40008000 //需要和uboot保持一致
params_phys-y := 0x00000100 //需要和uboot保持一致
initrd_phys-y := 0x00800000
arch/arm/boot/Makefile
/* Note:the following conditions must always be true:
ZRELADDR == virt_to_phys(PAGE_OFFSET +TEXT_OFFSET)
PARAMS_PHYS must be within 4MB of ZRELADDR
INITRD_PHYS must be in RAM */
ZRELADDR :=$(zreladdr-y) = 0x40008000 //zImage解压后最终Image的起始地址,uboot引导时zImage一般也存放在这个地址,所以后面涉及到解压前当前程序搬运问题。
PARAMS_PHYS:= $(params_phys-y) = 0x00000100 //内核参数地址
INITRD_PHYS:= $(initrd_phys-y) = 0x00800000
2、内核代码自解压过程
(1)arch/arm/boot/compressed/vmlinux.lds
连接文件内容
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
/DISCARD/ : {
*(.ARM.exidx*)
*(.ARM.extab*)
/*
* Discard any r/w data - this produces alink error if we have any,
* which is required for PICdecompression. Local data generates
* GOTOFF relocations, which prevents itbeing relocated independently
* of the text/got segments.
*/
*(.data)
}
. = 0; // TEXT_START连接地址0x0,因为从RAM直接启动
_text = .;
.text : {
_start = .;
*(.start) //入口地址
*(.text)
*(.text.*)
*(.fixup)
*(.gnu.warning)
*(.glue_7t)
*(.glue_7)
}
.rodata : {
*(.rodata)
*(.rodata.*)
}
.piggydata : {
*(.piggydata) //存放piggy.lzma字段
}
. = ALIGN(4);
_etext = .;
.got.plt : { *(.got.plt) }
_got_start = .;
.got : { *(.got) }
_got_end = .;
/* ensure the zImage file size is always amultiple of 64 bits */
/* (without a dummy byte, ld just ignores theempty section) */
.pad : { BYTE(0); . = ALIGN(8); }
_edata = .; //结束地址
. = ALIGN(8);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
. = ALIGN(8); /* the stack must be 64-bit aligned */
.stack : { *(.stack) }
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}
上面的.got,那么GOT表是什么?
GOT(Global Offset Table)表中每一项都是本运行模块要引用的一个全局变量或函数的地址。可以用GOT表来间接引用全局变量、函数,也可以把GOT表的首地址作为一个基准,用相对于该基准的偏移量来引用静态变量、静态函数
(2)arch/arm/boot/compressed/head.S
以下是对该自解压代码分析:
.section ".start", #alloc,#execinstr //以下代码存放在vmlinux.lds的.start字段
/*
* sort out different calling conventions
*/
.align
.arm //总是进入arm状态
start:
.type start,#function //定义start函数
.rept 7
mov r0, r0
.endr
ARM( mov r0, r0 )
ARM( b 1f )
THUMB( adr r12, BSYM(1f) )
THUMB( bx r12 )
.word 0x016f2818 @Magic numbers tohelp the loader
.word start //加载和运行zImage的绝对地址(编译时确定),在vmlinux.lds中zImage连接地址为0
.word _edata //zImage的结束地址,在vmlinux.lds中可以看到
THUMB( .thumb )
1:
mrs r9, cpsr
#ifdefCONFIG_ARM_VIRT_EXT
bl __hyp_stub_install @ get into SVCmode, reversibly
#endif
mov r7, r1 //保存机器码到R7,这个由uboot传递的第二个参数
mov r8, r2 //保存参数列表指针到R8,这个由uboot传递的第三个参数
…
ldr r4, =zreladdr //最终Image的执行起始地址存放在R4,上一节中提到该值为0x40008000,0x40000000为物理内存起始地址,0x8000为偏移
#ifdefined(CONFIG_ARCH_HI3536) \
|| defined(CONFIG_ARCH_HI3536_SLAVE) \
|| defined(CONFIG_ARCH_HI3521A)
/*
* set SMP bit ACTLR register to enable I cacheand D cache
*/
mrc p15, 0, r0, c1, c0, 1
orr r0, #(1 << 6)
mcr p15, 0, r0, c1, c0, 1
#endif
bl cache_on
/*开始cache以及MMU,并且会做一些页表初始化的工作,然后在head.S结束时关闭mmu和cache。初始化页表没有真正的使用价值,只是为了打开解压缩代码使用D-cache而打开的,所以这里的页表中映射的虚拟地址和物理地址的关系是等价映射的(也就是将物理地址映射到等值的虚拟地址上)。*/
restart: adr r0, LC0
/* 获取LC0的运行地址,依次将相应的连接地址放入到下面的寄存器中。这里之所以是restart主要是判断解压后是否会覆盖当前镜像,如果会就先搬运当前镜像到解压结束地址+堆栈之后,再从这里开始运行解压内核。*/
ldmia r0, {r1, r2, r3, r6, r10, r11, r12}
ldr sp, [r0, #28] //此时r0中存放的还是LC0的运行地址,所以加28后正好是LC0数组中的.L_user_stack_end的值(栈的结束地址),在head.S的结尾定义
{
.type LC0, #object
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word _edata @ r6 //zImage镜像的结束地址
.word input_data_end - 4 @ r10在上一节中提到过这个地址存放的就是image大小
.word _got_start @ r11
.word _got_end @ ip
.word .L_user_stack_end @ sp
.size LC0, . - LC0
}
/* 编译时连接地址从0x0开始,这里计算出当前实际运行的物理地址 */
sub r0, r0, r1 @ 计算偏移量
add r6, r6, r0 @ _edata 实际运行地址
add r10, r10, r0 @ 获取Image大小存放的实际物理地址
/*
内核编译系统将Image的大小4字节以小端形式添加到压缩数据的末尾。这里是将解压后的内核数据大小正确的放入R9寄存器里。
*/
ldrb r9, [r10, #0]
ldrb lr, [r10, #1]
orr r9, r9, lr, lsl #8
ldrb lr, [r10, #2]
ldrb r10, [r10, #3]
orr r9, r9, lr, lsl #16
orr r9, r9, r10, lsl #24
/* malloc获取的内存空间位于重定向的栈指针之上(64K max) */
add sp, sp, r0 //使用r0修正sp,得到堆栈的实际结束物理地址,为什么是结束地址?因为栈是向下生长的。
add r10, sp, #0x10000 //执行这句之前sp中存放的是栈的结束地址,执行后,r10中存放的是堆空间的结束物理地址
…
/* 检查解压是否会自我覆盖的情况
* r4 = 最终执行地址(解压后内核起始地址)
* r9 = 解压后的内核大小
* r10 = 当前执行映像的结束地址,包括bss/stack/malloc空间
* 判断是否会自我覆盖的的情况:
* A. r4 - 16k页目录 >= r10 -> OK,即最终执行地址在当前映像之后
* B. r4 + 内核大小 <=wont_overwrite的地址(当前位置PC) -> OK 即最终执行地址在当前映像之前
* C. 不满足上面条件的就会自我覆盖,需要搬运当前映像到不会覆盖的地方运行。通常是这种情况,zImage和Image同一个地址。uboot将zImage搬运到0x40008000运行,上面的r4最终执行地址也是该地址。
*/
add r10, r10, #16384
cmp r4, r10
bhs wont_overwrite
add r10, r4, r9
adr r9, wont_overwrite
cmp r10, r9
bls wont_overwrite
只有发生自我覆盖时才会运行这里,否则直接运行wont_overwrite开始的地方。
/*
* 将当前运行映像搬运到解压后的内核结束地址后面
* r6 =_edata
* r10 = 解压后内核结束地址
* 由于拷贝时当前映像向后执行移动,为了防止源数据和目标数据重叠,从后向前拷贝代码。
*/
/*
* Bump to the next 256-byte boundarywith the size of
* the relocation code added. Thisavoids overwriting
* ourself when the offset is small.
*/
add r10, r10, #((reloc_code_end -restart + 256) & ~255)
bic r10, r10, #255
/* Get start of code we want to copyand align it down. */
adr r5, restart //获取需要搬运的当前映像起始位置,并32B对齐
bic r5, r5, #31
/*Relocate the hyp vector base if necessary */
#ifdefCONFIG_ARM_VIRT_EXT
mrs r0, spsr
and r0, r0, #MODE_MASK
cmp r0, #HYP_MODE
bne 1f
bl __hyp_get_vectors
sub r0, r0, r5
add r0, r0, r10
bl __hyp_set_vectors
1:
#endif
sub r9, r6, r5 @ r6-r5 = 拷贝大小
add r9, r9, #31 @ 对齐
bic r9, r9, #31 @ ... of 32 bytes
add r6, r9, r5 //r6=当前映像需要搬运的结束地址
add r9, r9, r10 //当前映像搬运后的结束地址
//搬运当前映像,不包含bss/stack/malloc空间
1: ldmdb r6!, {r0 - r3, r10 - r12, lr}
cmp r6, r5
stmdb r9!, {r0 - r3, r10 - r12, lr}
bhi 1b
/* r6存放当映像和目标映像地址偏移量,修正SP和实现代码跳转 */
sub r6, r9, r6
#ifndefCONFIG_ZBOOT_ROM
修正sp地址,修正后sp指向重定向后的代码的栈区的结束地址(栈向下生长),栈区后面紧跟的就是堆空间
add sp, sp, r6
#endif
bl cache_clean_flush //清除缓存
adr r0, BSYM(restart) //获得restart的运行地址
add r0, r0, r6 //获得重定向后的代码的restart的物理地址
mov pc, r0 //跳到重定向后的代码的restart处开始执行
/* 在上面的跳转之后,程序又从restart开始。
* 但这次在检查自我覆盖的时候,新的执行位置必然满足上面B情况
* 所以必然直接跳到了下面的wont_overwrite执行
*/
wont_overwrite:
/*
*如果delta(当前映像地址与编译时的地址偏移)为0, 我们运行的地址就是编译时确定的地址。但我们不一致,所以需要重定向。
* r0 =delta
* r2 =BSS start
* r3 =BSS end
* r4 =kernel execution address
* r5 =appended dtb size (0 if not present)
* r7 =architecture ID
* r8 =atags pointer
* r11 = GOT start
* r12 = GOT end
* sp =stack pointer
*/
orrs r1, r0, r5
beq not_relocated //如果delta为0,无须对GOT表项和BSS进行重定位,否则下面进行重新定向
add r11, r11, r0 //重定位GOT start
add r12, r12, r0 //重定位GOT end
add r2, r2, r0 //重定位BSS start
add r3, r3, r0 //重定位BSS end
//重定位所有GOT表的入口项
1: ldr r1, [r11, #0] @ relocate entries in the GOT
add r1, r1, r0 @This fixes up C references
cmp r1, r2 @ if entry >= bss_start &&
cmphs r3, r1 @ bss_end > entry
addhi r1, r1, r5 @ entry += dtb size
str r1, [r11], #4 @ next entry
cmp r11, r12
blo 1b
// 重定向bbs
add r2, r2, r5
add r3, r3, r5
//到这里,当前映像搬运和重定向完成,开始内核解压
not_relocated: mov r0, #0
1: str r0, [r2], #4 @ clear bss
str r0, [r2], #4
str r0, [r2], #4
str r0, [r2], #4
cmp r2, r3
blo 1b
/*
* C运行环境充分建立,设置部分指针,开始内核解压
* r4 = 解压后内核执行地址0x40008000
* r7 = 机器码
* r8 = 内核参数列表指针
*/
mov r0, r4
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max
mov r3, r7
bl decompress_kernel //r0/r1/r2/r3分别是参数传递给decompress_kernel,该函数是C语言解压函数。
{
ret = do_decompress(input_data, input_data_end - input_data,
output_data, error);
input_data:上一节arch/arm/boot/compressed/piggy.lzma.S中存放piggy.lzma的起始地址
output_data:r4的地址0x40008000
}
bl cache_clean_flush //清除缓存
bl cache_off //关闭cache
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mrs r0, spsr @ Get saved CPU boot mode
and r0, r0, #MODE_MASK
cmp r0, #HYP_MODE @ if not booted in HYP mode...
bne __enter_kernel @ 进入解压后的内核运行
{
__enter_kernel:
mov r0, #0 @ must be 0
ARM( mov pc, r4 ) @ call kernel r4=解压后内核执行地址0x40008000
THUMB( bx r4 ) @ 跳转到0x40008000开始执行Image内核,入口为stext,细节见下文。
}
adr r12, .L__hyp_reentry_vectors_offset
ldr r0, [r12]
add r0, r0, r12
bl __hyp_set_vectors
__HVC(0) @ otherwise bounce to hyp mode
b . @ should never bereached
.align 2
.L__hyp_reentry_vectors_offset:.long __hyp_reentry_vectors - .
3、内核启动代码分析
(1)arch/arm/kernel/head.S
/*
* swapper_pg_dir是初始化页表的虚拟地址
* 页表放在KERNEL_RAM_VADDR前16K,所以KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000,vmlinux.lds是0xC0008000(虚拟地址)
*/
#defineKERNEL_RAM_VADDR (PAGE_OFFSET +TEXT_OFFSET) = 0xC0008000
#if(KERNEL_RAM_VADDR & 0xffff) != 0x8000
#errorKERNEL_RAM_VADDR must start at 0xXXXX8000
#endif
#definePG_DIR_SIZE 0x4000 //页表大小16K
#definePMD_ORDER 2 //占据4个页
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
.endm
/*
真正内核入口点:
* 上一节解压后即跳转到这里,要求:
* MMU = off, D-cache = off, I-cache = dont care,r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
* Seelinux/arch/arm/tools/mach-types 完整的机器码列表 for r1.
*/
.arm
__HEAD
ENTRY(stext)
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: )
#ifdefCONFIG_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=cupid判断是否是内核支持的cpu类型(即ARM核,这里为armv7)
{
arch/arm/kernel/head-common.S中定义:
__lookup_processor_type
从.long __proc_info_begin
.long __proc_info_end 获取所有支持列表
这里armv7则由arch/arm/mm/proc-v7.S文件生成列表
}
movs r10, r5 @ invalidprocessor (r5=0)?
THUMB( it eq ) @ force fixup-ablelong branch encoding
beq __error_p @ yes, error 'p' //不匹配就出错,执行不下去
#ifndefCONFIG_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, =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 //判断r2的合法性
#ifdefCONFIG_SMP_ON_UP
bl __fixup_smp //修正smp内核在单处理器上的运作
#endif
#ifdefCONFIG_ARM_PATCH_PHYS_VIRT
bl __fixup_pv_table
#endif
bl __create_page_tables //创建页表,比较复杂,不理解!
/*
* The following calls CPU specific code ina 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 CPUcontrol register value.
*/
ldr r13, =__mmap_switched //将mmu使能后的跳转地址放入LR,后面执行完后跳转到该函数执行
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 //使能mmu
ENDPROC(stext)
.ltorg
#ifndefCONFIG_XIP_KERNEL
2: .long .
.long PAGE_OFFSET
#endif
(2)arch/arm/kernel/head-common.S
/* Thefollowing fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is notposition independent.
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
adr r3, __mmap_switched_data //依次将__mmap_switched_data的连接地址加载
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID 保存id到processor_id变量里
str r1, [r5] @ Save machine type 保存机器码到__machine_arch_type变量里
str r2, [r6] @ Save atags pointer 保存参数列表到__atags_pointer变量
cmp r7, #0
bicne r4, r0, #CR_A @ Clear'A' bit
stmneia r7, {r0, r4} @ Save control register values
b start_kernel //跳转到C语言启动内核的代码处
ENDPROC(__mmap_switched)
.align 2
.type __mmap_switched_data, %object
__mmap_switched_data:
.long __data_loc @ r4
.long _sdata @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
#ifdefCONFIG_CPU_CP15
.long cr_alignment @ r7
#else
.long 0 @ r7
#endif
.long init_thread_union + THREAD_START_SP @ sp //start_kernel使用的内核堆栈,也可以认为是空闲任务0使用的内核堆栈8K
.size __mmap_switched_data, . - __mmap_switched_data
(3)init/main.c—start_kernel
该函数涉及对内核所需要的各种初始化。
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern const struct kernel_param __start___param[], __stop___param[];
/*
*Need to run as early as possible, to initialize the
*lockdep hash:
*/
lockdep_init();
smp_setup_processor_id();
debug_objects_early_init();
/*
*Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled= true;
/*
*Interrupts are still disabled. Do necessary setups, then
*enable them
*/
boot_cpu_init();
page_address_init();
//打印内核信息
pr_notice("%s", linux_banner);
//重要,见后面一节(4)详细说明
setup_arch(&command_line);
mm_init_owner(&init_mm, &init_task);
mm_init_cpumask(&init_mm);
//保存未操作过的命令行内容
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
//建立所有内存管理区链表(DMA,NORMAL,HIGHMEM 3种管理区)
build_all_zonelists(NULL, NULL);
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
//分析系统能够辨别的一些早期参数,以未做任何修改的boot_command_line为基础
parse_early_param();
//解析命令行各个参数,mem、rootfs等
parse_args("Booting kernel", static_command_line,__start___param,
__stop___param - __start___param,
-1, -1, &unknown_bootoption);
jump_label_init();
/*
*These use large bootmem allocations and must precede
*kmem_cache_init()
*/
setup_log_buf(0);
//顺序扫描进程链表并检查进程描述符的pid字段是可行但是相当低效,为了加速查找,引入了4个散列表。为每种hash表申请空间,并将内存虚拟基址分别存放在pid_hash数组中。
pidhash_init();
vfs_caches_init_early();
/* 将放在__start_ex_table到__stop_ex_table之间的*(_ex_table)区域中的structexception_table_entry型全局结构变量按照insn成员变量值从小到大排序。
*/
sort_main_extable();
trap_init();
//内存初始化(包括slab分配器初始化),释放前面标志为保留的所有页面,这个函数结束后就不能在使用alloc_bootmem,alloc_bootmem_low alloc_bootmem_pages等申请低端内存的函数申请内存。
mm_init();
/* 初始化每个处理器的可运行队列。同时通过init_idle(current,smp_processor_id());设置系统初始化进程即0号idle进程,当前的运行都可以算作是在idle这个进程,使用init_task(静态分配的第一个任务描述符)和init_thread_union(静态分配的第一个指向任务描述符的堆栈结构体)(init/init_task.c)
*/
sched_init();
//禁止内核抢占,因为这是调度还没准备好,直到cpu_idle为止
preempt_disable();
if(WARN(!irqs_disabled(), "Interrupts were enabled *very* early, fixingit\n"))
local_irq_disable();
idr_init_cache();
perf_event_init();
rcu_init();
tick_nohz_init();
radix_tree_init();
/*init some links before init_ISA_irqs() */
early_irq_init();
//初始化中断,调用的是平台中断接口machine_desc->init_irq();(hi3536_gic_init_irq)。
init_IRQ();
//初始化当前cpu的读、拷贝、更新数据结构全局变量per_cpu_rcu_data和per_cpu_rcu_bh_data
tick_init();
//初始化当前处理器的时间向量,打开TIMER_SOFTIRQ定时器软中断
init_timers();
//高精度定时器初始化
hrtimers_init();
//软中断初始化,打开TASKLET_SOFTIRQ和HI_SOFTIRQ tasklet所需要的软中断
softirq_init();
timekeeping_init();
//初始化体系结构硬件定时器
time_init();
//用来对系统剖析的,在系统调试的时候有用
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
//使能IRQ中断
local_irq_enable();
kmem_cache_init_late();
//初始化系统控制台结构,该函数执行后调用printk函数将log_buf中所有符合打印几倍的信息打印到控制台。
console_init();
if(panic_later)
panic(panic_later, panic_param);
lockdep_info();
/*
*Need to run this when irqs are enabled, because it wants
*to self-test [hard/soft]-irqs on/off lock inversion bugs
*too:
*/
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if(initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disablingit.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
page_cgroup_init();
debug_objects_mem_init();
kmemleak_init();
setup_per_cpu_pageset();
//cpu系统的BogMIPS数值,即处理器每秒执行的指令数
numa_policy_init();
if(late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
//为了循环使用PID编号,内容通过pidmap管理已经分配的pid和空闲PID
pidmap_init();
//为匿名逆序内存区域链表结构分配一个高速缓存内存
anon_vma_init();
#ifdef CONFIG_X86
if(efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
thread_info_cache_init(); //do nothing
cred_init();
//创建进程相关的初始化
fork_init(totalram_pages);
proc_caches_init();
buffer_init();
key_init();
security_init();
dbg_late_init();
vfs_caches_init(totalram_pages);
signals_init();
/*rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cgroup_init();
cpuset_init();
taskstats_init_early();
delayacct_init();
check_bugs();
//add by hlb
gs_proc_init();
acpi_early_init(); /* before LAPIC and SMP init */
sfi_init_late();
if(efi_enabled(EFI_RUNTIME_SERVICES)) {
efi_late_init();
efi_free_boot_services();
}
ftrace_init();
//重要,见后面一节(5)详细说明
rest_init();
}
(4)arch/arm/kernel/setup.c——setup_arch
void __init setup_arch(char **cmdline_p)
{
struct machine_desc *mdesc;
//获取arm处理相关信息
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);
//获取uboot传递进来的参数列表,解析各个tags,其中一个重要的是boot_command_line
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
//设置DMA内存管理区,当前没有用
setup_dma_zone(mdesc);
if(mdesc->restart_mode)
reboot_setup(&mdesc->restart_mode);
//初始化init_mm内存描述符,后面的值在vmlinux.lds中编译时确定
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsignedlong) _etext;
init_mm.end_data = (unsignedlong) _edata;
init_mm.brk = (unsigned long)_end;
//将cmd_line传递出去,后续解析使用
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
parse_early_param();
sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]),meminfo_cmp, NULL);
sanity_check_meminfo();
arm_memblock_init(&meminfo, mdesc);
//为系统内存创建页表,初始化内存
paging_init(mdesc);
request_standard_resources(mdesc);
….
}
(5)init/main.c——rest_init
该函数创建init内核进程(1号进程)和kthreadd内核进程(2号进程),原来的为0号系统启动进程进入空闲状态。
static noinline void __init_refokrest_init(void)
{
intpid;
rcu_scheduler_starting();
//创建1号init进程
kernel_thread(kernel_init, NULL, CLONE_FS| CLONE_SIGHAND);
numa_default_policy();
//创建2号kthreadd进程
pid= kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
//初始化当前任务到空闲进程
init_idle_bootup_task(current);
//禁止调度抢占
schedule_preempt_disabled();
//进入空闲内核
cpu_startup_entry(CPUHP_ONLINE);
}
kernel_init内核进程主要运行/sbin/init命令,完成内核态到用户态的切换,执行/etc下面的各种脚本,实现程序启动。