内核编译完成后会生成zImage内核镜像文件。关于bootloader加载zImage到内核,并且跳转到zImage开始地址运行zImage的过程,相信大家都很容易理解。但对于zImage是如何解压的过程,就不是那么好理解了。本文将结合部分关键代码,讲解zImage的解压过程。
先看看zImage的组成吧。在内核编译完成后会在arch/arm/boot/下生成zImage
在arch/arm/boot/Makefile中:
56 $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
57 $(call if_changed,objcopy)
58 @echo ' Kernel: $@ is ready'
由此可见,zImage的是elf格式的arch/arm/boot/compressed/vmlinux二进制化得到的
在arch/arm/boot/compressed/Makefile中:
104 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
105 $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE
106 $(call if_changed,ld)
107 @:
108
109 $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
110 $(call if_changed,$(suffix_y))
111
112 $(obj)/piggy.$(suffix_y).o: $(obj)/piggy.$(suffix_y) FORCE
其中Image是由内核顶层目录下的vmlinux二进制化后得到的。注意:arch/arm/boot/compressed/vmlinux是位置无关的,这个有助于理解后面的代码。链接选项中有个 -fpic参数:
79 EXTRA_CFLAGS := -fpic -fno-builtin
在说-fpic参数前先说一下位置无关代码,位置无关代码主要是在访问全局变量和全局函数的时候采用了位置无关的重定位方法,既依赖GOT和PLT来重定位.普通的重定位方法需要修改代码段,比如偏移地址0x100处需要重定位,loader就修改代码段的0x100处的内容,通过查找重定位信息得到具体的值.这种方法需要修改代码段的内容,对于动态连接库来说,其初衷是让多个进程共享代码段,若对其进行写操作,就回引起COW,从而失去共享.
-fPIC选项告诉编绎器使用GOT和PLT的方法重定位,这两个都是数据段,因此避免了COW,真正实现了共享.如果不用-fPIC,动态连接库依然可以使用,但其重定位方法为一般方法,必然会引起COW.但也无所谓,除了性能在COW时稍微受些影响,其他也没啥
总结一下zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码(head.o misc.o),组成的。
下面就要看内核的启动了,那么内核是从什么地方开始运行的呢?这个当然要看lds文件啦。zImage的生成经历了两次大的链接过程:一次是顶层vmlinux的生成,由arch/arm/kernel/vmlinux.lds(这个lds文件是由arch/arm/kernel/vmlinux.lds.S生成的)决定;另一次是arch/arm/boot/compressed/vmlinux的生成,是由arch/arm/boot/compressed/vmlinux.lds(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)决定。zImage的入口点应该由arch/arm/boot/compressed/vmlinux.lds决定。从中可以看出入口点为‘_start’,在分析lds文件前建议先看看前面的Linux下的lds链接脚本基础一文
10 OUTPUT_ARCH(arm)
11 ENTRY(_start)
12 SECTIONS
13 {
14 /DISCARD/ : {
15 *(.ARM.exidx*)
16 *(.ARM.extab*)
17 /*
18 * Discard any r/w data - this produces a link error if we have any,
19 * which is required for PIC decompression. Local data generates
20 * GOTOFF relocations, which prevents it being relocated independently
21 * of the text/got segments.
22 */
23 *(.data)
24 }
25
26 . = 0;
27 _text = .;
28
29 .text : {
30 _start = .;
31 *(.start)
。。。。。。。。。。。
69 }
在arch/arm/boot/compressed/head.S中找到入口点.。也就是说文件arch/arm/boot/compressed/head.S是linux内核启动过程执行的第一个文件。
启动zImage内核时,u-boot调用boot_zImage函数(前面部分略过,从boot_zImage函数讲起),该函数完成以下工作:
1. 设置内核由nand flash复制到sdram中的地址:0x30008000;
2. 调用copy_kernel_img 函数复制内核到sdram;
3. 设置Image magic number;
4. 设置传递给内核的参数地址为0x30001000;
5. 设置机器码为193;
6. 最后调用call_linux函数,将控制权彻底交给内核。
当完成了上述工作后,内核开始启动,zImage内核的入口程序为:arch/arm/boot/compressed/head.S
我们现在来分析一下这个文件
123 .section ".start", #alloc, #execinstr
124 /*
125 * sort out different calling conventions
126 */
127 .align
128 start:
129 .type start,#function //type指定start这个符号是函数类型
130 .rept 8 //重复8次 mov r0, r0,
131 mov r0, r0 //空操作,让前面所取指令得以执行。
132 .endr
133
134 b 1f //跳转
/*
魔数0x016f2818是在bootloader中用于判断zImage的存在,
而zImage的判别的magic number为0x016f2818,这个也是内核和bootloader约定好的。
*/
135 .word 0x016f2818 @ Magic numbers to help the loader
136 .word start @ absolute load/run zImage address
137 .word _edata @ zImage end address
//r1和r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。
138 1: mov r7, r1 @ save architecture ID
139 mov r8, r2 @ save atags pointer
140
/*
这也标志着u-boot将系统完全的交给了OS,bootloader生命终止。之后会读取
cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;
而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入
*/
141 #ifndef __ARM_ARCH_2__
142 /*
143 * Booting from Angel - need to enter SVC mode and disable
144 * FIQs/IRQs (numeric definitions from angel arm.h source).
145 * We only do this if we were in user mode on entry.
146 */
147 mrs r2, cpsr @ get current mode
148 tst r2, #3 @ not user?
149 bne not_angel
150 mov r0, #0x17 @ angel_SWIreason_EnterSVC//0x17是angel_SWIreason_EnterSVC半主机操作
151 ARM( swi 0x123456 ) @ angel_SWI_ARM //0x123456是arm指令集的半主机操作编号
152 THUMB( svc 0xab ) @ angel_SWI_THUMB
153 not_angel:
154 mrs r2, cpsr @ turn off interrupts to
155 orr r2, r2, #0xc0 @ prevent angel from runnin g
156 msr cpsr_c, r2 //这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
157 #else
158 teqp pc, #0x0c000003 @ turn off interrupts
159 #endif
160
161 /*
162 * Note that some cache flushing and other stuff may
163 * be needed here - is there an Angel SWI call for this?
164 */
165
166 /*
167 * some architecture specific code can be inserted
168 * by the linker here, but it should preserve r7, r8, and r 9.
169 */
170
/*
LC0表是链接文件arch/arm/boot/compressed/vmlinux.lds(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)的各段入口。
10 OUTPUT_ARCH(arm)
11 ENTRY(_start)
12 SECTIONS
13 {
14 /DISCARD/ : {
15 *(.ARM.exidx*)
16 *(.ARM.extab*)
17 /*
18 * Discard any r/w data - this produces a link error if we have any,
19 * which is required for PIC decompression. Local data generates
20 * GOTOFF relocations, which prevents it being relocated independently
21 * of the text/got segments.
22 */
23 *(.data)
24 }
25
26 . = 0;
27 _text = .;
28
29 .text : {
30 _start = .;
31 *(.start)
32 *(.text)
33 *(.text.*)
34 *(.fixup)
35 *(.gnu.warning)
36 *(.rodata)
37 *(.rodata.*)
38 *(.glue_7)
39 *(.glue_7t)
40 *(.piggydata)
41 . = ALIGN(4);
42 }
43
44 _etext = .;
45
46 /* Assume size of decompressed image is 4x the compressed image */
47 _image_size = (_etext - _text) * 4;
48
49 _got_start = .;
50 .got : { *(.got) }
51 _got_end = .;
52 .got.plt : { *(.got.plt) }
53 _edata = .;
54
55 . = ALIGN(4);
56 __bss_start = .;
57 .bss : { *(.bss) }
58 _end = .;
59
60 .stack (NOLOAD) : { *(.stack) }
61
62 .stab 0 : { *(.stab) }
63 .stabstr 0 : { *(.stabstr) }
64 .stab.excl 0 : { *(.stab.excl) }
65 .stab.exclstr 0 : { *(.stab.exclstr) }
66 .stab.index 0 : { *(.stab.index) }
67 .stab.indexstr 0 : { *(.stab.indexstr) }
68 .comment 0 : { *(.comment) }
69 }
展开如下表:
zImage在内存中的初始地址为0x30008000,那么这个地址是怎么来的?
查看2410的 datasheet ,发现内存映射的基址是0x3000 0000 ,那么0x30008000又是如何来的呢?
在内核文档 Document/arm/Booting 文件中有:
5. Calling the kernel image
---------------------------
Existing boot loaders: MANDATORY
New boot loaders: MANDATORY
There are two options for calling the kernel zImage. If the zImage
is stored in flash, and is linked correctly to be run from flash,
then it is legal for the boot loader to call the zImage in flash
directly.
The zImage may also be placed in system RAM (at any location) and
called there. Note that the kernel uses 16K of RAM below the image
to store page tables. The recommended placement is 32KiB into RAM.
看来在 image 下面用了32K(0x8000)的空间存放内核页表,
链接文件arch/arm/boot/compressed/vmlinux.lds中的连接地址都是位置无关的,即都是以0地址为偏移的。而此时内核已被bootloader搬移到了SDRAM中。链接地址应该加上这个偏移。
*/
171 .text
172 adr r0, LC0 //指令adr是基于PC的值来获取标号LC0的地址的,LC0在后面第315行定义,由于内核已被搬移,标号LC0的地址不是以地址0为偏移的,这中间就存在一个固定的地址偏移,在s3c2410中是0x30008000.
173 ldmia r0, {r1, r2, r3, r5, r6, r11, ip}
174 ldr sp, [r0, #28]
175 #ifdef CONFIG_AUTO_ZRELADDR
176 @ determine final kernel image address
177 and r4, pc, #0xf8000000
178 add r4, r4, #TEXT_OFFSET
179 #else
180 ldr r4, =zreladdr
/*zreladdr内核运行地址,相关定义如下:
arch/arm/boot/Makefile
24 ZRELADDR := $(zreladdr-y)
25 PARAMS_PHYS := $(params_phys-y)
arch/arm/mach-s3c2410/Makefile.boot
1 ifeq ($(CONFIG_PM_H1940),y)
2 zreladdr-y := 0x30108000
3 params_phys-y := 0x30100100
4 else
5 zreladdr-y := 0x30008000
6 params_phys-y := 0x30000100
7 endif
*/
181 #endif
182 subs r0, r0, r1 @ calculate the delta offse t
//这里获得当前运行地址与链接地址的偏移量,存入r0中为0x30008000.如果不需要重定位,即内核没有进行过搬移,就跳转。如果内核代码是存于NANDflash中的是需要搬移的。
183
184 @ if delta is zero, we are
185 beq not_relocated @ running at the address we
186 @ were linked at.
187
188 /*
189 * We're running at a different address. We need to fix
190 * up various pointers:
191 * r5 - zImage base address (_start)
192 * r6 - size of decompressed image
193 * r11 - GOT start
194 * ip - GOT end
195 */
196 add r5, r5, r0 //修改内核映像基地址此时r5=0x30008000
197 add r11, r11, r0 //修改got表的起始和结束位置
198 add ip, ip, r0
199
200 #ifndef CONFIG_ZBOOT_ROM
201 /*
202 * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
203 * we need to fix up pointers into the BSS region.
204 * r2 - BSS start
205 * r3 - BSS end
206 * sp - stack pointer
207 */
//S3C2410平台是需要一下调整的,将bss段以及堆栈的地址都进行调整
208 add r2, r2, r0
209 add r3, r3, r0
210 add sp, sp, r0
211
212 /*
213 * Relocate all entries in the GOT table.
214 *///修改GOT(全局偏移表)表。根据当前的运行地址,修正该表
215 1: ldr r1, [r11, #0] @ relocate entries in the G OT
216 add r1, r1, r0 @ table. This fixes up the
217 str r1, [r11], #4 @ C references.
218 cmp r11, ip
219 blo 1b
220 #else
221
222 /*
223 * Relocate entries in the GOT table. We only relocate
224 * the entries that are outside the (relocated) BSS region.
225 *///S3C2410平台不会进入该分支,只对got表中在bss段以外的符号进行重定位
226 1: ldr r1, [r11, #0] @ relocate entries in the G OT
227 cmp r1, r2 @ entry < bss_start ||
228 cmphs r3, r1 @ _end < entry
229 addlo r1, r1, r0 @ table. This fixes up the
230 str r1, [r11], #4 @ C references.
231 cmp r11, ip
232 blo 1b
233 #endif
234
//下面的代码,如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段
235 not_relocated: mov r0, #0
236 1: str r0, [r2], #4 //清BSS段,所有的arm程序都需要做这些的
237 str r0, [r2], #4
238 str r0, [r2], #4
239 str r0, [r2], #4
240 cmp r2, r3
241 blo 1b
242
243 /*
244 * The C runtime environment should now be setup
245 * sufficiently. Turn the cache on, set up some
246 * pointers, and start decompressing.
247 */
248 bl cache_on //打开cache
249
250 mov r1, sp @ malloc space above stack
251 add r2, sp, #0x10000 @ 64k max 分配一段解压函数需要的内存缓冲,参见下图。
252
253 /*
254 * Check to see if we will overwrite ourselves.
255 * r4 = final kernel address
256 * r5 = start of this image
257 * r6 = size of decompressed image
258 * r2 = end of malloc space (and therefore this image)
259 * We basically want:
260 * r4 >= r2 -> OK
261 * r4 + image length <= r5 -> OK
262 */
263 cmp r4, r2 //r4为内核执行地址,此时为0X30008000,r2此时为用户栈定,即解压函数所需内存缓冲的开始处,显然r4 < r2所以不会跳转。
264 bhs wont_overwrite
265 add r0, r4, r6
266 cmp r0, r5
//r5是内核映像的开始地址0X30008000,r6为内核映像大小,r4为解压后内核开始地址,此时为0X30008000,r5为解压前映存放的开始位置,此时也为0X30008000。下面的判断是,看解压后的内核是不是会覆盖未解压的映像。显然是覆盖的,所以是不会跳转的。注意:内核映像解压后不会超过解压前的4倍大小。
267 bls wont_overwrite
268
269 mov r5, r2 @ decompress after malloc space//此时r2为解压函数缓冲区的尾部地址。
270 mov r0, r5 //r0为映像解压后存放的起始地址,解压后的内核就紧接着存放在解压函数缓冲区的尾部。
271 mov r3, r7 //r7中存放的是architecture ID,对于SMDK2410这个值为193;
/*
解压函数是用C语言实现的在文件arch/arm/boot/compressed/misc.c中。
解压函数的是个参数是有r0~r3传入的。r0~r3的值在上面已初始化,再对照上面表格就很清楚了。
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
output_start:指解压后内核输出的起始位置,此时它的值参考上面的图表,紧接在解压缓冲区后;
free_mem_ptr_p:解压函数需要的内存缓冲开始地址;
free_mem_ptr_end_p:解压函数需要的内存缓冲结束地址,共64K;
arch_id :architecture ID,对于SMDK2410这个值为193;
*/
272 bl decompress_kernel
/*下图是head.S调用misc.c中的decompress_kernel刚解压完内核后,还没有进行重定位时的情况
先看看zImage的组成吧。在内核编译完成后会在arch/arm/boot/下生成zImage
在arch/arm/boot/Makefile中:
56 $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
57 $(call if_changed,objcopy)
58 @echo ' Kernel: $@ is ready'
由此可见,zImage的是elf格式的arch/arm/boot/compressed/vmlinux二进制化得到的
在arch/arm/boot/compressed/Makefile中:
104 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
105 $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) FORCE
106 $(call if_changed,ld)
107 @:
108
109 $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
110 $(call if_changed,$(suffix_y))
111
112 $(obj)/piggy.$(suffix_y).o: $(obj)/piggy.$(suffix_y) FORCE
其中Image是由内核顶层目录下的vmlinux二进制化后得到的。注意:arch/arm/boot/compressed/vmlinux是位置无关的,这个有助于理解后面的代码。链接选项中有个 -fpic参数:
79 EXTRA_CFLAGS := -fpic -fno-builtin
在说-fpic参数前先说一下位置无关代码,位置无关代码主要是在访问全局变量和全局函数的时候采用了位置无关的重定位方法,既依赖GOT和PLT来重定位.普通的重定位方法需要修改代码段,比如偏移地址0x100处需要重定位,loader就修改代码段的0x100处的内容,通过查找重定位信息得到具体的值.这种方法需要修改代码段的内容,对于动态连接库来说,其初衷是让多个进程共享代码段,若对其进行写操作,就回引起COW,从而失去共享.
-fPIC选项告诉编绎器使用GOT和PLT的方法重定位,这两个都是数据段,因此避免了COW,真正实现了共享.如果不用-fPIC,动态连接库依然可以使用,但其重定位方法为一般方法,必然会引起COW.但也无所谓,除了性能在COW时稍微受些影响,其他也没啥
总结一下zImage的组成,它是由一个压缩后的内核piggy.o,连接上一段初始化及解压功能的代码(head.o misc.o),组成的。
下面就要看内核的启动了,那么内核是从什么地方开始运行的呢?这个当然要看lds文件啦。zImage的生成经历了两次大的链接过程:一次是顶层vmlinux的生成,由arch/arm/kernel/vmlinux.lds(这个lds文件是由arch/arm/kernel/vmlinux.lds.S生成的)决定;另一次是arch/arm/boot/compressed/vmlinux的生成,是由arch/arm/boot/compressed/vmlinux.lds(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)决定。zImage的入口点应该由arch/arm/boot/compressed/vmlinux.lds决定。从中可以看出入口点为‘_start’,在分析lds文件前建议先看看前面的Linux下的lds链接脚本基础一文
10 OUTPUT_ARCH(arm)
11 ENTRY(_start)
12 SECTIONS
13 {
14 /DISCARD/ : {
15 *(.ARM.exidx*)
16 *(.ARM.extab*)
17 /*
18 * Discard any r/w data - this produces a link error if we have any,
19 * which is required for PIC decompression. Local data generates
20 * GOTOFF relocations, which prevents it being relocated independently
21 * of the text/got segments.
22 */
23 *(.data)
24 }
25
26 . = 0;
27 _text = .;
28
29 .text : {
30 _start = .;
31 *(.start)
。。。。。。。。。。。
69 }
在arch/arm/boot/compressed/head.S中找到入口点.。也就是说文件arch/arm/boot/compressed/head.S是linux内核启动过程执行的第一个文件。
启动zImage内核时,u-boot调用boot_zImage函数(前面部分略过,从boot_zImage函数讲起),该函数完成以下工作:
1. 设置内核由nand flash复制到sdram中的地址:0x30008000;
2. 调用copy_kernel_img 函数复制内核到sdram;
3. 设置Image magic number;
4. 设置传递给内核的参数地址为0x30001000;
5. 设置机器码为193;
6. 最后调用call_linux函数,将控制权彻底交给内核。
当完成了上述工作后,内核开始启动,zImage内核的入口程序为:arch/arm/boot/compressed/head.S
我们现在来分析一下这个文件
123 .section ".start", #alloc, #execinstr
124 /*
125 * sort out different calling conventions
126 */
127 .align
128 start:
129 .type start,#function //type指定start这个符号是函数类型
130 .rept 8 //重复8次 mov r0, r0,
131 mov r0, r0 //空操作,让前面所取指令得以执行。
132 .endr
133
134 b 1f //跳转
/*
魔数0x016f2818是在bootloader中用于判断zImage的存在,
而zImage的判别的magic number为0x016f2818,这个也是内核和bootloader约定好的。
*/
135 .word 0x016f2818 @ Magic numbers to help the loader
136 .word start @ absolute load/run zImage address
137 .word _edata @ zImage end address
//r1和r2中分别存放着由bootloader传递过来的architecture ID和指向标记列表的指针。
138 1: mov r7, r1 @ save architecture ID
139 mov r8, r2 @ save atags pointer
140
/*
这也标志着u-boot将系统完全的交给了OS,bootloader生命终止。之后会读取
cpsr并判断是否处理器处于supervisor模式——从u-boot进入kernel,系统已经处于SVC32模式;
而利用angel进入则处于user模式,还需要额外两条指令。之后是再次确认中断关闭,并完成cpsr写入
*/
141 #ifndef __ARM_ARCH_2__
142 /*
143 * Booting from Angel - need to enter SVC mode and disable
144 * FIQs/IRQs (numeric definitions from angel arm.h source).
145 * We only do this if we were in user mode on entry.
146 */
147 mrs r2, cpsr @ get current mode
148 tst r2, #3 @ not user?
149 bne not_angel
150 mov r0, #0x17 @ angel_SWIreason_EnterSVC//0x17是angel_SWIreason_EnterSVC半主机操作
151 ARM( swi 0x123456 ) @ angel_SWI_ARM //0x123456是arm指令集的半主机操作编号
152 THUMB( svc 0xab ) @ angel_SWI_THUMB
153 not_angel:
154 mrs r2, cpsr @ turn off interrupts to
155 orr r2, r2, #0xc0 @ prevent angel from runnin g
156 msr cpsr_c, r2 //这里将cpsr中I、F位分别置“1”,关闭IRQ和FIQ
157 #else
158 teqp pc, #0x0c000003 @ turn off interrupts
159 #endif
160
161 /*
162 * Note that some cache flushing and other stuff may
163 * be needed here - is there an Angel SWI call for this?
164 */
165
166 /*
167 * some architecture specific code can be inserted
168 * by the linker here, but it should preserve r7, r8, and r 9.
169 */
170
/*
LC0表是链接文件arch/arm/boot/compressed/vmlinux.lds(这个lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)的各段入口。
10 OUTPUT_ARCH(arm)
11 ENTRY(_start)
12 SECTIONS
13 {
14 /DISCARD/ : {
15 *(.ARM.exidx*)
16 *(.ARM.extab*)
17 /*
18 * Discard any r/w data - this produces a link error if we have any,
19 * which is required for PIC decompression. Local data generates
20 * GOTOFF relocations, which prevents it being relocated independently
21 * of the text/got segments.
22 */
23 *(.data)
24 }
25
26 . = 0;
27 _text = .;
28
29 .text : {
30 _start = .;
31 *(.start)
32 *(.text)
33 *(.text.*)
34 *(.fixup)
35 *(.gnu.warning)
36 *(.rodata)
37 *(.rodata.*)
38 *(.glue_7)
39 *(.glue_7t)
40 *(.piggydata)
41 . = ALIGN(4);
42 }
43
44 _etext = .;
45
46 /* Assume size of decompressed image is 4x the compressed image */
47 _image_size = (_etext - _text) * 4;
48
49 _got_start = .;
50 .got : { *(.got) }
51 _got_end = .;
52 .got.plt : { *(.got.plt) }
53 _edata = .;
54
55 . = ALIGN(4);
56 __bss_start = .;
57 .bss : { *(.bss) }
58 _end = .;
59
60 .stack (NOLOAD) : { *(.stack) }
61
62 .stab 0 : { *(.stab) }
63 .stabstr 0 : { *(.stabstr) }
64 .stab.excl 0 : { *(.stab.excl) }
65 .stab.exclstr 0 : { *(.stab.exclstr) }
66 .stab.index 0 : { *(.stab.index) }
67 .stab.indexstr 0 : { *(.stab.indexstr) }
68 .comment 0 : { *(.comment) }
69 }
展开如下表:
zImage在内存中的初始地址为0x30008000,那么这个地址是怎么来的?
查看2410的 datasheet ,发现内存映射的基址是0x3000 0000 ,那么0x30008000又是如何来的呢?
在内核文档 Document/arm/Booting 文件中有:
5. Calling the kernel image
---------------------------
Existing boot loaders: MANDATORY
New boot loaders: MANDATORY
There are two options for calling the kernel zImage. If the zImage
is stored in flash, and is linked correctly to be run from flash,
then it is legal for the boot loader to call the zImage in flash
directly.
The zImage may also be placed in system RAM (at any location) and
called there. Note that the kernel uses 16K of RAM below the image
to store page tables. The recommended placement is 32KiB into RAM.
看来在 image 下面用了32K(0x8000)的空间存放内核页表,
0x30008000就是2410的内核在 RAM 中的启动地址,这个地址就是这么来的。所以后面zreladdr(内核运行地址)及bootloader中都定义相关的宏来定位到这个地址
链接文件arch/arm/boot/compressed/vmlinux.lds中的连接地址都是位置无关的,即都是以0地址为偏移的。而此时内核已被bootloader搬移到了SDRAM中。链接地址应该加上这个偏移。
*/
171 .text
172 adr r0, LC0 //指令adr是基于PC的值来获取标号LC0的地址的,LC0在后面第315行定义,由于内核已被搬移,标号LC0的地址不是以地址0为偏移的,这中间就存在一个固定的地址偏移,在s3c2410中是0x30008000.
173 ldmia r0, {r1, r2, r3, r5, r6, r11, ip}
174 ldr sp, [r0, #28]
175 #ifdef CONFIG_AUTO_ZRELADDR
176 @ determine final kernel image address
177 and r4, pc, #0xf8000000
178 add r4, r4, #TEXT_OFFSET
179 #else
180 ldr r4, =zreladdr
/*zreladdr内核运行地址,相关定义如下:
arch/arm/boot/Makefile
24 ZRELADDR := $(zreladdr-y)
25 PARAMS_PHYS := $(params_phys-y)
arch/arm/mach-s3c2410/Makefile.boot
1 ifeq ($(CONFIG_PM_H1940),y)
2 zreladdr-y := 0x30108000
3 params_phys-y := 0x30100100
4 else
5 zreladdr-y := 0x30008000
6 params_phys-y := 0x30000100
7 endif
*/
181 #endif
182 subs r0, r0, r1 @ calculate the delta offse t
//这里获得当前运行地址与链接地址的偏移量,存入r0中为0x30008000.如果不需要重定位,即内核没有进行过搬移,就跳转。如果内核代码是存于NANDflash中的是需要搬移的。
183
184 @ if delta is zero, we are
185 beq not_relocated @ running at the address we
186 @ were linked at.
187
188 /*
189 * We're running at a different address. We need to fix
190 * up various pointers:
191 * r5 - zImage base address (_start)
192 * r6 - size of decompressed image
193 * r11 - GOT start
194 * ip - GOT end
195 */
196 add r5, r5, r0 //修改内核映像基地址此时r5=0x30008000
197 add r11, r11, r0 //修改got表的起始和结束位置
198 add ip, ip, r0
199
200 #ifndef CONFIG_ZBOOT_ROM
201 /*
202 * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
203 * we need to fix up pointers into the BSS region.
204 * r2 - BSS start
205 * r3 - BSS end
206 * sp - stack pointer
207 */
//S3C2410平台是需要一下调整的,将bss段以及堆栈的地址都进行调整
208 add r2, r2, r0
209 add r3, r3, r0
210 add sp, sp, r0
211
212 /*
213 * Relocate all entries in the GOT table.
214 *///修改GOT(全局偏移表)表。根据当前的运行地址,修正该表
215 1: ldr r1, [r11, #0] @ relocate entries in the G OT
216 add r1, r1, r0 @ table. This fixes up the
217 str r1, [r11], #4 @ C references.
218 cmp r11, ip
219 blo 1b
220 #else
221
222 /*
223 * Relocate entries in the GOT table. We only relocate
224 * the entries that are outside the (relocated) BSS region.
225 *///S3C2410平台不会进入该分支,只对got表中在bss段以外的符号进行重定位
226 1: ldr r1, [r11, #0] @ relocate entries in the G OT
227 cmp r1, r2 @ entry < bss_start ||
228 cmphs r3, r1 @ _end < entry
229 addlo r1, r1, r0 @ table. This fixes up the
230 str r1, [r11], #4 @ C references.
231 cmp r11, ip
232 blo 1b
233 #endif
234
//下面的代码,如果运行当前运行地址和链接地址相等,则不需进行重定位。直接清除bss段
235 not_relocated: mov r0, #0
236 1: str r0, [r2], #4 //清BSS段,所有的arm程序都需要做这些的
237 str r0, [r2], #4
238 str r0, [r2], #4
239 str r0, [r2], #4
240 cmp r2, r3
241 blo 1b
242
243 /*
244 * The C runtime environment should now be setup
245 * sufficiently. Turn the cache on, set up some
246 * pointers, and start decompressing.
247 */
248 bl cache_on //打开cache
249
250 mov r1, sp @ malloc space above stack
251 add r2, sp, #0x10000 @ 64k max 分配一段解压函数需要的内存缓冲,参见下图。
252
253 /*
254 * Check to see if we will overwrite ourselves.
255 * r4 = final kernel address
256 * r5 = start of this image
257 * r6 = size of decompressed image
258 * r2 = end of malloc space (and therefore this image)
259 * We basically want:
260 * r4 >= r2 -> OK
261 * r4 + image length <= r5 -> OK
262 */
263 cmp r4, r2 //r4为内核执行地址,此时为0X30008000,r2此时为用户栈定,即解压函数所需内存缓冲的开始处,显然r4 < r2所以不会跳转。
264 bhs wont_overwrite
265 add r0, r4, r6
266 cmp r0, r5
//r5是内核映像的开始地址0X30008000,r6为内核映像大小,r4为解压后内核开始地址,此时为0X30008000,r5为解压前映存放的开始位置,此时也为0X30008000。下面的判断是,看解压后的内核是不是会覆盖未解压的映像。显然是覆盖的,所以是不会跳转的。注意:内核映像解压后不会超过解压前的4倍大小。
267 bls wont_overwrite
268
269 mov r5, r2 @ decompress after malloc space//此时r2为解压函数缓冲区的尾部地址。
270 mov r0, r5 //r0为映像解压后存放的起始地址,解压后的内核就紧接着存放在解压函数缓冲区的尾部。
271 mov r3, r7 //r7中存放的是architecture ID,对于SMDK2410这个值为193;
/*
解压函数是用C语言实现的在文件arch/arm/boot/compressed/misc.c中。
解压函数的是个参数是有r0~r3传入的。r0~r3的值在上面已初始化,再对照上面表格就很清楚了。
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
output_start:指解压后内核输出的起始位置,此时它的值参考上面的图表,紧接在解压缓冲区后;
free_mem_ptr_p:解压函数需要的内存缓冲开始地址;
free_mem_ptr_end_p:解压函数需要的内存缓冲结束地址,共64K;
arch_id :architecture ID,对于SMDK2410这个值为193;
*/
272 bl decompress_kernel
/*下图是head.S调用misc.c中的decompress_kernel刚解压完内核后,还没有进行重定位时的情况