您的位置 首页 制造

Android arm linux kernel发动流程一

虽然这里的ArmLinuxkernel前面加上了Android,但实际上还是和普遍Armlinuxkernel启动的过程一样的,这里只是结合一下Android的Make…

尽管这儿的Arm Linux kernel前面加上了Android,但实际上仍是和遍及Arm linux kernel发动的进程相同的,这儿仅仅结合一下Android的Makefile,讲一下bootimage生成的一个进程。这篇文档首要描绘bootimage的结构,以及kernel真实履行前的解压进程。

在了解这些之前咱们首要需求了解几个名词,这些名词界说在/Documentation/arm/Porting里边,这儿首要说到其间的几个,其他几个会在后边kernel的履行进程中叙述:

1)ZTEXTADDR boot.img运转时分zImage的开端地址,即kernel解压代码的地址。这儿没有虚拟地址的概念,由于没有敞开MMU,所以这个地址是物理内存的地址。解压代码不一定需求载入RAM才干运转,在FLASH或许其他可寻址的媒体上都能够运转。

2)ZBSSADDR 解压代码的BSS段的地址,这儿也是物理地址。

3)ZRELADDR 这个是kernel解压今后寄存的内存物理地址,解压代码履行完结今后会跳到这个地址履行kernel的发动,这个地址和后边kernel运转时分的虚拟地址满意:__virt_to_phys(TEXTADDR) = ZRELADDR。

4)INITRD_PHYS Initial Ram Disk寄存在内存中的物理地址,这儿便是咱们的ramdisk.img。

5)INITRD_VIRT Initial Ram Disk运转时分虚拟地址。

6)PARAMS_PHYS 内核发动的初始化参数在内存上的物理地址。

下面咱们首要来看看boot.img的结构,了解其间的内容对咱们了解kernel的发动进程是很有协助的。首要来看看Makefile是怎么发生咱们的boot.img的:

out/host/linux-x86/bin/mkbootimg-msm7627_ffa –kernel out/target/product/msm7627_ffa/kernel –ramdisk out/target/product/msm7627_ffa/ramdisk.img –cmdline “mem=203M console=ttyMSM2,115200n8 androidboot.hardware=qcom” –output out/target/product/msm7627_ffa/boot.img

依据上面的指令咱们能够首要看看mkbootimg-msm7627ffa这个东西的源文件:system/core/mkbootimg.c。看完之后咱们就能很明晰地看到boot.img的内部结构,它是由boot header /kernel /ramdisk /second stage构成的,其间前3项是有必要的,最终一项是可选的。

view plaincopy to clipboardprint?
/*
+—————–+
| boot header | 1 page
+—————–+
| kernel | n pages
+—————–+
| ramdisk | m pages
+—————–+
| second stage | o pages
+—————–+

n = (kernel_size + page_size – 1) / page_size
m = (ramdisk_size + page_size – 1) / page_size
o = (second_size + page_size – 1) / page_size

0. all entities are page_size aligned in flash
1. kernel and ramdisk are required (size != 0)
2. second is optional (second_size == 0 -> no second)
3. load each element (kernel, ramdisk, second) at
the specified physical address (kernel_addr, etc)
4. prepare tags at tag_addr. kernel_args[] is
appended to the kernel commandline in the tags.
5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
6. if second_size != 0: jump to second_addr
else: jump to kernel_addr
*/
/*
+—————–+
| boot header | 1 page
+—————–+
| kernel | n pages
+—————–+
| ramdisk | m pages
+—————–+
| second stage | o pages
+—————–+

n = (kernel_size + page_size – 1) / page_size
m = (ramdisk_size + page_size – 1) / page_size
o = (second_size + page_size – 1) / page_size

0. all entities are page_size aligned in flash
1. kernel and ramdisk are required (size != 0)
2. second is optional (second_size == 0 -> no second)
3. load each element (kernel, ramdisk, second) at
the specified physical address (kernel_addr, etc)
4. prepare tags at tag_addr. kernel_args[] is
appended to the kernel commandline in the tags.
5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
6. if second_size != 0: jump to second_addr
else: jump to kernel_addr
*/

关于boot header这个数据结构咱们需求要点留意,在这儿咱们重视其间几个比较重要的值,这些值界说在boot/boardconfig.h里边,不同的芯片对应vendor下不同的boardconfig,在这儿咱们的值别离是(别离是kernel/ramdis/tags载入ram的物理地址):

view plaincopy to clipboardprint?
#define PHYSICAL_DRAM_BASE 0x00200000
#define KERNEL_ADDR (PHYSICAL_DRAM_BASE + 0x00008000)
#define RAMDISK_ADDR (PHYSICAL_DRAM_BASE + 0x01000000)
#define TAGS_ADDR (PHYSICAL_DRAM_BASE + 0x00000100)
#define NEWTAGS_ADDR (PHYSICAL_DRAM_BASE + 0x00004000)
#define PHYSICAL_DRAM_BASE 0x00200000
#define KERNEL_ADDR (PHYSICAL_DRAM_BASE + 0x00008000)
#define RAMDISK_ADDR (PHYSICAL_DRAM_BASE + 0x01000000)
#define TAGS_ADDR (PHYSICAL_DRAM_BASE + 0x00000100)
#define NEWTAGS_ADDR (PHYSICAL_DRAM_BASE + 0x00004000)

上面这些值别离和咱们开篇时分说到的那几个名词相对应,比方kernel_addr便是ZTEXTADDR,RAMDISK_ADDR便是INITRD_PHYS,而TAGS_ADDR便是PARAMS_PHYS。bootloader会从boot.img的分区中将kernel和ramdisk别离读入RAM上面界说的地址中,然后就会跳到ZTEXTADDR开端履行。

根本了解boot.img的内容之后咱们来别离看看里边的ramdisk.img和kernel又是怎么发生的,以及其包括的内容。从简略的说起,咱们先看看ramdisk.img,这儿首要要侧重一下这个ramdisk.img在arm linux中的效果。它在kernel发动进程中充当着第一阶段的文件体系,是一个CPIO格局打成的包。浅显上来讲他便是咱们将生成的root目录,用CPIO办法进行了打包,然后在kernel发动进程中会被mount作为文件体系,当kernel发动完结今后会履行init,然后将system.img再mount进来作为Android的文件体系。在这儿略微解说下这个mount的概念,所谓mount实际上便是告知linux虚拟文件体系它的根目录在哪,便是说我这个虚拟文件体系需求操作的那块区域在哪,比方说ramdisk实际上是咱们在内存中的一块区域,把它作为文件体系的意思实际上便是告知虚拟文件体系你的根目录就在我这儿,我的开端地址赋给你,你今后就能对我进行操作了。实际上咱们也能够运用rom上的一块区域作为根文件体系,可是rom相对ram慢,所以这儿运用ramdisk。然后咱们在把system.img mount到ramdisk的system目录,实际上便是将system.img的地址给了虚拟文件体系,然后虚拟文件体系拜访system目录的时分会从头定位到对system.img的拜访。咱们能够看看makefile是怎么生成它的:

out/host/linux-x86/bin/mkbootfs out/target/product/msm7627_ffa/root | out/host/linux-x86/bin/minigzip > out/target/product/msm7627_ffa/ramdisk.img

下面咱们来看看kernel发生的进程,老办法,从Makefile开端/arch/arm/boot/Makefile ~

view plaincopy to clipboardprint?
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
@echo Kernel: $@ is ready
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo Kernel: $@ is ready
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
@echo Kernel: $@ is ready
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@echo Kernel: $@ is ready

咱们分化地来看各个进程,第一个是将vmlinux经过objcopy后生成一个未经紧缩的raw binary(Image 4M左右),这儿的vmlinux是咱们编译链接今后生成的vmlinx,大约60多M。这儿略微说一下这个objcopy,在发动的时分ELF格局是无法履行的,ELF格局的解析是在kernel发动今后有了操作体系之后才干进行的。由于尽管咱们编出的img尽管被编成ELF格局,但要想发动起来有必要将其转化成原始的二进制格局,咱们能够多照着man objcopy和OBJCOPYFLAGS :=-O binary -R .note -R .note.gnu.build-id -R .comment -S(arch/arm/Makefile)来看看这些objcopy详细做了什么事情 ~

得到Image今后,再将这个Image跟解压代码组成一个vmlinux,详细的咱们能够看看arch/arm/boot/compressed/Makefile:

view plaincopy to clipboardprint?
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o /
$(addprefix $(obj)/, $(OBJS)) FORCE
$(call if_changed,ld)
@:
$(obj)/piggy.gz: $(obj)/../Image FORCE
$(call if_changed,gzip)
$(obj)/piggy.o: $(obj)/piggy.gz FORCE
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o /
$(addprefix $(obj)/, $(OBJS)) FORCE
$(call if_changed,ld)
@:
$(obj)/piggy.gz: $(obj)/../Image FORCE
$(call if_changed,gzip)
$(obj)/piggy.o: $(obj)/piggy.gz FORCE

从这儿咱们就能够看出来实际上这个vmlinux便是将Image紧缩今后依据vmlinux.lds与解压代码head.o和misc.o链接今后生成的一个elf,并且用readelf或许objdump能够很明显地看到解压代码是PIC的,一切的虚拟地址都是相对的,没有肯定地址。这儿的vmlinx.lds能够对照着后边的head.s略微看一下~得到紧缩今后的vmlinx今后再将这个vmlinx经过objcopy今后就得到咱们的zImage了,然后复制到out目录下便是咱们的kernel了~~

在这儿要侧重几个地址,这些地址界说在arch/arm/mach-msm/makefile.boot里边,被arch/arm/boot/Makefile调用,其间zreladdr-y便是咱们的kernel被解压今后要开释的地址了,解压代码跑完今后就会跳到这个地址来履行kernel的发动。不过这儿还有其他两个PHYS,跟前面界说在boardconfig.h里边的值重复了,不知道这两个值在这儿界说跟前面的值是一种什么关系???

好啦,讲到这儿咱们根本就知道boot.img的构成了,下面咱们就从解压的代码开端看看arm linux kernel发动的一个进程,这个解压的source便是/arch/arm/boot/compressed/head.S。要看懂这个汇编需求了解GNU ASM以及ARM汇编指令,ARM指令就不说了,ARM RVCT里边的文档有得下,至于GNU ASM,不需求音讯了解的话首要是看一下一些伪指令的意义(http://sources.redhat.com/binutils/docs-2.12/as.info/Pseudo-Ops.html#Pseudo%20Ops)

那么咱们现在就开端剖析这个解压的进程:

1)bootloader会传递2个参数过来,别离是r1=architecture ID, r2=atags pointer。head.S从哪部分开端履行呢,这个咱们能够看看vmlinx.lds:

view plaincopy to clipboardprint?
ENTRY(_start)
SECTIONS
{
. = 0;
_text = .;
.text : {
_start = .;
*(.start)
*(.text)
*(.text.*)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
*(.piggydata)
. = ALIGN(4);
}
ENTRY(_start)
SECTIONS
{
. = 0;
_text = .;
.text : {
_start = .;
*(.start)
*(.text)
*(.text.*)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
*(.piggydata)
. = ALIGN(4);
}

能够看到咱们最开端的section便是.start,所以咱们是从start段开端履行的。ELF对程序的进口地址是有界说的,这能够参照*.lds的语法规矩里边有描绘,别离是GNU LD的-E —> *.lds里边的ENTRY界说 —> start Symbol —> .text section —>0。在这儿是没有这些判别的,由于还没有操作体系,bootloader会直接跳到这个start的地址开端履行。

在这儿略微带一句,假如觉得head.S看的不太舒畅的话,比方有些跳转并不知道意思,能够直接objdump vmlinx来看,dump出来的汇编的流程就比较明晰了。

view plaincopy to clipboardprint?
1: mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
#ifndef __ARM_ARCH_2__
/*
* Booting from Angel – need to enter SVC mode and disable
* FIQs/IRQs (numeric definitions from angel arm.h source).
* We only do this if we were in user mode on entry.
*/
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel @ 假如不是
mov r0, #0x17 @ angel_SWIreason_EnterSVC
swi 0x123456 @ angel_SWI_ARM
not_angel:
mrs r2, cpsr @ turn off interrupts to
orr r2, r2, #0xc0 @ prevent angel from running
msr cpsr_c, r2
1: mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
#ifndef __ARM_ARCH_2__
/*
* Booting from Angel – need to enter SVC mode and disable
* FIQs/IRQs (numeric definitions from angel arm.h source).
* We only do this if we were in user mode on entry.
*/
mrs r2, cpsr @ get current mode
tst r2, #3 @ not user?
bne not_angel @ 假如不是
mov r0, #0x17 @ angel_SWIreason_EnterSVC
swi 0x123456 @ angel_SWI_ARM
not_angel:
mrs r2, cpsr @ turn off interrupts to
orr r2, r2, #0xc0 @ prevent angel from running
msr cpsr_c, r2

上面首要保存r1和r2的值,然后进入超级用户形式,并封闭中止。

view plaincopy to clipboardprint?
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
subs r0, r0, r1 @ calculate the delta offset
@ if delta is zero, we are
beq not_relocated @ running at the address we
@ were linked at.
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
subs r0, r0, r1 @ calculate the delta offset
@ if delta is zero, we are
beq not_relocated @ running at the address we
@ were linked at.

这儿首要判别LC0当时的运转地址和链接地址是否相同,假如相同就不需求重定位,假如不相同则需求进行重定位。这儿肯定是不相等的,由于咱们能够经过objdump看到LC0的地址是0x00000138,是一个相对地址,然后adr r0, LC0 实际上便是将LC0当时的运转地址,而咱们直接跳到ZTEXTADDR跑的,实际上PC里边现在的地址肯定是0x00208000今后的一个值,adr r0, LC0编译之后实际上为add r0, pc, #208,这个208便是LC0到.text段头部的偏移。

view plaincopy to clipboardprint?
add r5, r5, r0
add r6, r6, r0
add ip, ip, r0
add r5, r5, r0
add r6, r6, r0
add ip, ip, r0

然后便是重定位了,即都加上一个偏移,经过重定位今后就都是肯定地址了。

view plaincopy to clipboardprint?
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
/*
* The C runtime environment should now be setup
* sufficiently. Turn the cache on, set up some
* pointers, and start decompressing.
*/
bl cache_on
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
/*
* The C runtime environment should now be setup
* sufficiently. Turn the cache on, set up some
* pointers, and start decompressing.
*/
bl cache_on

重定位完结今后翻开cache,详细这个翻开cache的进程咱没细心研讨过,大致进程是先从C0里边读到processor ID,然后依据ID来进行cache_on。

view plaincopy to clipboardprint?
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max

解压的进程首要是在仓库之上请求一个空间

view plaincopy to clipboardprint?
/*
* Check to see if we will overwrite ourselves.
* r4 = final kernel address
* r5 = start of this image
* r2 = end of malloc space (and therefore this image)
* We basically want:
* r4 >= r2 -> OK
* r4 + image length <= r5 -> OK
*/
cmp r4, r2
bhs wont_overwrite
sub r3, sp, r5 @ > compressed kernel size
add r0, r4, r3, lsl #2 @ allow for 4x expansion
cmp r0, r5
bls wont_overwrite
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
add r0, r0, #127 + 128 @ alignment + stack
bic r0, r0, #127 @ align the kernel length
/*
* Check to see if we will overwrite ourselves.
* r4 = final kernel address
* r5 = start of this image
* r2 = end of malloc space (and therefore this image)
* We basically want:
* r4 >= r2 -> OK
* r4 + image length <= r5 -> OK
*/
cmp r4, r2
bhs wont_overwrite
sub r3, sp, r5 @ > compressed kernel size
add r0, r4, r3, lsl #2 @ allow for 4x expansion
cmp r0, r5
bls wont_overwrite
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
add r0, r0, #127 + 128 @ alignment + stack
bic r0, r0, #127 @ align the kernel length

这个进程是判别咱们解压出的vmlinx会不会掩盖本来的zImage,这儿的final kernel address便是解压后的kernel要寄存的地址,而start of this image则是zImage在内存中的地址。依据咱们前面的剖析,现在这两个地址是重复的,即都是0x00208000。相同r2是咱们请求的一段内存空间,由于他是在sp上请求的,而依据vmlinx.lds咱们知道stack实际上处与vmlinx的最上面,所以r4>=r2是不可能的,这儿首要核算zImage的巨细,然后判别r4+r3是不是比r5小,很明显r4和r5的值是相同的,所以这儿先将r2的值赋给r0,经kernel先解压到s请求的内存空间上面,详细的解压进程就不描绘了,界说在misc.c里边。(这儿我所说的上面是指内存地址的高地址,默许载入的时分从低地址往高地址写,所以从内存低地址开端运转,stack处于最终边,所以成说是最上面)

view plaincopy to clipboardprint?
* r0 = decompressed kernel length
* r1-r3 = unused
* r4 = kernel execution address
* r5 = decompressed kernel start
* r6 = processor ID
* r7 = architecture ID
* r8 = atags pointer
* r9-r14 = corrupted
*/
add r1, r5, r0 @ end of decompressed kernel
adr r2, reloc_start
ldr r3, LC1
add r3, r2, r3
: ldmia r2!, {r9 – r14} @ copy relocation code
stmia r1!, {r9 – r14}
ldmia r2!, {r9 – r14}
stmia r1!, {r9 – r14}
cmp r2, r3
blo 1b
add sp, r1, #128 @ relocate the stack
bl cache_clean_flush
add pc, r5, r0 @ call relocation code
* r0 = decompressed kernel length
* r1-r3 = unused
* r4 = kernel execution address
* r5 = decompressed kernel start
* r6 = processor ID
* r7 = architecture ID
* r8 = atags pointer
* r9-r14 = corrupted
*/
add r1, r5, r0 @ end of decompressed kernel
adr r2, reloc_start
ldr r3, LC1
add r3, r2, r3
1: ldmia r2!, {r9 – r14} @ copy relocation code
stmia r1!, {r9 – r14}
ldmia r2!, {r9 – r14}
stmia r1!, {r9 – r14}
cmp r2, r3
blo 1b
add sp, r1, #128 @ relocate the stack
bl cache_clean_flush
add pc, r5, r0 @ call relocation code

由于没有将kernel解压在要求的地址,所以有必要重定向,说穿了便是要将解压的kernel复制到正确的地址,由于正确的地址与zImage的地址是重合的,而要复制咱们又要履行zImage的重定位代码,所以这儿首要将重定位代码reloc_start复制到vmlinx上面,然后再将vmlinx复制到正确的地址并掩盖掉zImage。这儿首要核算出解压后的vmlinux的高地址放在r1里边,r2寄存侧重定位代码的首地址,r3寄存侧重定位代码的size,这样经过复制就将reloc_start移动到vmlinx后边去了,然后跳转到重定位代码开端履行。

view plaincopy to clipboardprint?
/*
* All code following this line is relocatable. It is relocated by
* the above code to the end of the decompressed kernel image and
* executed there. During this time, we have no stacks.
*
* r0 = decompressed kernel length
* r1-r3 = unused
* r4 = kernel execution address
* r5 = decompressed kernel start
* r6 = processor ID
* r7 = architecture ID
* r8 = atags pointer
* r9-r14 = corrupted
*/
.align 5
reloc_start: add r9, r5, r0
sub r9, r9, #128 @ do not copy the stack
debug_reloc_start
mov r1, r4
1:
.rept 4
ldmia r5!, {r0, r2, r3, r10 – r14} @ relocate kernel
stmia r1!, {r0, r2, r3, r10 – r14}
.endr
cmp r5, r9
blo 1b
add sp, r1, #128 @ relocate the stack
debug_reloc_end
call_kernel: bl cache_clean_flush
bl cache_off
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mov pc, r4 @ call kernel
/*
* All code following this line is relocatable. It is relocated by
* the above code to the end of the decompressed kernel image and
* executed there. During this time, we have no stacks.
*
* r0 = decompressed kernel length
* r1-r3 = unused
* r4 = kernel execution address
* r5 = decompressed kernel start
* r6 = processor ID
* r7 = architecture ID
* r8 = atags pointer
* r9-r14 = corrupted
*/
.align 5
reloc_start: add r9, r5, r0
sub r9, r9, #128 @ do not copy the stack
debug_reloc_start
mov r1, r4
1:
.rept 4
ldmia r5!, {r0, r2, r3, r10 – r14} @ relocate kernel
stmia r1!, {r0, r2, r3, r10 – r14}
.endr
cmp r5, r9
blo 1b
add sp, r1, #128 @ relocate the stack
debug_reloc_end
call_kernel: bl cache_clean_flush
bl cache_off
mov r0, #0 @ must be zero
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
mov pc, r4 @ call kernel

这儿便是将vmlinx复制到正确的地址了,复制到正确的方位今后,就将kernel的首地址赋给PC,然后就跳转到真实kernel发动的进程~~

最终咱们来总结一下一个根本的进程:

1)当bootloader要从分区中数据读到内存中来的时分,这儿触及最重要的两个地址,一个便是ZTEXTADDR还有一个是INITRD_PHYS。不管用什么办法来生成IMG都要让bootloader有办法知道这些参数,否则就不知道应该将数据从FLASH读入今后放在什么当地,下一步也不知道从哪个当地开端履行了;

2)bootloader将IMG载入RAM今后,并跳到zImage的地址开端解压的时分,这儿就触及到别的一个重要的参数,那便是ZRELADDR,便是解压后的kernel应该放在哪。这个参数一般都是arch/arm/mach-xxx下面的Makefile.boot来供给的;

3)别的现在解压的代码head.S和misc.c一般都会以PIC的办法来编译,这样载入RAM在任何当地都能够运转,这儿触及到两次冲定位的进程,根本上这个重定位的进程在ARM上都是差不多相同的。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/bandaoti/zhizao/276694.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部