可以结合《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下面的各种脚本,实现程序启动。

arm linux内核启动过程详解相关推荐

  1. Linux开启动过程详解

    Linux开启动过程详解 Linux启动过程 前言: Linux是一种自由和开放源代码的类UNIX操作系统.该操作系统的内核由林纳斯·托瓦兹在1991年10月5日首次发布.在加上用户空间的应用程序之后 ...

  2. arm linux内核启动过程,ARM64的启动过程之(一):内核第一个脚印

    ARM64的启动过程之(一):内核第一个脚印 作者:linuxer 发布于:2015-10-10 15:06 分类:ARMv8A Arch 一.前言 kernel的整个启动过程涉及的内容很多,不可能每 ...

  3. linux查询内核参数命令,Linux内核启动参数详解

    1.环境: Ubuntu 16.04 Linux linuxidc 4.4.0-89-generic #112-Ubuntu SMP Mon Jul 31 19:38:41 UTC 2017 x86_ ...

  4. linux efi 启动原理,Linux(RHEL6)启动过程详解

    Linux(RHEL6)启动过程详解 Linux(红帽RHEL6)启动过程详解: RHEL的一个重要和强大的方面是它是开源的,并且系统的启动过程是用户可配置的.用户可以自由的配置启动过程的许多方面,包 ...

  5. linux upstart脚本,Linux upstart启动方式详解

    Ubuntu从6.10开始逐步用Upstart()代替原来的SysVinit进行服务进程的管理.RHEL(CentOS)也都从版本6开始转用Upstart代替以往的init.d/rcX.d的线性启动方 ...

  6. 简述arm linux内核启动流程,Linux内核启动过程和Bootloader(总述)

    1.Linux内核启动过程概述 一个嵌入式 Linux 系统从软件角度看可以分为四个部分:引导加载程序(Bootloader),Linux 内核,文件系统,应用程序.其中 Bootloader是系统启 ...

  7. 嵌入式linux的u-boot系统启动过程,【站友投递】U-boot启动过程详解

    [站友投递]U-boot启动过程详解 来源:互联网 作者:denny 时间:2009-03-18 Tag:点击: 一.U-BOOT的目录结构 u-boot目录下有18个子目录,分别存放管理不通的源程序 ...

  8. 朱老师ARM裸机学习笔记(四):S5PV210启动过程详解

    常用器件特性 内存: SRAM 静态内存 特点就是容量小.价格高,优点是不需要软件初始化直接上电就能用 DRAM 动态内存 特点就是容量大.价格低,缺点就是上电后不能直接使用,需要软件初始化后才可以使 ...

  9. centos7 启动流程图_Linux启动过程详解

    Linux启动过程详解 作者:江远航 一.启动流程图如下 图1 Linux启动流程图 BIOS ---> MBR ---> Kernel---> Init 二.Linux启动顺序 一 ...

  10. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

最新文章

  1. mysql二进制包下的support-files文件夹
  2. [GO] go使用etcd和watch方法进行实时的配置变更
  3. maven 程序包不存在_有人说 Maven 很简单,我却被“伤害”过
  4. Centos7安装配置Xhgui
  5. 求职特训营火热来袭 阿里大咖教你制作专业简历
  6. html flex 的高度,html – 使flexbox行成为最短子元素的高度?
  7. 简单的busybox创建_用busybox构建一个最小根文件系统
  8. php 语法验证_在线PHP语法检查器/验证器
  9. centos7.x表安装iptables防火墙
  10. LabVIEW编程LabVIEW开发 控制雷赛运动控制器SMC604A例程与相关资料
  11. 计算机视觉目标检测算法综述
  12. Element组件--Upload文件/图片上传
  13. python常用library
  14. JVM:7种垃圾收集器
  15. 什么是启发式算法(heuristic algorithm)?
  16. 蓝牙耳机南卡和JBL哪款好用?半入耳耳机南卡和JBL详细对比评测
  17. 7-1 统计正数和负数的个数然后计算这些数的平均值 (15 分)-java
  18. 教您一招解决Word不能复制粘贴问题
  19. 【BZOJ】3956 Count 单调栈+ST表
  20. 《训练指南》中的“突击战”和分金币问题

热门文章

  1. 删除小于一定尺寸的模型
  2. 2018.09.18 循环终止
  3. Web系统Login拦截器
  4. Spring No mapping found for HTTP request with URI错误
  5. HD 2177(威佐夫博弈 入门)
  6. 在gfs2中关闭selinux
  7. 使用Visual Studio 2010 Team System中的架构师工具(设计与建模)
  8. Spring Cloud构建微服务架构(四)分布式配置中心(续)
  9. stack(栈)数据结构详解
  10. VB.NET与 sql数据库