Chip123 科技應用創新平台

標題: trace linux kernel source - ARM - 03 [打印本頁]

作者: gogojesse    時間: 2008-10-7 02:00 PM
標題: trace linux kernel source - ARM - 03
到目前為止,我們已經進展到kernel幫自己relocate完,並解將自己解壓縮到一個地方要準備開始執行,那疑問來了?到底是跳到哪裡去了,因為./compressed/head.S最後一行居然是/ x/ Z1 S* F4 v7 W
『mov pc, r4』
6 ~2 e( K3 L" i- L( A, }. pr4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!
7 \* y! M1 M& B1 p0 ~# |3 U2 f. l1 T! ~4 J
所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。
/ d  m3 I& ~- `7 O& X/ [/ m- b6 P: a3 |3 ^4 P1 w6 ~
有興趣的人可以看一下 kernel source 根目錄裡頭的 Makefile,Makefile file裡面指定了使用vmlinux.lds來當做lds檔。
  1. 659 vmlinux-lds  := arch/$(SRCARCH)/kernel/vmlinux.lds
複製代碼
打開./arch/arm/kernel/vmlinux.lds.S (會用來產生vmlinux.lds)
3 |; U6 D1 o) G, p- Y+ p; Q我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。
: }, k3 h! }% g( b$ B$ C! Q於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {( G2 \+ }; E, h4 `. T
  2.      27         _stext = .;4 I* A; ^3 o& D% W% u* N; A: B
  3.      28         _sinittext = .;1 n  f! f4 q2 F! \
  4.      29         *(.text.head)
    2 q+ A* N6 C2 S
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"- n6 x) z( U9 n. o1 e( L; i) k' ]) p
  2.      78     .type   stext, %function
    . y8 A1 C1 x8 g+ H
  3.      79 ENTRY(stext)8 K1 |% G! |, h! R( V) f" K& U" _
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode( V% d' Q/ G- v) o/ V5 L) T, U7 z' X
  5.      81                         @ and irqs disabled
    . ~0 D" S: z; G: O7 o
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id3 T) V' D+ b0 E
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    ) N/ Y. Y- i& K* T: C
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?* b( a" Y& X3 S- e
  9.      85     beq __error_p           @ yes, error 'p'
    % }5 E) o& `3 J1 l. R3 `
  10.      86     bl  __lookup_machine_type       @ r5=machinfo
    " ^7 n* R$ f1 r' q
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?
    , F6 ~- j# J9 u, T
  12.      88     beq __error_a           @ yes, error 'a'
    : b5 V# O, z$ x% r- r( S1 k7 E
  13.      89     bl  __vet_atags5 T# h3 L  x; \8 ~& V% y% b
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。
. N! c$ T+ I, w$ b7 ?0 X3 B$ v& w: F" d' e4 P
看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
作者: gogojesse    時間: 2008-10-9 03:32 PM
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。
7 @7 t+ @* `# d2 W/ Z  i% S# {  k  a8 Y% B1 E. A% q( l$ V! S
可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*, C  d! j) \# M- |
  2.      60  * Kernel startup entry point.
    " H5 s9 V# b2 X" l1 h
  3.      61  * ---------------------------# ^! G+ [# f0 O, J) b' E
  4.      62  *
    $ @" w) ^3 O6 P  O! ^4 U
  5.      63  * This is normally called from the decompressor code.  The requirements3 b; W, h& b- v
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
    5 ^" @: j% ]6 w1 c3 n4 q  F
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。4 O1 \, w: l3 a
line 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)' r4 n, A1 A1 _2 q+ q
line 82, 讀取CPU ID到r9
* b/ I* S3 D# Z, f8 C  {line 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"
    * L* t9 c# I7 a! u: O" E, A
  2.      78     .type   stext, %function
    . ?$ W/ Y. B! E7 T9 l+ r5 K
  3.      79 ENTRY(stext)
    , L/ O6 m" P1 b
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    & u" G1 v( g8 K. U9 L
  5.      81                         @ and irqs disabled
    . W- d3 |) B3 R- P6 ?3 Y/ j
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id
    4 M6 E+ U0 Y: I: J$ H# c
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,
. [1 w8 n( f/ ?4 y+ }$ Nline 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。; l" Y; u; G% f* c* y6 k( r
line 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)
+ B  Q; V" u3 k* \# b7 oline l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。/ Q% [; _* |% F! `1 Z( b9 S
line 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S
9 p8 F9 T- P8 c! r" P' ]& m) |line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。
% l& V5 n1 @( A3 @- q8 y
1 m" {" B) D; x  _" j__proc_info_xxx可以在 vmlinux.lds.S 找到,是用來包住CPU info的所有data.資料則是被定義在./arch/arm/mm/proc-xxx.S,例如arm926就有 proc-arm926.S,裡面有相對應的data宣告,compiling time的時候,這些資料會被編譯到這個區段當中。
  1.     156     .type   __lookup_processor_type, %function; {% m$ e( W; ?3 F
  2.     157 __lookup_processor_type:) u* G& _8 ], u4 T6 c: G
  3.     158     adr r3, 3f
    / b5 V! b5 p0 l: Q4 z. v( T
  4.     159     ldmda   r3, {r5 - r7}$ ], E7 ~5 X. h( f+ b
  5.     160     sub r3, r3, r7          @ get offset between virt&phys! Y1 V9 W9 n8 e* j
  6.     161     add r5, r5, r3          @ convert virt addresses to6 o3 z) a7 u1 j
  7.     162     add r6, r6, r3          @ physical address space- R/ x. V* i. T* x% R
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask$ K& V4 Y& z( `. f6 F5 J$ K6 I
  9.     164     and r4, r4, r9          @ mask wanted bits
    ( |/ w5 \7 k' `" q) t/ Y
  10.     165     teq r3, r4+ H  ?% ^+ ?" y3 F, d; o# z
  11.     166     beq 2f3 q9 f/ X  q- a$ T4 R. |
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    9 r4 {) L  \2 O7 w6 y
  13.     168     cmp r5, r6
    # f+ h8 \/ s  |
  14.     169     blo 1b& H7 N- q  E- D  ~7 E. Z
  15.     170     mov r5, #0              @ unknown processor
    ' E2 l$ `7 j/ {4 u" i
  16.     171 2:  mov pc, lr* ~3 _4 H( Z4 h$ j6 z) n8 c

  17. " _% S5 B, w$ w7 x
  18.     187     .long   __proc_info_begin( d0 ]2 K0 Z: t2 [4 L, P( b0 w
  19.     188     .long   __proc_info_end
    8 V4 K6 Y/ T( ~0 }& |9 ^9 \' o' {
  20.     189 3:  .long   .
    3 U3 r( Y& z- F% z
  21.     190     .long   __arch_info_begin
    # V3 v) [( A' G) I0 k5 Y/ B
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
作者: gogojesse    時間: 2008-10-13 05:20 PM
我們從 head-common.S返回之後,接著繼續看。
; S; a) Y9 Q. X; n& O  ?6 s% U$ D6 f0 |7 F( n$ ]& p) g
line 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。
6 H$ n8 l2 u1 \line 85, 就是r5 = 0的話,就跳到__error_p去執行。
* W0 c2 ^. J. s* E8 eline 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid" b2 H3 W( H0 x* c* H4 J. I) J
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?* x/ r  N2 E+ g
  3.      85         beq     __error_p                       @ yes, error 'p'5 c, X* R5 L* ]( n" x- r
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是
& c9 ~# d/ s+ j% K
( O; V& b2 f6 w& l' i1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。
& c. ~6 U( }: e  o  o5 {
" R# D9 w6 n0 Q2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */5 _$ F. ]0 n0 c1 v  \
  2.      50 #define MACHINE_START(_type,_name)                      \
    ' ?) j! x& ~5 Z/ l! N
  3.      51 static const struct machine_desc __mach_desc_##_type    \
    6 B1 @# n  [4 }. I
  4.      52  __used                                                 \  B2 B+ g4 @/ H" C
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \; r" `* C! C/ C1 q7 K7 b
  6.      54         .nr             = MACH_TYPE_##_type,            \
      S$ Z) m; A6 S* p' m6 ^
  7.      55         .name           = _name,
    3 \% C' {+ u& g
  8.      56* U, z3 G& L+ D: ~* H
  9.      57 #define MACHINE_END                             \
    1 s4 j6 n' `( R0 E. V
  10.      58 };" |9 P& j4 L' A( g
  11.      /* 用法 */& w7 q  C; @  n$ W, m2 F* U
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")
    ; w# G7 r( I& X4 E# ~
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    * h% s5 F  ?( G& [- \
  14.      95         .phys_io        = 0xfff00000,
    - V  ^2 C: c; g8 |& ^8 t
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,9 `& X9 j2 A8 R. s$ }; p+ z7 M
  16.      97         .boot_params    = 0x10000100,4 b" T  C0 F) B7 z/ n2 h
  17.      98         .map_io         = omap_generic_map_io,) D# h" `2 ~7 T. H
  18.      99         .init_irq       = omap_generic_init_irq,
    9 S3 p# P( G4 f6 v( d
  19.     100         .init_machine   = omap_generic_init,$ Z$ v+ C) C' _5 s: U
  20.     101         .timer          = &omap_timer,5 X6 P& Y8 Y. e% I- `1 @
  21.     102 MACHINE_END. [, |1 q0 L& A
  22. ! B" c- }7 u$ w. |$ T7 D( C6 L
  23.     /* func */0 L" T) Q; J; _, [- W' q7 L( F
  24.     204         .type   __lookup_machine_type, %function
    8 [  S4 }3 q( y6 P6 Q  ?- t
  25.     205 __lookup_machine_type:% P1 x1 d6 g% r* H9 b2 N
  26.     206         adr     r3, 3b
    " c* |8 _6 I5 A: j& W
  27.     207         ldmia   r3, {r4, r5, r6}
    ( W  l' I1 A- j" o
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys
    3 u- n: B0 M) }& w$ o9 ]; c6 r
  29.     209         add     r5, r5, r3                      @ convert virt addresses to% ]* `8 [* r, C% a4 @, `( O5 H
  30.     210         add     r6, r6, r3                      @ physical address space
    $ U9 F! w9 H7 e6 G! A7 g
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    & ~$ `: w( R, i7 t
  32.     212         teq     r3, r1                          @ matches loader number?' x( [1 X) K# R5 e1 ~4 U# v9 ~
  33.     213         beq     2f                              @ found7 O" I. ^! L1 \  s
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    4 t: h8 G$ S9 B2 D+ ]& h
  35.     215         cmp     r5, r6
    & Y2 d5 x- R* O7 ~$ S  Q: ^9 ^
  36.     216         blo     1b
    : u" L2 i' P8 ~, s8 S7 M" p* _
  37.     217         mov     r5, #0                          @ unknown machine
    5 n, ]0 z. C( g& |: j
  38.     218 2:      mov     pc, lr
複製代碼

作者: gogojesse    時間: 2008-10-13 05:56 PM
接著我們又返回到head.S,
  |) r, U, }' p- O6 u( tline 87~88也是做check動作。% [+ y3 v; j. l! o; W! W  B/ f# P
line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?3 W1 K$ i, }3 [0 a+ Y/ F7 _
  2.      88         beq     __error_a                       @ yes, error 'a'
    " ^7 t" [+ d+ h- M7 V" X
  3.      89         bl      __vet_atags, K2 j! J6 I. S+ Y$ K
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。/ o+ g+ E0 e1 p9 j, n* w9 Q
line 246, 沒有aligned跳到label 1,就返回了。
$ [5 N, H2 v- z! Nline 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
6 P4 K$ G4 t$ C$ p3 i- x3 G$ t' E- jline 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。
! o  g5 p2 D/ K& `% {, }3 q(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x54410001; ]0 z! K/ z- |' j) F
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)' R* d5 g' u' \# L

  3. 9 B* Q. P% \, \
  4.     243         .type   __vet_atags, %function. N3 h" f9 i; ^/ T
  5.     244 __vet_atags:
    ( s* f2 Y7 V7 j
  6.     245         tst     r2, #0x3                        @ aligned?# h0 w) R; J3 ^5 e  I* J3 G' t5 N. S) x
  7.     246         bne     1f/ k, R6 v3 x1 ~" f' Q$ O
  8.     247
    * c$ H. K8 x7 m0 g  n( j1 n. L
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?
    7 t; ^* C. t, L; h& x8 [
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE: i2 G( p! M- o0 G) y+ ?* q
  11.     250         bne     1f. N  ^5 j0 q' x. U
  12.     251         ldr     r5, [r2, #4]
    ! ]/ I1 k! J4 s, H; T
  13.     252         ldr     r6, =ATAG_CORE
    - Y5 F2 B1 }* }) K7 r& \% A
  14.     253         cmp     r5, r63 P/ d. |0 Z/ W
  15.     254         bne     1f, _' k0 G# \) R7 x) _' e0 S
  16.     2551 N! C& a  M. J% h
  17.     256         mov     pc, lr                          @ atag pointer is ok
    : A8 O6 ~% J" |. G. q
  18.     257
    ' r' D) l3 ^' R8 P: }% R
  19.     258 1:      mov     r2, #0+ f* W0 u4 E, ~+ ~( Q. X& q$ P
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  
8 G: j8 [8 ?! r9 h5 Bline 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)
; g& H; `; H! }# J哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
作者: gogojesse    時間: 2008-10-14 12:13 PM
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:1 W' S* a( C' J3 F, |$ w% `
6 z# R, o$ |# c
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。" U/ O6 X9 @' ?& P$ J8 C
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。
5 Q% z" c* ?0 G. }3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。; L. ^$ T, o6 {, S! ~; f
3 i8 W+ w  O' Y: ?
以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。6 R! Z6 C9 l& C8 N

6 ~- w" b4 F, K/ g7 H8 K* |6 P+ p由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
作者: gogojesse    時間: 2008-10-14 12:14 PM
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。: i6 b5 T" Z3 E$ A
0 N( X' R* g( A3 O# v( x& L5 H: A
『產生page table到底是要給誰用的?』
5 V& e) ^0 ?# u- a% e/ o7 ^9 S9 Z9 G' ?- ^' H7 V# d
其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。
' v. b' }$ D; G# ?5 ~! E- B: s* b7 g! @* c5 \( F7 S3 O& q( f1 q
這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。
% s2 K( K1 Z( O4 ^1 X8 F. N2 d
. c  e5 }# q0 A0 T到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
作者: gogojesse    時間: 2008-10-14 12:15 PM
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。
' X9 T8 I7 y$ s; T: b
0 s% i/ i0 S' f現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。
* w* g& l/ e5 T
3 S' d* @/ \! O# g4 @知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。
% [0 Z! k0 O) s) b# i, v9 N  v6 h. L& x9 H/ y
p.s. 字數限制好像變短了。   (看來很難寫)
作者: gogojesse    時間: 2008-10-14 01:56 PM
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
作者: gogojesse    時間: 2008-10-14 01:57 PM
現在,讓我們跳入create_page_tables吧∼
! ]5 h6 P. t4 I6 G+ i5 Y/ Zline 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。5 y. D9 K1 {3 h* X$ N7 h
1 K# l5 K& V$ {- l  M' r
只是這個位址因為你硬體規劃dram位置不同,所以必須可以變動。一般會定義在./include/asm-arm/arch-你的平台/memory.h,我們看得出來dram開始的地方是從0x8000 offset(text_offset)開始算,猜測可能一開始有保留空間給kernel使用。實際算page table的時候有減去0x4000,表示是從DRAM+0x8000-0x4000開始放pg table.
  1. /* arch/arm/Makefile */! r  D" ~6 H  |+ w1 j0 y; _
  2.      95 textofs-y       := 0x000080004 p; F) P6 w- j3 _2 E1 y' _
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */
      L% F" C0 N& C
  2.      40 #define PHYS_OFFSET             UL(0x10000000)/ ?( b5 D& w1 {9 i6 |5 }
  3. ! ^# c, h6 V7 _, t% F) E$ W# C
  4.      /* arch/arm/kernel/head.S */
    4 {& X* t  T, e0 K7 M3 I% i
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET): g1 s; u9 G* x+ Q2 g6 p/ a- h
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)2 ~" [- N+ j5 t; z' Y, G
  7. % M0 E3 ^3 j3 ]. {# n# l
  8.      47         .macro  pgtbl, rd
    + A4 ~7 X# S: o/ Y8 ~) ~$ Y4 d
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)
    - j$ {4 H/ ]& q7 k7 ?
  10.      49         .endm
    $ C; e; m8 n: h6 Y" ~5 x& I& y
  11. - C. E; a* M% }2 d* O
  12.     216         pgtbl   r4                              @ page table address
複製代碼

作者: gogojesse    時間: 2008-10-14 02:16 PM
得到pg table的開始位置之後,當然就是初始化囉。" l$ ~: R: F/ c& U7 N
line 221, 將pg table的base addr放到r0.
( W" _' p/ [/ k9 b( x1 k1 vline 223, 將pg table的end addr放到r6.
9 U1 H& _/ u% h3 `7 L; }line 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r45 a  t( U4 Z  v% u
  2.     222         mov     r3, #0
    7 g+ l- H8 s+ v) g
  3.     223         add     r6, r0, #0x4000
    , F7 q% Y0 B$ U, X3 D
  4.     224 1:      str     r3, [r0], #4
    % [4 W  x' Q5 x& `
  5.     225         str     r3, [r0], #4; B1 ?9 y' v3 i: }* U
  6.     226         str     r3, [r0], #4
    0 X2 ~' |) e& A$ y' V
  7.     227         str     r3, [r0], #4* S! }5 }& j( r+ V; o' z* Y
  8.     228         teq     r0, r67 Z& B) o) ^/ ?5 v+ E7 `
  9.     229         bne     1b
複製代碼
line 231, 將位址等於 r10+PROCINFO_MM_MMUFLAGS 裡頭的值放到r7。r10是proc_info的位址。proc的info data structure被定義在『./include/asm-arm/procinfo.h』,offset取得的方式用compiler的功能,以便以後新增structure的欄位的時候不需要更動程式碼。這邊的動作合起來就是讀預設要設給mmu flags的值。
  1.    231         ldr     r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
複製代碼

作者: gogojesse    時間: 2008-10-14 03:11 PM
問題怎麼填值??
9 j$ e* w# [6 N8 e( _拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。
! P& h. _# O0 H5 H. K2 [
0 C/ `3 Q6 H! v: m  X# J- J" ?/ Y念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)
9 x, Z# P; }( S+ Z+ p5 n1. [31:20]存著section base addr( l& h7 T7 F4 u2 T$ X( g) a
2. [19:2]存著mmu flags
1 K" G0 g1 D7 w1 p3 c' @) ]3. [1:0]用來辨別這是存放哪種page, 有四種:
! A; _; K. x5 w) ~9 t   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)
8 B+ l8 n& e5 @+ ]2 s) u! S* e3 O" o4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
! O7 a$ F3 U* _+ x: U; ^4 q9 L/ f' m5 K2 F2 P  M7 _+ A
來看code是怎麼設定。# F- a# S4 ^1 q
0 y3 }9 G/ V4 ^. _, Z
line 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。9 s: V! j. ?# f5 Q+ e
line 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。
: V  |" ^. m) R! Y所以前面兩個做完,就完成了bit[31:2]。$ `% F1 Q8 ?9 t8 }5 O4 B9 k" w: X+ H
line 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #205 d, t% z# v5 y/ }
  2.     240         orr     r3, r7, r6, lsl #20' I, e5 h& Z+ D# K, l
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
作者: gogojesse    時間: 2008-10-22 07:47 PM
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼3 ^8 v7 N& M$ {* L  k, }+ J

7 ^- b! `& c  E( c& [上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看& G5 P/ C  p) b5 U
line 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。/ V$ a6 Q) R) r* J
' y% M6 l0 r/ R. s
line 249~252, 算出KERNEL_END-1的pte位址放到r6, KERNEL_START的下一個pte的位址放到r0。r0 <= r6的話就持續對pte寫入初值的動作。但是這邊的r3有加上(0x1<<20),所以原本的section base會變成加1,目前不是很明瞭為什麼要加1,或許往後面會找到答案。
  1.     247         add     r0, r4,  #(KERNEL_START & 0xff000000) >> 18
    4 ?0 ?* C' Y( m$ _
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
    $ I& U1 `, h4 P
  3.     249         ldr     r6, =(KERNEL_END - 1): D- o) X! u. f
  4.     250         add     r0, r0, #4
    : W; O0 g. E- L4 D
  5.     251         add     r6, r4, r6, lsr #18
    : g% k8 ^0 L  ]" A& O2 ]
  6.     252 1:      cmp     r0, r6
    9 |! H- P! C9 _* C) |
  7.     253         add     r3, r3, #1 << 20
    , j) g$ o$ z9 {4 Q0 s& }2 C" q
  8.     254         strls   r3, [r0], #40 n3 d. g3 M1 b9 Q- Q- \
  9.     255         bls     1b
複製代碼

作者: gogojesse    時間: 2008-10-22 08:24 PM
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。+ y6 }  ]: a2 n# ]$ p
line 280~283,將要 map 的physical address的方式算出來放到r6。/ n1 C4 a' C# u" R
line 284,最後將結果存到r0所指到的pte。& P9 J. W$ P7 Q( J8 h
9 Q, R; \) p, i+ g
以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。
8 _0 Z! u6 |5 [9 m9 X) k7 Q) j4 Y: b4 M  l  S* s. Y
line 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 18
    * ~4 a0 w' U, _" [. j* [
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
    2 v! Y# @7 X# \
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)
    3 Z9 j" q" ^& R4 v. h+ d7 ]: c
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)6 ?; t$ G6 S7 x* [* \
  5.     283         .endif" _2 W( l2 {- C2 S% ]1 @
  6.     284         str     r6, [r0]
    6 I( S0 I4 v. {9 V! K; C  `, q
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
作者: gogojesse    時間: 2008-10-22 08:37 PM
自create page table返回後,我們偷偷看一下接下來的程式碼,0 }- F9 N2 w: v" S; f; z) }
line 99, 將switch_data擺到r13( u# k) M* S$ S9 F) |
line 101, 將enable_mmu擺到lr! z6 G/ G( Z  O  M
line 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去
* z0 e+ h8 k$ T/ y' g+ P( P. B7 n3 K, ~3 B* ~& ~5 Y/ `
其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。 & @1 T; ~2 \* K( a
9 b+ r/ l% ~. b* N0 H8 r1 M
switch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after- e4 Q" B  F$ |+ ~3 P5 G
  2.     100                                                 @ mmu has been enabled2 o( p' @/ _6 B
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address; @4 W3 i2 }8 x# `
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼

作者: gogojesse    時間: 2009-7-4 01:09 AM
老店重新開張~
4 L) c4 o1 U+ `. h" ^7 R
3 n( K# D" ?4 N花了一些時間把舊的貼文整理到一個blog
" e3 S( ]! N( Y$ k: c  G8 k有把一些敘述修改過* [  I  k3 ?1 @0 o
希望會比較容易集中閱讀/ J6 L2 k2 v/ S
目前因為某些敘述不容易
' g4 X" k$ B8 Q+ }. Y還是比較偏向筆記式而且用字不夠精確
5 H1 R& i( l# _希望之後能夠慢慢有系統地整理
3 V3 @2 G" n4 J: x0 D+ Z大家有興趣的話
3 ]8 i, E, s& M6 D9 f5 t# Q可以來看看和討論 ; k. L1 _2 {: I# ~& e- e
http://gogojesseco.blogspot.com/3 B+ j5 ^# n4 S: q

9 y; q) M+ x; {6 e以後可能會採取  先在chip123貼新文章5 t3 X% ~5 F* J8 j+ I9 M  K( q' w2 u
慢慢整理到blog上的方式
2 ?9 f) m* z+ N* f7 y# `; p因為chip123比較方便討論 =)
: O9 E8 J! b( r4 r1 L! r3 u5 \blog編輯修改起來比較方便( x2 A" f( z, e( a
閱讀也比較集中   大家可以在這邊看到討論  h5 {, P  c/ r( L  P
然後在blog看到完整的文章 (類似BBS精華區的感覺)
作者: gogojesse    時間: 2009-7-15 05:07 PM
隔了很長一段時間沒update$ _9 Z3 `) Y. F8 _; ^$ ?
之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after: d: ]( U. b! s0 f6 R) o0 T
  2.     100                                                 @ mmu has been enabled
    7 n. \( Q+ u% l7 l( D
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    & h8 a' l* d0 f5 S' b7 |1 |" d
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)
5 X3 e; J3 X1 \2 a) @3 r4 m6 e* U6 Mline 101, 將__enable_mmu的addr放到lr。(留作之後用)% o" E7 a2 }& O! B3 m% ]- h' n9 J
line 102, 將 r10+#PROCINFO_INITFUNC 放到pc,也就是jump過去的意思。r10是proc_info的位址。PROCINFO_INITFUNC則是用之前提過的技巧,指向定義在./arch/arm/mm/proc-xxx.S的資料結構,以arm926為例,最後會指到
  1. 463         b       __arm926_setup
複製代碼
所以程式碼就跳到了 __arm926_setup。
  1. 373         .type   __arm926_setup, #function% @2 f7 S; ^6 g- W4 m
  2. 374 __arm926_setup:
    4 p7 R" d3 ]3 s, w0 y
  3. 375         mov     r0, #0$ ]1 g7 Z+ J) V+ a
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4" G2 b! I; G- s2 Q# f
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4, @+ e' N$ c3 W! [9 L/ z" [
  6. 378 #ifdef CONFIG_MMU3 }2 w" `7 I5 y) V2 U8 v; n% f% a
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4
    4 J; \5 p& I3 t5 V  s
  8. 380 #endif
    $ T4 ^9 q; [6 C8 s3 t: W

  9. ! r1 C$ M# A4 Y5 U( t
  10. 388         adr     r5, arm926_crval
    2 ]( v( d4 g# P6 r1 o6 P( r0 l0 I
  11. 389         ldmia   r5, {r5, r6}
    . ?3 W) @- L, j2 z4 D
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v45 W* G7 }0 u2 _, z" H9 @4 T
  13. 391         bic     r0, r0, r5. `$ q4 ~" D* d/ `$ B5 l
  14. 392         orr     r0, r0, r6
    ) G3 W/ m. v4 |! j" Y
  15. 2 f5 U" _! [5 w$ E$ H
  16. 396         mov     pc, lr
    6 s  A9 Z1 ~% g* s' h" F
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,3 d; W, {( @3 L4 j* U9 A
line 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。+ D) p( t8 F. }# Q- v
line 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)
) q) c! d0 H4 u/ M8 \) d) x. rline 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
作者: gogojesse    時間: 2009-7-15 05:29 PM
  1. 155 __enable_mmu:# |% E1 N  d( T1 g1 E) b
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \$ ~. Z- c8 D) r8 y6 \) y6 s
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
    ' Z1 \" v# n* h6 w
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
    : b( P+ _+ |; V! @; T
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    0 e" `( i7 o5 P1 r, `1 l
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register
    + j; u& Q8 o( z
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer4 z% _  Y' x; H" ], b
  8. 176         b       __turn_mmu_on8 I; L  v( T4 ~+ b9 Q( q3 `
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)
0 [" W! w4 |2 N# i9 e1 E- jline 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:
    ! S1 B6 B3 F/ P3 I; H5 d7 V
  2. 192         mov     r0, r0
    " E1 Y$ ~& x! U6 A# x3 N
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg5 W! m- T/ g) p* i$ |
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg
    ; W6 x+ I8 z; o( S$ e
  5. 195         mov     r3, r30 i+ H6 w+ v) _9 v6 x' e* |$ B/ A
  6. 196         mov     r3, r3& z2 n+ ]4 P# k) i7 P2 V. w0 Y2 b
  7. 197         mov     pc, r13
    # T+ X8 a# M! Q0 V
  8. 198 ENDPROC(__turn_mmu_on)
複製代碼
顧名思義就是把mmu打開,將我們準備好的r0設定交給mmu,並讀取id到r3,接著pc跳到r13,r13剛剛在head.S已經先擺好__switch_data。所以會跳到head-common.S。
  1. 18 __switch_data:# H$ j2 ~5 \2 e! B) K& c/ v
  2. 19         .long   __mmap_switched" u7 @) D' w  Y2 ^
  3. 20         .long   __data_loc                      @ r4+ h( w/ ]" B+ G7 M
  4. 21         .long   _data                           @ r5
    ' F' k9 O% |! H, A# ?) N3 H
  5. 22         .long   __bss_start                     @ r6
    + l8 ~4 e6 X; J7 X3 v5 Q
  6. 23         .long   _end                            @ r7
    % i1 j# g3 J4 G1 V7 }. f; J
  7. 24         .long   processor_id                    @ r45 j, e$ x* d6 ?+ ~
  8. 25         .long   __machine_arch_type             @ r5, X/ s; X& u: R" b
  9. 26         .long   __atags_pointer                 @ r6
    5 [# ]) `# H6 g- n7 Z/ q' Q
  10. 27         .long   cr_alignment                    @ r7
    ( i& ]* K) l$ `2 B. h
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp
    * f2 R; p# J( P! `* s: s
  12. 29
    1 }: `* l$ ^1 Y& C3 F( G
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
作者: gogojesse    時間: 2009-7-15 05:30 PM
  1. 39 __mmap_switched:) A, h1 H0 l! Q  d1 T' ~
  2. 40         adr     r3, __switch_data + 4. \* w$ E" ^  g0 _/ Z
  3. 41
    . u9 h+ @1 U- D* Y% z1 d) e
  4. 42         ldmia   r3!, {r4, r5, r6, r7}5 X, L. e$ x. ~6 S7 w
  5. 43         cmp     r4, r5                          @ Copy data segment if needed7 l& L0 p/ O1 B) B; h: a! i2 h
  6. 44 1:      cmpne   r5, r6
      P+ V# F* g$ s$ t: f+ u
  7. 45         ldrne   fp, [r4], #4, F* @6 L0 i  u* F1 X. L; f. J
  8. 46         strne   fp, [r5], #4+ j) ^$ J" z: o1 ?2 X. J
  9. 47         bne     1b
      n/ j5 Y7 s; x
  10. 484 R  S& A5 C9 N! D9 b5 C+ @
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)9 D6 A, R, [3 H$ z) u
  12. 50 1:      cmp     r6, r7
    2 c1 h4 j5 k, U
  13. 51         strcc   fp, [r6],#4
    4 Q3 d7 t# d' F9 \" Q- }# e+ K
  14. 52         bcc     1b
    8 g+ T! E, ?* E/ Z
  15. 539 i. x5 Y- [+ }
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}1 A7 h4 a- X& a# _7 Q2 k
  17. 55         str     r9, [r4]                        @ Save processor ID: g" j. ^6 o8 r. C3 M1 H( [$ t) I1 p3 C
  18. 56         str     r1, [r5]                        @ Save machine type
    7 o4 g5 D6 S1 A/ f
  19. 57         str     r2, [r6]                        @ Save atags pointer( w1 Y; z) H) E1 s2 r
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit
    # ?; s/ }" c5 I& r( ]8 v
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values
    ! ]- V- [" I9 S
  22. 60         b       start_kernel
      K% C8 q* F. R) u8 f
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。# m) l( X3 @+ N( ?9 {
line 39,將__data_loc的addr放到r3  v, R: ^" t) m7 Q5 ], f
line 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7
2 ]; g+ t1 K6 O& x  Cline 43~47,看看data segment是不是需要搬動。
: E6 h" M8 t7 L( g1 V7 Dline 49~52, clear BSS。
/ _+ S- J4 y2 P+ @; z
* M5 h  v3 N7 ]" C由於linux kernel在進入start_kernel前有一些前提必須要滿足:
% x9 ~& `5 `) q; Y" pr0  = cp#15 control register
# E( N4 c. c0 R2 U# dr1  = machine ID. d8 b- Y& t6 S8 u8 n3 Z$ p
r2  = atags pointer
+ m& x& x/ W- g% w/ u. Z( Sr9  = processor ID* f% a& W+ t# q, q1 N9 n% v
* j, v- J( s6 I6 n7 M
所以line 54~59就是在做這些準備。- ?6 p& I+ \4 p2 G  }
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)
3 R7 @9 [' @0 I  q( z* d+ ?; ?! f4 k. C7 U1 ?$ K) {0 g* g
看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示- `! ~# f) d- s. Z; i
我們真正的開始linux kernel的初始化。
8 e. U% l  x8 P0 ^+ H4 z" A像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。
% Y4 s7 _: P! A( a5 \9 D( G到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。




歡迎光臨 Chip123 科技應用創新平台 (http://chip123.com/) Powered by Discuz! X3.2