先看看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中都定義相關的宏來定位到這個地址
1、初始狀態
鏈接文件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剛解壓完內核後,還沒有進行重定位時的情況
解壓完畢後,內核長度返回值存放在r0寄存器裏。在內核末尾空出128字節的棧空間,並且使其長度128字節對齊。*/
273
274 add r0, r0, #127 + 128 @ alignment + stack
275 bic r0, r0, #127 @ align the kernel length
276 /*
277 * r0 = 解壓後內核的長度
278 * r1-r3 = 沒使用
279 * r4 = 內核執行地址
280 * r5 = decompressed kernel start解壓後內核的起始地址,如上面初始化 mov r5, r2
281 * r7 = architecture ID 處理器ID
282 * r8 = atags pointer 標記列表地址
283 * r9-r12,r14 = corrupted
284 */
/*
上面只是將內核臨時解壓到了一個位置,下面還要將它重定位到0X30008000處。
標號reloc_start下面有一段重定位內核的程序。爲了在內核重定位的過程中不至於將這段用於
重定位的代碼給覆蓋了,就先將這段用於內核重定位的代碼搬到另一個地方,如下表。
*/
285 add r1, r5, r0 //r1就是解壓後內核代碼的結束位置,下面就是將這段重定位代碼搬移到r1地址處。
286 adr r2, reloc_start //重定位代碼起始地址
287 ldr r3, LC1 //用於內核重定位的代碼的長度
288 add r3, r2, r3 //重定位代碼的結束地址
289 1: ldmia r2!, {r9 - r12, r14} @ copy relocation code//將這段重定位代碼搬移到r1地址處,如上表。
290 stmia r1!, {r9 - r12, r14}
291 ldmia r2!, {r9 - r12, r14}
292 stmia r1!, {r9 - r12, r14}
293 cmp r2, r3
294 blo 1b
295 mov sp, r1
296 add sp, sp, #128 @ relocate the stack//改變堆棧指針位置。
297
298 bl cache_clean_flush //搬移完成後刷新cache,因爲代碼地址變化了不能讓cache再命中被內核覆蓋的老地址。
299 ARM( add pc, r5, r0 ) @ call relocation code//r0 + r5 就是被搬移後的內核重定位代碼的開始位置,reloc_start。下面將會講到。
300 THUMB( add r12, r5, r0 )
301 THUMB( mov pc, r12 ) @ call relocation code
302
303 /*
304 * We're not in danger of overwriting ourselves. Do this the simple way.
305 *
306 * r4 = kernel execution address
307 * r7 = architecture ID
308 */
//如果內核映像沒有被bootloader搬移過,上面程序就會跳到此處。
309 wont_overwrite: mov r0, r4
310 mov r3, r7
311 bl decompress_kernel
312 b call_kernel
313
314 .align 2
315 .type LC0, #object
//這個表與文件arch/arm/kernel/vmlinux.lds.S(這個lds文件是由arch/arm/boot/compressed/vmlinux.lds.in生成的)
316 LC0: .word LC0 @ r1
317 .word __bss_start @ r2
318 .word _end @ r3
319 .word _start @ r5
320 .word _image_size @ r6
321 .word _got_start @ r11
322 .word _got_end @ ip
323 .word user_stack_end @ sp
324 LC1: .word reloc_end - reloc_start
325 .size LC0, . - LC0
下面代碼是將解壓後的內核代碼重定位。過程見下圖
//下面代碼就是實現將解壓後的內核代碼搬到0X30008000處
546 /*
547 * All code following this line is relocatable. It is relocated by
548 * the above code to the end of the decompressed kernel image and
549 * executed there. During this time, we have no stacks.
550 *
551 * r0 = decompressed kernel length
552 * r1-r3 = unused
553 * r4 = kernel execution address
554 * r5 = decompressed kernel start
555 * r7 = architecture ID
556 * r8 = atags pointer
557 * r9-r12,r14 = corrupted
558 */
559 .align 5
560 reloc_start: add r9, r5, r0 // r0 + r5就是解壓後內核代碼的結束位置加128字節棧空間。
561 sub r9, r9, #128 @ do not copy the stack
562 debug_reloc_start
563 mov r1, r4 //r4爲內核執行地址,即爲0X30008000。
564 1:
565 .rept 4 //將解壓後的內核搬到r1處,即0X30008000處。
566 ldmia r5!, {r0, r2, r3, r10 - r12, r14} @ relocate kernel
567 stmia r1!, {r0, r2, r3, r10 - r12, r14}
568 .endr
569
570 cmp r5, r9
571 blo 1b
572 mov sp, r1
573 add sp, sp, #128 @ relocate the stack
574 debug_reloc_end
575
//清除並關閉cache,清零r0,將machine ID存入r1,atags指針存入r2,再跳入0x30008000執行真正的內核Image
576 call_kernel: bl cache_clean_flush
577 bl cache_off
578 mov r0, #0 @ must be zero
579 mov r1, r7 @ restore architecture numb er
580 mov r2, r8 @ restore atags pointer
581 mov pc, r4 @ call kernel
582
此時內核解壓已經完成。內核啓動要執行的第二個文件就是arch/arm/kernel/head.S文件。
總結一下head.S會做些什麼樣的工作:
1、對於各種Arm CPU的DEBUG輸出設定,通過定義宏來統一操作;
2、設置kernel開始和結束地址,保存architecture ID;
3、如果在ARM2以上的CPU中,用的是普通用戶模式,則升到超級用戶模式,然後關中斷
4、分析LC0結構delta offset,判斷是否需要重載內核地址(r0存入偏移量,判斷r0是否爲零)。
5、需要重載內核地址,將r0的偏移量加到BSS region和GOT table中的每一項。對於位置無關的代碼,程序是通過GOT表訪問全局數據目標的,也就是說GOT表中中記錄的是全局數據目標的絕對地址,所以其中的每一項也需要重載。
6、清空bss堆棧空間r2-r3l,建立C程序運行需要的緩存
7、這時r2是緩存的結束地址,r4是kernel的最後執行地址,r5是kernel境象文件的開始地址
8、用文件misc.c的函數decompress_kernel(),解壓內核於緩存結束的地方(r2地址之後)。
爲了更清楚的瞭解解壓的動態過程。我們用圖表的方式描述下代碼的搬運解壓過程。然後再針對中間的一些關鍵過程闡述。
zImage在內存中的初始地址爲0x30008000(這個地址由bootloader決定,位置不固定)u-boot會將zImage鏡像copy到sdram的0x30008000位置處。此時爲初始狀態,這裏稱爲狀態1。
1、初始狀態
|.text | 0x30008000開始,包含piggydata段(即壓縮的內核段)
|. got |
|. data |
|.bss |
|.stack | 4K大小
2、 head.S調用misc.c中的decompress_kernel剛解壓完內核後,內存中的各段位置如下,狀態2
.text | 0x30008000開始,包含piggydata段(即壓縮的內核段)
. got |
. data |
.bss |
.stack |4K大小
解壓函數所需緩衝區 | 64K大小
解壓後的內核代碼 | 小於4M
3、當如果head.S中有代碼搬運工作時,即出現overwrite時,內存中的各段位置如下,此時會將head.S中的部分代碼重定位,狀態3
.text |0x30008000開始,包含piggydata段(即壓縮的內核段)
. got |
. data |
.bss |
.stack | 4K大小
解壓函數所需緩衝區 | 64K大小
解壓後的內核代碼 | 小於4M
head.S中的部分重定位代碼代碼 | reloc_start至reloc_end
4、跳轉到重定位後的reloc_start處,由reloc_start至reloc_end的代碼複製解壓後的內核代碼到0x30008000處,並調用call_kernel跳轉到0x30008000處執行。
解壓後的內核 | 0x30008000開始
在通過head.S瞭解了動態過程後,我們可能會有幾個問題:
問題1:zImage是如何知道自己最後的運行地址是0x30008000的?
問題2:調用decompress_kernel函數時,其4個參數是什麼值及物理含義?
問題3:解壓函數是如何確定代碼中壓縮內核位置的?
先回答第1個問題
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)的空間存放內核頁表,
再來先看看TEXT_OFFSET(內核在RAM中的起始位置相對於RAM起始地址偏移。值爲0x00008000)它在
./arch/arm/Makefile中定義
118 textofs-y := 0x00008000
222 TEXT_OFFSET := $(textofs-y)
此處,定義了Image的偏移地址。
0x30008000就是2410的內核在 RAM 中的啓動地址,這個地址就是這麼來的。所以後面zreladdr(內核運行地址)及bootloader中都定義相關的宏來定位到這個地址
在arch/arm/boot/Makefile文件中 ZRELADDR := $(zreladdr-y)
arch/arm/boot/Makefile
24 ZRELADDR := $(zreladdr-y)
25 PARAMS_PHYS := $(params_phys-y)
在arch/arm/mach-s3c2410/Makefile.boot中zreladdr-y := 0x30008000這個就是zImage的運行地址了,
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
在arch/arm/boot/compressed/head.S中有
180 ldr r4, =zreladdr 內核就是用這種方式讓代碼知道最終運行的位置的
此處定義了內核的物理地址和u-boot傳遞參數的地址,也就是加載地址。此處要和u-boot一致。
接下來再回答第2個問題
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:解壓函數需要的內存緩衝開始地址;
ulg free_mem_ptr_end_p:解壓函數需要的內存緩衝結束地址,共64K;
arch_id :architecture ID,對於SMDK2410這個值爲193;
最後回答第3個問題
首先看看piggy.o是如何生成的,
打開arch/arm/boot/Makefile文件:
49 $(obj)/Image: vmlinux FORCE
50 $(call if_changed,objcopy)
51 @echo ' Kernel: $@ is ready'
56 $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
57 $(call if_changed,objcopy)
58 @echo ' Kernel: $@ is ready'
可以看出,zImage是vmlinux通過objcopy後生成的;打開arch/arm/boot/compressed/Makefie文件:
63 suffix_$(CONFIG_KERNEL_GZIP) = gzip
64 suffix_$(CONFIG_KERNEL_LZO) = lzo
65 suffix_$(CONFIG_KERNEL_LZMA) = lzma
67 targets := vmlinux vmlinux.lds \
68 piggy.$(suffix_y) piggy.$(suffix_y).o \
69 font.o font.c head.o misc.o $(OBJS)
./arch/arm/boot/Makefile:30:targets := Image zImage xipImage bootpImage uImage
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 @:
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
可以看出,當配置內核爲GZIP方式時,zImage的壓縮方式使用gzip。現在可以清楚的明白zImage和vmlinux的關係了,一句話總結一下:zImage就是vmlinux通過objcopy、gzip壓縮後,得到的內核,其頭部是由head.S misc.c組成的自解壓代碼。
而u-boot所一直追捧的uImage格式,只是在zImage的前面加上了40Byte的內核頭部信息,由於本文主要講解zImage,此處暫不對uImage進行過多的分析。這裏內核默認採用gzip方式,所以第112行也就是
piggy.gzip
$(obj)/piggy.gzip.o: $(obj)/piggy.gzip FORCE
piggy.gzip.o是由piggy.S生成的,咱們看看piggy.gzip.S的內容:
.section .piggydata,#alloc
.globl input_data
input_data:
.incbin "arch/arm/boot/compressed/piggy.gzip"
.globl input_data_end
input_data_end:
再看看misc.c中decompress_kernel函數吧,它將調用
do_decompress(input_data, input_data_end - input_data,
output_data, error);
解壓內核。發現什麼沒?這裏的input_data不正是piggy.S裏的input_data嗎?這個時候應該明白內核是怎樣確定piggy.gz在zImage中的位置了吧。
最後說明一點的是do_decompress()在arch/arm/boot/compressed/decompresse.c中定義,它將調用decompress,進而調用bunzip2()或unlzma()來獲取壓縮內核代碼。具體解壓過程以後有時間再分析。
unsigned long
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
{
unsigned char *tmp;
output_data = (unsigned char *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
tmp = (unsigned char *) (((unsigned long)input_data_end) - 4);
output_ptr = get_unaligned_le32(tmp);
putstr("Uncompressing Linux...");
do_decompress(input_data, input_data_end - input_data,
output_data, error);
putstr(" done, booting the kernel.\n");
return output_ptr;
}
decompress_kernel()先調用arch_decomp_setup()進行設置,初始化,實現的功能是檢測CPU型號、使能看門狗(如果在配置內核時配置的前提下)和串口。然後使用在打印出信息“Uncompressing Linux...”後,之後調用do_decompress函數進行解壓,do_decompress函數是在arch/arm/boot/compressed/Decompress.c文件中的。將內核放於指定的位置。最後打印出信息" done, booting the kernel." do_decompress函數其代碼如下。
#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif
#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif
#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif
void do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{
decompress(input, len, NULL, NULL, output, NULL, error);
}
當配置內核時選中GZIP方式,就會#include “../../../../lib/decompress_inflate.c”。文件最後一行爲:
#define decompress gunzip
即:最後調用 gunzip函數對zImage進行解壓。
當完成所有解壓任務後,又將跳轉回head.S文件中,執行call_kernel,將啓動真正的Image.
arch/arm/plat-samsung/include/plat/uncompress.h
typedef unsigned int upf_t; /* cannot include linux/serial_core.h */
/* uart setup */
static unsigned int fifo_mask;
static unsigned int fifo_max;
/* forward declerations */
static void arch_detect_cpu(void);
/* defines for UART registers */
#include <plat/regs-serial.h>
#include <plat/regs-watchdog.h>
/* working in physical space... */
#undef S3C2410_WDOGREG
#define S3C2410_WDOGREG(x) ((S3C24XX_PA_WATCHDOG + (x)))
/* how many bytes we allow into the FIFO at a time in FIFO mode */
#define FIFO_MAX (14)
#define uart_base S3C_PA_UART + (S3C_UART_OFFSET * CONFIG_S3C_LOWLEVEL_UART_PORT)
static __inline__ void
uart_wr(unsigned int reg, unsigned int val)
{
volatile unsigned int *ptr;
ptr = (volatile unsigned int *)(reg + uart_base);
*ptr = val;
}
static __inline__ unsigned int
uart_rd(unsigned int reg)
{
volatile unsigned int *ptr;
ptr = (volatile unsigned int *)(reg + uart_base);
return *ptr;
}
/* we can deal with the case the UARTs are being run
* in FIFO mode, so that we don't hold up our execution
* waiting for tx to happen...
*/
static void putc(int ch)
{
if (uart_rd(S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE) {
int level;
while (1) {
level = uart_rd(S3C2410_UFSTAT);
level &= fifo_mask;
if (level < fifo_max)
break;
}
} else {
/* not using fifos */
while ((uart_rd(S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE) != S3C2410_UTRSTAT_TXE)
barrier();
}
/* write byte to transmission register */
uart_wr(S3C2410_UTXH, ch);
}
static inline void flush(void)
{
}
#define __raw_writel(d, ad) \
do { \
*((volatile unsigned int __force *)(ad)) = (d); \
} while (0)
/* CONFIG_S3C_BOOT_WATCHDOG
*
* Simple boot-time watchdog setup, to reboot the system if there is
* any problem with the boot process
*/
#ifdef CONFIG_S3C_BOOT_WATCHDOG
#define WDOG_COUNT (0xff00)
static inline void arch_decomp_wdog(void)
{
__raw_writel(WDOG_COUNT, S3C2410_WTCNT);
}
static void arch_decomp_wdog_start(void)
{
__raw_writel(WDOG_COUNT, S3C2410_WTDAT);
__raw_writel(WDOG_COUNT, S3C2410_WTCNT);
__raw_writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128 | S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x80), S3C2410_WTCON);
}
#else
#define arch_decomp_wdog_start()
#define arch_decomp_wdog()
#endif
#ifdef CONFIG_S3C_BOOT_ERROR_RESET
static void arch_decomp_error(const char *x)
{
putstr("\n\n");
putstr(x);
putstr("\n\n -- System resetting\n");
__raw_writel(0x4000, S3C2410_WTDAT);
__raw_writel(0x4000, S3C2410_WTCNT);
__raw_writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128 | S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x40), S3C2410_WTCON);
while(1);
}
#define arch_error arch_decomp_error
#endif
#ifdef CONFIG_S3C_BOOT_UART_FORCE_FIFO
static inline void arch_enable_uart_fifo(void)
{
u32 fifocon = uart_rd(S3C2410_UFCON);
if (!(fifocon & S3C2410_UFCON_FIFOMODE)) {
fifocon |= S3C2410_UFCON_RESETBOTH;
uart_wr(S3C2410_UFCON, fifocon);
/* wait for fifo reset to complete */
while (1) {
fifocon = uart_rd(S3C2410_UFCON);
if (!(fifocon & S3C2410_UFCON_RESETBOTH))
break;
}
}
}
#else
#define arch_enable_uart_fifo() do { } while(0)
#endif
static void
arch_decomp_setup(void)
{
/* we may need to setup the uart(s) here if we are not running
* on an BAST... the BAST will have left the uarts configured
* after calling linux.
*/
arch_detect_cpu();
arch_decomp_wdog_start();
/* Enable the UART FIFOs if they where not enabled and our
* configuration says we should turn them on.
*/
arch_enable_uart_fifo();
}
arch/arm/mach-s3c2410/include/mach/uncompress.h
static inline int is_arm926(void)
{
unsigned int cpuid;
asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (cpuid));
return ((cpuid & 0xff0) == 0x260);
}
static void arch_detect_cpu(void)
{
unsigned int cpuid;
cpuid = *((volatile unsigned int *)S3C2410_GSTATUS1);
cpuid &= S3C2410_GSTATUS1_IDMASK;
if (is_arm926() || cpuid == S3C2410_GSTATUS1_2440 ||
cpuid == S3C2410_GSTATUS1_2442 ||
cpuid == S3C2410_GSTATUS1_2416 ||
cpuid == S3C2410_GSTATUS1_2450) {
fifo_mask = S3C2440_UFSTAT_TXMASK;
fifo_max = 63 << S3C2440_UFSTAT_TXSHIFT;
} else {
fifo_mask = S3C2410_UFSTAT_TXMASK;
fifo_max = 15 << S3C2410_UFSTAT_TXSHIFT;
}
}