昨天下載了linux-2.6.26的kernel source
4 s U5 ]) _. E! |# Y4 g
打算trace一下 (以
ARM為例子)
% ]6 h3 H9 J. E看看能不能多了解一下kernel booting時候的一些動作
' t4 Z! V" r( f) ^& f0 H+ f1 z一些文章提到是從/arch/arm/boot/bootp/init.S開始
; u3 \* ?# O. u9 W" S所以節錄了一些下來
& F7 Y, \4 y* p- J V( W
6 h" f; t3 ^% ~$ R1 _) J 19 .section .start,#alloc,#execinstr
& N* n1 X( b2 n6 u6 ^) k 20 .type _start, #function
: S5 b* J6 z3 x
21 .globl _start
* N: I8 l5 | M7 M& o9 p 22
" V* V6 m, G3 C/ O 23 _start: add lr, pc, #-0x8 @ lr = current load addr
# S2 X% F9 I* X1 [ 24 adr r13, data
- F* m2 W6 ~% _" D
25 ldmia r13!, {r4-r6} @ r5 = dest, r6 = length
. Z+ _" @0 d; G8 ]) _ E/ B, Y
26 add r4, r4, lr @ r4 = initrd_start + load addr
Y' L2 x' E" A0 { 27 bl move @ move the initrd
6 ^: h) M7 P4 p; ?1 U S. m
.....
0 W, | b H& `0 k& Y
76 .type data,#object
0 {+ N- H) t/ M1 T 77 data: .word initrd_start @ source initrd address
3 S5 v8 J+ w: ~+ o5 Y: @ 78 .word initrd_phys @ destination initrd address
" y: m, O" X$ M* y
79 .word initrd_size @ initrd size
" s S5 A* D, L% T4 s ^
80
+ j- M, J2 H: r+ `, l; e0 C5 C, N9 c r4 ? K
line 19,宣告了叫做.start的section
6 \3 n) ~! r% Y/ D5 L
line 20,21宣告了一個叫做_start的function
. B) F2 ?! A4 ~1 G$ E# ~* j程式碼似乎從line 23開始
% f# Y G; X- c8 P/ L- Kline 23, 『add lr, pc, #-0x8』
4 _7 M) V5 S& D" Z2 Dadd就是將pc+(-0x8)的結果放到lr之中,pc和lr都是ARM裡頭暫存器
4 t/ Q' {5 r1 l
pc就是program counter,CPU用來指著目前要執行的指令,執行完CPU就會自動把PC+1
! a% G0 y/ u% k* Z h: c, ?這樣就會拿到下一道指令,lr通常是第14個register, r14,常被用來繼續function
: @$ |0 x- E3 o1 {: _* Qreturn時候的返回位置。奇怪的是為什麼要-0x8??
2 }; Q+ M% y1 {) j8 M
原因應該是ARM本身有pipeline的設計,prefetch->decode->execution,當指令被執行
7 o" v1 m" Y2 J5 d的時候,其實已經預先去偷偷抓下一道,所以PC值不是真的指在目前執行的地方,三級
0 a* B! n1 G. z9 M
pipeline剛好多了兩個cycle所以4 bytes x 2必須要-8才是正在執行指令的位址。
. w: r. I" z M9 x; M
5 ~5 c" `! r6 l* H& sline 24, 『adr r13, data』
! [& ]" Z: \" h# z4 Madr會去讀取data所在的位址當作值,寫道r13裡頭。r13通常是用來放stack pointer,
8 \# O! w! `% r- i3 n! e, y
常縮寫成sp,所以現在r13就指到data所在的位置上去。
. }2 ?6 v9 `6 E: _8 J6 e1 a n6 [
* E: i6 O7 C y' V4 S* M" Hline 25, 『ldmia r13!, {r4-r6}』
# C2 S9 _1 d3 l( k" A+ I& uldmia, load multiple increment after,顧名思義就是可以做很多次load的動作,每此
: M9 B y" |# X( w6 M0 Kload完就把位址+1,執行完之後
! H; N( M* d# `' F9 F! Q# |) qr4 = initrd_start
3 x1 P% V6 S4 U: I( `; Vr5 = initrd_phys
y8 |( ] d, r3 M# p7 J, [* R* gr6 = initrd_size
6 ~8 Q& D1 x% O1 R
4 G I* x2 `' W' t0 K6 I0 B
line 26, 『add r4, r4, lr』
~/ n; a8 c( t! K+ [
r4 = r4+lr, lr是剛剛我們算過的,指到一開始執行指令的地方,看程式碼的解釋,被當
. B5 b( Q/ R. a2 [' N* _1 k b
成載入的位址,所以執行完 r4 = initrd_start+程式被載入的位置,用途不明,看看之
- ]5 i$ H3 Z3 U( {
後有沒有被用到。
c3 k% B+ r# J- S! n+ M, d) o! {0 E7 F/ ^2 I, J2 w
line 27, 『bl move』
$ I4 r3 L6 c0 `$ I
bl, b是branch,相當於C語言goto的動作,l就是goto之前,先把目前位置存放到lr裡面,
3 _' Q! u1 E7 X9 N( ^所以bl除了goto之外,也改寫了lr,應該是有利於等一下返回的時候,可以用lr的值。
上面的 code 裡面出現了一些還沒定義的symbol
9 y3 {9 S: _8 V- W, g& @; j' q
例如 initrd_start, initrd_size等等
K, D, c2 A, e7 G6 {3 Y9 B
其實是被定義在另外一個檔 ./arch/arm/boot/bootp/initrd.S
7 D) r% Q7 d/ i# v0 P! X
- }0 c& ?$ U, \- p6 B/ k( V 1 .type initrd_start,#object
# o. {! @& T( W6 B) o3 e+ a' H
2 .globl initrd_start
3 T0 e" t8 x( Q: k( H g, V
3 initrd_start:
% a0 b) R( }: G 4 .incbin INITRD
& T) g: p& o- k K( L' @2 O6 } 5 .globl initrd_end
0 i( y4 w9 R4 j+ v- \1 b0 g* ]' a# Y 6 initrd_end:
" y i. Z9 t5 X% e! O
4 p7 q9 k/ P2 o, M( Qline 2, 3, 5, 6定義了兩個symbol initrd_start和initrd_end
+ }8 T, L9 R a, K* c; ]
中間還用.incbin INITRD 將 ramdisk 的 image include進來
( L z9 p. r7 A% p7 Q
這邊有點複雜
. @2 Z7 L5 }( c7 x假如compiler kernel的時候
: k/ ?$ [' Q+ I' L# O2 X) E
有選擇使用ramdisk當成boot device的話
/ L/ P7 `( X9 |& p* C2 J
會對從環境變數去設置 INITRD
- r1 p: w! ], ]1 \" G, L, f+ [- @: `0 P
這個檔名會被帶入到MAKEFILE
% m) p+ y, T3 X" D% W7 m
並且在做assembler動作的丟進來
, r6 t" n# d6 J" d7 Y3 H9 x假如沒有使用initrd
1 w6 E! `3 z& n8 j6 v, i' s那就.incbin應該就包不到東西
5 e) j& P! t( ]/ j: |initrd_start 和 initrd_end 就會相等
7 K i, k. g, N& g% K: Y7 `8 Z
. h# j0 C0 _% u& ]+ J, ?另外有個 script ./arch/arm/boot/bootp/bootp.lds
" g. \. `8 L+ _5 _$ h8 e9 S- \
它規範了所有link起來的object code裡面的section要怎麼編排
1 \3 ^( f+ x5 O" e: b8 F, j! I4 O
這樣撰寫kernel的時候
+ \. `7 D# n. M/ G: K. B5 M2 x" D
可以到特定的section取得想要的資料或是計算某個section的大小
! s B8 S, d2 g( r* W5 m E/ v( H: x2 `: {$ g
10 OUTPUT_ARCH(arm)
$ q5 { y) ^2 l0 s3 v 11 ENTRY(_start)
; f% n+ E9 t- N! w 12 SECTIONS
7 a0 }* f) v/ y
13 {
4 t* A* V m: Q2 I( I
14 . = 0;
5 @7 f2 G5 H0 {6 D
15 .text : {
$ H t* [# `) g" ?! P. L, V
16 _stext = .;
% g2 |& g- v% w, V1 T$ Y4 Y
17 *(.start)
. k8 A a3 V2 ^, ^( m8 } 18 *(.text)
, T6 U' x9 _. d
19 initrd_size = initrd_end - initrd_start;
& g% q0 G9 u! F2 g+ [
20 _etext = .;
" ], V1 n# T# u) @+ J 21 }
0 s, f, \! E0 _' _) L- o9 l9 O
22
+ k) D, Z. J8 W
23 .stab 0 : { *(.stab) }
! |4 B' C) P. T8 s/ p. Y 24 .stabstr 0 : { *(.stabstr) }
% g, D& f( I: @+ e! n 25 .stab.excl 0 : { *(.stab.excl) }
- R% C# t( e1 k0 U U- y 26 .stab.exclstr 0 : { *(.stab.exclstr) }
: f: f# I, f' o5 z
27 .stab.index 0 : { *(.stab.index) }
8 S* I- j6 P- C6 q1 @# k
28 .stab.indexstr 0 : { *(.stab.indexstr) }
+ p7 X$ u- q5 [6 n1 J3 O# C
29 .comment 0 : { *(.comment) }
" l& \# B0 g3 s 30 }
* k$ P: z1 L/ U4 S; Q# k# U, g5 ^+ y; O( L5 U: t% b5 b( l
對於object file的格式不熟悉的話
0 k4 Y3 V5 K2 B/ L q可以參考ELF format的相關文件
接著繼續看 init.S
. B: M& [/ ~+ j( n之前的code已經goto到move這邊來
: ~ t$ X* \, ]- E所以貼一些move的程式碼
# B7 r( t, @( g! q
. O2 j) ?0 n7 K s) ?. w 66 move: ldmia r4!, {r7 - r10} @ move 32-bytes at a time
6 Y0 R, q) y5 T- g" e 67 stmia r5!, {r7 - r10}
8 h1 U4 s# ]' n, ]
68 ldmia r4!, {r7 - r10}
8 o7 p0 J! ?. c8 Y+ g( g
69 stmia r5!, {r7 - r10}
' J( O# m( K6 G/ m S 70 subs r6, r6, #8 * 4
9 _& `! q* y$ l% E/ J 71 bcs move
4 a" y0 @' ?. E% Q 72 mov pc, lr
( H9 z) y! N$ h N: Q& \
, C+ d+ }$ r0 e4 k+ qline 66, 將r4所指到的位置,分別將值讀出來放到r7, r8, r9, r10, 可以發現剛剛計算過的r4這邊被
' N% d! t& s# O Q3 d/ o) L用到了,但是為什麼r4不是用initrd_start,卻還要加上load addr??
/ x6 K% _" _1 z3 q原因應該是bootp.lds的14行『. = 0;』表示最後被link好的address會從0x0開始
! A$ A9 N8 M+ ?
所以 initrd_start 所記錄的位置可以當成是offset
' g$ [( u" U& P" P2 L- a$ b) q& K) o, E加上load到DRAM或是擺在flash上的位址後
( u& N5 N# P n' p
就剛好是initrd所在的地方
1 g; d5 J( E; o5 P+ d8 g9 q. T$ Z3 l' k7 P& h3 R
line 67, 『stmia r5!, {r7 - r10}』
( e( ~; u1 n+ ]1 c) D+ S" t
stmia, store multiple increment after, 和ldmia動作相同,只是用來寫資料。
0 [. l' p# V" i# Br5是存放著initrd要擺放的位置
: T; h6 [. c+ c
猜測應該是為了一開始image放在flash上,但是可以將initrd拷貝到DRAM上
- s# e- W. [! Xr7寫到r5指到的位置
1 l+ C9 |: {( n+ j2 a' j6 i O! }r8->r5+1
" Q. e( m& X, {6 r: T5 ur9->r5+2
8 c& o; I: i) Rr10->r5+3
. b1 X" l8 q b4 E" s
所以我們發現,66,67行就是將r4所指的東西搬到r5。
4 s. ?* [% U( ^) S; L$ K
( F) h0 n5 Q/ e5 t: f) Jline 68, 69也是一樣copy了4x4bytes,一共是32bytes。
4 E7 {- S6 ~( W# W& I
line 70,『subs r6, r6, #8 * 4』,將length - 32bytes
5 j; F& U* e; _) Y
line 71,『bcs move』,b是branch的意思,cs是表示condition的條件,要是條件符合的話,
5 Y/ B q8 m4 k* @: [
就做branch的動作,這邊的用意是判斷前一個length是不是已經到0,如果不為零就繼續copy。
" g# x0 s6 f, b/ L, pline 72,『mov pc, lr』
- y" _+ ?0 l/ W2 [* o接著就把剛剛bl指令預先存放好的lr 填入pc,這樣CPU就會跳回去原本的return address。
G9 @* J5 v7 e8 n( V5 T c+ E+ f* a3 B6 o/ p4 M8 ^" g K
以上的動作,慢慢看得出來有在做些什麼事
' l/ B% {$ F0 R$ n2 o1. 找出initrd的所在位置
' _# ^3 c% T* |9 k
2. 將它copy到一個指定的destination去
程式返回之後
5 e" ~; ?5 p3 b9 F5 @我們接著看下一行
複製內容到剪貼板
代碼:
33 ldmia r13, {r5-r9} @ get size and addr of initrd
34 @ r5 = ATAG_CORE
35 @ r6 = ATAG_INITRD2
36 @ r7 = initrd start
37 @ r8 = initrd end
38 @ r9 = param_struct address
39
40 ldr r10, [r9, #4] @ get first tag
41 teq r10, r5 @ is it ATAG_CORE?
line 33, 繼續從r13的地方取出資料到r5, r6, r7 ,r8, r9,註解的說明有提到各個資料
. x. D6 d- f7 ]) t1 B0 u4 A1 `& }的意義,注意一下這邊的r7是initrd的destination address不是source address。
6 M4 a4 k1 V0 a- _7 T6 `" w
* M5 e3 H+ i+ x" N$ i* ^, @line 40, 讀入第一個tag,這邊的tag是指bootloader丟給kernel的一個boot arguments,
9 ]3 l i6 G( ]% a0 j
會被用一個叫做ATAG的structure包起來,並且放到系統的某個地方。然後kernel跑init.S,
* J2 |( R8 `& @
的時候就會去這個地方拿ATAG的資料,這些資訊包括記憶體要使用多大,螢幕的解析度多大等等。
# b4 V: N% j7 \4 s% ~8 C3 P
% k5 D. r/ P+ `. ^line 41, t是test, eq是equal, 判斷拿到的第一個tag是不是等於atag core. 應該是看
' I7 A* f a0 [& B: P+ Z6 _
atag list 是不是成立的。
; V8 b* Z8 |- F; P( e- i% x4 Z/ u' _, q
) u2 [1 |" ?1 a, a6 }繼續接著看
複製內容到剪貼板
代碼:
45 movne r10, #0 @ terminator
46 movne r4, #2 @ Size of this entry (2 words)
47 stmneia r9, {r4, r5, r10} @ Size, ATAG_CORE, terminator
發現45, 46, 47的指令都帶有condition "ne", not equal,表示是剛剛 line 41發現atag不成立
7 n* `4 k. {: f' T/ Y8 x所做的事情,注釋是寫『If we didn't find a valid tag list, create a dummy ATAG_CORE entry.』
, d1 i* b) n$ f8 S! U) _$ V- A' B
所以以上三行就是用來創造一個假的entry,假設一切順利這三行指令會bypass過去不會被執行到。
6 {& V$ P9 p- L$ z
! b4 n6 ?3 v0 V接著來看init.S最後一段程式碼 (終於~)
複製內容到剪貼板
代碼:
54 taglist: ldr r10, [r9, #0] @ tag length
55 teq r10, #0 @ last tag (zero length)?
56 addne r9, r9, r10, lsl #2
57 bne taglist
58
59 mov r5, #4 @ Size of initrd tag (4 words)
60 stmia r9, {r5, r6, r7, r8, r10}
61 b kernel_start @ call kernel
line 54, 將r9指到的位址的offset 0x0的值載入到r10。看註解是tag length,所以這邊得要去翻翻atag的規範
5 R3 v4 v2 M1 m這邊有個文章有提到 ,一開
5 _: s4 j/ j [- V) t# O9 z始應該是去讀atag_header所看第一個欄位,確認一下是size,應該沒問題。
複製內容到剪貼板
代碼:
struct atag_header {
u32 size; /* legth of tag in words including this header */
u32 tag; /* tag value */
};
line 55,測試一下size是不是0。
1 u p7 A- X! P/ [line 56, 57也有condition ne,表示是不為0的時候做的。將拿到的length(r10)乘以4,這邊的lsl是將r10往
; N% S; U7 ~4 n' `. Z* r左shift的意思,因為一個欄位是4bytes,所以乘4之後就跳到下一個tag,一直跳到最後沒東西。
! d6 @. P6 x. W3 N# A1 M9 G
; I7 ]; w- `& e" u$ V
line 59, 將r5設成4
) j1 j! _% Z/ W
line 60, 將r5, r6, r7, r8 ,r10存到r9所指到的位置,應該就是跟在atag list的後面。
4 y5 E6 G& @! uline 61, jump 到 kernel_start ,注意這邊是用b而不是bl,因為跳過去kernel就不需要返回了。BL會用到
. t I' a4 F- Hlr紀錄返回位置。
4 C7 _( O8 F: M4 |5 y- m% b5 n% a' R0 M4 G( r2 Z9 s- P& [
以上,走過一整個init.S,接著會跳到./arch/arm/boot/compressed/head.S。
% o1 M% ~; k V2 p: o6 `* r! G9 K$ c, m$ m( ]8 @0 W( S8 K- s
kernel_start的定義方式跟initrd_start有點類似,中間有透過 kernel.S去用.incbin把kernel image包進來。
; s1 |; b6 S& I) o- |6 h* m& U" f& o0 p" i! k1 O5 j