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最後一行居然是
- R& `$ ], l% _4 s『mov pc, r4』5 b, y/ E- R! J/ A, g9 l& U
r4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!0 f; m% s8 s" `8 i% A
7 Z+ Y' d  A, R
所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。# m1 ?0 j) y- f3 O
/ f+ z& C) K- T
有興趣的人可以看一下 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)
' Y; O( g/ T( l我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。
0 J2 f0 i& S, S5 Z5 u6 g+ D6 c於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {
    0 \: y4 o  w7 [
  2.      27         _stext = .;
    ! D- K1 i9 x- N
  3.      28         _sinittext = .;9 a5 n" V6 `& x( J; n
  4.      29         *(.text.head)
    1 `9 T& C  t' j  k
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"
    " t# r# d! Z9 Y0 P
  2.      78     .type   stext, %function
    . x% h& f, C& y
  3.      79 ENTRY(stext)
    ( N) _% P: X$ [" c; Q6 H1 j( o- \
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    1 z6 X3 r* N! Y# N2 Y
  5.      81                         @ and irqs disabled
    2 m1 h: a+ Q) E
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id8 n9 c  R4 N4 N; J8 ], N
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    & i. \5 B; c. m
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?* H# N$ @7 B! x! `6 d
  9.      85     beq __error_p           @ yes, error 'p'
    . F3 K+ Q5 h5 [* X0 r
  10.      86     bl  __lookup_machine_type       @ r5=machinfo  a7 W% F$ c' Y1 J6 u) l1 p
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?
    - C: x  D9 `+ Z) p- v! Y! d3 o
  12.      88     beq __error_a           @ yes, error 'a'2 e2 X, U0 r1 r! o. i; B4 n4 B' N" z! Y  ^
  13.      89     bl  __vet_atags/ X6 _, B5 n4 c, C
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。
4 s( q- Q3 F  p* m
( v+ Q/ A8 Z9 G1 k5 X" {4 R看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
作者: gogojesse    時間: 2008-10-9 03:32 PM
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。( K$ ~% H0 y7 M! a/ b- j; p3 c9 s
. @7 a6 M0 y% h' S" b0 U; T
可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*
    6 X% i: B& @% H& }. u' [% L. w
  2.      60  * Kernel startup entry point.+ L- P" t- g2 R6 }8 g5 S& F/ u6 z: A
  3.      61  * ---------------------------6 a: j. j% ^8 D
  4.      62  */ k. G# @% o, g" ?
  5.      63  * This is normally called from the decompressor code.  The requirements
    5 h* m9 e& S; h+ n8 g8 |! d8 S
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,% s4 x# Z' d9 ?1 b) G' r; Y# _/ j
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。
2 g, L1 m' ^4 vline 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)
3 C0 o7 f  Z1 {* G! |" `line 82, 讀取CPU ID到r97 V. t8 c0 d" g- ]4 h) R4 K- Y' D( j
line 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"+ a  f, n6 {- h4 n9 g9 |5 b. l! T
  2.      78     .type   stext, %function$ F5 H) e) _( V2 `: I* @
  3.      79 ENTRY(stext)
    0 {- e9 P" {" K% ]- j
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    1 L$ I( H. r; l. D6 D2 @, h4 T
  5.      81                         @ and irqs disabled
    : C" W' ]! k, M) F. d6 ]3 G
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id
    * j; w% J9 f: f
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,9 v* w  k3 x# ?6 o+ l
line 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。) Q0 _$ J* o+ W! y5 ]. N! T+ ?9 }  C
line 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)% B* I# T; t8 M. T! Y. I
line l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。
6 [2 r* }9 L+ [& A6 m) Pline 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S
; g/ o5 r8 M5 Uline 170, 找不到的話,r5的processor id就放0x0.表示unknown id。
' s, \" i) k5 c* b; z( E, u9 H- Q' T& z
__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( b5 q, @( t5 J' O2 P
  2.     157 __lookup_processor_type:
    ' K9 W  R) A# ?! x
  3.     158     adr r3, 3f
    , a; y) l) v" [9 O! [- D, l, n
  4.     159     ldmda   r3, {r5 - r7}) ?! Z1 \5 H9 {" T" T* h
  5.     160     sub r3, r3, r7          @ get offset between virt&phys
    ' a0 D1 m/ j# T+ ]' Y8 a( `2 j' F6 h
  6.     161     add r5, r5, r3          @ convert virt addresses to' C; g7 j* `7 b$ A5 f
  7.     162     add r6, r6, r3          @ physical address space
    % U) Z: D$ d; G7 a! {% V& U
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask
    - e/ \1 u  i0 I
  9.     164     and r4, r4, r9          @ mask wanted bits
    0 ^" g8 s. k$ J+ j
  10.     165     teq r3, r4
    . n/ J' B) u  L6 f7 P+ ?3 h5 x8 P
  11.     166     beq 2f) N8 `3 j. S' X+ |: ]
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    / Z0 f. V4 b- j+ u4 s' z
  13.     168     cmp r5, r6; M8 d. i" A" F6 X( {$ V% |" H# R
  14.     169     blo 1b2 p) Y( d: E" ^8 C) {
  15.     170     mov r5, #0              @ unknown processor3 o$ f4 o/ ~* _, M% |7 d' U9 {
  16.     171 2:  mov pc, lr
    , m: i( W7 `) U& m# C

  17. 3 r' p9 j) `& j, v3 |+ R
  18.     187     .long   __proc_info_begin
    5 T0 A( i& P4 P* t7 e- D, G
  19.     188     .long   __proc_info_end
    5 A$ ^! ~+ p/ L' R* L  T( G
  20.     189 3:  .long   .
    4 z5 l  a* {  I2 a+ ^( W, s
  21.     190     .long   __arch_info_begin5 l6 j: r/ D& k! _) t* L- n5 k
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
作者: gogojesse    時間: 2008-10-13 05:20 PM
我們從 head-common.S返回之後,接著繼續看。4 }4 l# r/ ?$ n/ X# l

, G# R; b2 d* I1 b2 y, M; vline 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。' r! A$ G3 T. S9 R
line 85, 就是r5 = 0的話,就跳到__error_p去執行。
5 X) ]/ s$ l1 ~+ a+ r4 s8 \" mline 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid/ i7 l) Y' G( E: x8 R
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?0 V6 c: H4 u' X
  3.      85         beq     __error_p                       @ yes, error 'p'% K+ p5 t# U! e9 W+ G
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是1 R" w! n: m7 `( _4 S
2 ~" B# u1 a1 l8 F
1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。2 w: p, [( `9 p  ?1 l* ^. l8 q
# a( b* c; K0 @. [  {- M
2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */8 q* D7 x* w2 ?* x1 Q) f
  2.      50 #define MACHINE_START(_type,_name)                      \, M/ M6 s0 G- C3 I# W
  3.      51 static const struct machine_desc __mach_desc_##_type    \6 f, Y0 s+ }+ x( O9 P, ~
  4.      52  __used                                                 \) Q/ l4 W8 Z) ?3 [
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \, D, A" V# L0 R- s- K% L( O
  6.      54         .nr             = MACH_TYPE_##_type,            \" J0 n' ?) ~' ^+ C7 }8 ^; h
  7.      55         .name           = _name,' d  q9 K) \0 X1 v
  8.      56
      [* f* q0 ^* K2 x
  9.      57 #define MACHINE_END                             \
    * Y- K0 u% Z$ C% ^
  10.      58 };& [& L  ]8 @1 e( h
  11.      /* 用法 */
    6 ^% i/ A+ J6 V$ a  T5 J
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")
    + V/ s( a' p3 N- N2 ]3 \
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    ) r! i% K/ P( [+ Q( ^
  14.      95         .phys_io        = 0xfff00000,# M! l8 V4 l9 O) ~
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,5 J/ A' u0 R0 ~2 p  i, N
  16.      97         .boot_params    = 0x10000100,$ B0 Z1 a& Z: k  a$ g0 ^- S2 N
  17.      98         .map_io         = omap_generic_map_io,
    6 y" p0 @+ z) w. \$ u
  18.      99         .init_irq       = omap_generic_init_irq,
    5 y6 W! z- m3 r0 c, ^
  19.     100         .init_machine   = omap_generic_init,) O+ Y) z: b$ L" @+ Q
  20.     101         .timer          = &omap_timer,6 e: I1 G. X, ]% \
  21.     102 MACHINE_END0 v7 `& o% y% H% p1 }! r% B
  22. # }9 c1 g, E3 p
  23.     /* func */
    1 d$ Q1 ^' f& Z6 T+ j$ i
  24.     204         .type   __lookup_machine_type, %function
    6 [, H0 e+ D+ G: Z
  25.     205 __lookup_machine_type:
    0 h, S, R2 ]0 n1 {4 I, p
  26.     206         adr     r3, 3b
    2 J% o& Z3 X, V' V8 z4 X9 d( _
  27.     207         ldmia   r3, {r4, r5, r6}
    + @5 R' U' P2 D! i/ e. m$ A
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys/ g* {( j2 c- |& X% z2 W
  29.     209         add     r5, r5, r3                      @ convert virt addresses to8 `  C  d; E4 q: m8 s
  30.     210         add     r6, r6, r3                      @ physical address space
    2 [' ^: r) j8 T6 B; {# p
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    ) N8 b! B. J9 A# R1 F! q
  32.     212         teq     r3, r1                          @ matches loader number?+ j: e) I" {. N: h* y! l
  33.     213         beq     2f                              @ found
    ! y2 t1 M+ g% X, G7 R4 S3 I
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc( ~, f, E8 {% ]
  35.     215         cmp     r5, r6' p7 b+ u2 ~9 H
  36.     216         blo     1b
    - ^2 P' c1 ]% E, `
  37.     217         mov     r5, #0                          @ unknown machine
    ) |& R- S/ }& B) j4 o, y5 d$ @
  38.     218 2:      mov     pc, lr
複製代碼

作者: gogojesse    時間: 2008-10-13 05:56 PM
接著我們又返回到head.S,
5 S! a5 D# p& \' J6 Y* j& T- h% gline 87~88也是做check動作。5 E, f( L+ N0 }$ D$ w/ j5 e
line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?: X% v+ o5 \# i' X. U; i" h
  2.      88         beq     __error_a                       @ yes, error 'a'8 j! D1 J& k/ o9 c
  3.      89         bl      __vet_atags
    / O: Y# D: o( c- O; c2 _5 Y
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。! q  ?0 V$ z$ ?4 n& Q7 x
line 246, 沒有aligned跳到label 1,就返回了。7 y- J7 w  n5 f9 _1 i# T
line 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。, W* D" P5 Z  h9 y. l; `% H
line 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。
" ~& R: c9 S9 A. o6 @1 u(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x544100012 {3 S4 b/ k5 u9 r& O; }6 j
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
    4 O1 {, w. j* d: d/ r

  3. : }7 Z/ n' }, w* X# e- P+ j# t
  4.     243         .type   __vet_atags, %function( O1 P" d) U3 O8 E+ l4 v  \
  5.     244 __vet_atags:+ \  T0 {/ k- w
  6.     245         tst     r2, #0x3                        @ aligned?& k9 q0 O5 u+ D0 h+ G
  7.     246         bne     1f
    " |& F' U" e3 N2 X# C6 V
  8.     247
    ) Y2 I0 X# r9 u5 s
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?
    * e7 @; j9 L4 d) d
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE
    8 `; Y' n, B4 o
  11.     250         bne     1f
    / d& V$ b' m4 c1 u4 [; o: p
  12.     251         ldr     r5, [r2, #4]
    8 _% @' d3 J5 @2 L
  13.     252         ldr     r6, =ATAG_CORE
    0 ^2 x. G  y0 J/ k  g7 l
  14.     253         cmp     r5, r6
    9 O% `  s0 X8 F
  15.     254         bne     1f( o9 z, b. \( a! e9 F. g
  16.     255
      Q, ~# ]( ?* P2 q0 m
  17.     256         mov     pc, lr                          @ atag pointer is ok
      K, U3 h( u2 U0 F4 n! V9 @, ]
  18.     257
    % s' v$ d" U, W7 }" }! E
  19.     258 1:      mov     r2, #0
    : a8 L' |6 ^  R- g/ Z) c
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  
4 G: t$ C8 U  X" }- A$ D" Mline 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)
7 {+ O. D* s; s9 u. m, L% D哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
作者: gogojesse    時間: 2008-10-14 12:13 PM
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:4 b% Z; ]2 |& W# M
9 d3 g  R8 W0 \
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。
3 w) G, M, a! [* ?2 S2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。) I: o# ]- I1 [# A7 ^9 l# ]
3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。
4 K) O- r- y+ `; q6 O9 U4 g
% W% w' F" @. C! W7 l& c以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。
% @0 W) }, A7 d5 X2 v! x' k$ ?
2 F$ {4 q0 n5 _# P, F/ e; ]$ }由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
作者: gogojesse    時間: 2008-10-14 12:14 PM
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。! M( H& n4 H6 c* C. z

3 D, R) T; z' f: d* I『產生page table到底是要給誰用的?』
; ~) H2 J% y7 v8 w. R& P
; v: m% |4 V1 V% k) A2 ?/ j其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。4 ^; C' ^- b" `6 `3 k
7 u1 X* ]' V# y+ \# N
這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。
1 G9 F( j) D9 ?& M$ G
& d+ L1 Z9 Z% A( I6 L到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
作者: gogojesse    時間: 2008-10-14 12:15 PM
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。
! J7 S6 ^/ r  `( S0 R" h
  E, v& L% \/ @+ W- L1 T現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。
( K& `; h' `; {8 F9 U4 ~  m% o
' p; v  r: G4 Y' O知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。: j- N( k# B4 R/ k7 I$ X9 y& D; q, ^! w
; \. i2 [% W+ l2 e+ _
p.s. 字數限制好像變短了。   (看來很難寫)
作者: gogojesse    時間: 2008-10-14 01:56 PM
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
作者: gogojesse    時間: 2008-10-14 01:57 PM
現在,讓我們跳入create_page_tables吧∼. t+ S8 _; R6 w3 h
line 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。- d$ c9 m( q2 ~, |

$ j( k/ y- Z) U只是這個位址因為你硬體規劃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 */) O% F4 `4 {; q4 A+ M9 n8 s4 v
  2.      95 textofs-y       := 0x00008000+ b2 l% J5 }: ~6 g3 n
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h *// V# k9 [) \  \
  2.      40 #define PHYS_OFFSET             UL(0x10000000)0 y/ v$ k# W1 t$ e
  3. # P* [) c1 j6 j
  4.      /* arch/arm/kernel/head.S */
    - X. S" R. E% I5 p, z& x% J
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)
    2 X- \7 l& `2 v* h
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)3 b8 U) c' K& `: W+ N( m
  7. 3 k" o3 o7 v  M4 J8 y* O; w$ c
  8.      47         .macro  pgtbl, rd
    ' T# O" X4 u  O( W
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)
    ' v, |: F' D7 U/ m- R0 e
  10.      49         .endm
    $ A" \# J3 w9 r/ `4 @4 Z

  11. 9 u; n; L, ~0 k7 y
  12.     216         pgtbl   r4                              @ page table address
複製代碼

作者: gogojesse    時間: 2008-10-14 02:16 PM
得到pg table的開始位置之後,當然就是初始化囉。
2 P: d6 u# U2 R) S, tline 221, 將pg table的base addr放到r0.
! y+ R  u0 `( t8 Vline 223, 將pg table的end addr放到r6.
+ a$ B4 d" n8 }0 ~line 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4
    ) T( L7 C0 r8 c5 U2 C! E% d
  2.     222         mov     r3, #0
    ' Q- Z: C* ?" p6 X! [7 ]
  3.     223         add     r6, r0, #0x4000
    , @- ^& c+ f3 U: n$ V6 l5 b" g
  4.     224 1:      str     r3, [r0], #48 f/ P& j% ~9 U2 R/ [7 C
  5.     225         str     r3, [r0], #4
    - X3 p% ?% G0 ?' k2 Z+ _8 m4 w
  6.     226         str     r3, [r0], #4
    1 E6 U5 J3 \$ [
  7.     227         str     r3, [r0], #41 W" |+ O% J3 R# I. z: H- S1 s
  8.     228         teq     r0, r6
    ) H# z% r5 z  g- {2 b/ O
  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
問題怎麼填值??
8 B+ |" e0 [4 [) Z( j8 y拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。 & a8 [2 q- M- W- P( M
% _! I! @0 a: [  c0 \, Q4 T
念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)
1 M/ B+ C0 F$ [3 b, ~1. [31:20]存著section base addr, O5 n5 n4 u6 r4 k1 v) ]
2. [19:2]存著mmu flags
; A7 s2 a; b# W- m4 U! E+ S3. [1:0]用來辨別這是存放哪種page, 有四種:
5 k) Q, V9 C6 Z' C$ x/ d' P   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)
0 Z7 l4 U9 b: ]! l, k4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
0 h+ ]. ^8 G+ {2 K- O' O" f; ]; w
  @& e2 V0 ?- `; t4 I來看code是怎麼設定。/ I) a# C3 [' O4 [3 C

. R. y4 l  x+ P% {! yline 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。4 [# X. V: Q" l* G0 K
line 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。
' Q2 @, H4 h- b6 q所以前面兩個做完,就完成了bit[31:2]。4 D% C' x* t2 I" M4 `  O) y( f
line 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20
    ) }! R" T+ v, _" K2 O2 Y# e+ Y
  2.     240         orr     r3, r7, r6, lsl #20
    5 G3 v/ j( q, g# k% t
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
作者: gogojesse    時間: 2008-10-22 07:47 PM
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼: E, W+ p$ u+ v: M' Z
4 z+ s5 `; v# W5 h9 p
上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看
. O6 X( o% y$ T9 D9 N+ G5 q7 y2 w  ?line 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。
( z6 g* P9 G& c9 J& V3 p
0 k, o1 u. q' S! S9 k6 I, nline 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. S5 L# f5 V, ?1 K1 w2 [3 i- N; P, A
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
    * z  n6 [( I, a" Q) a+ N
  3.     249         ldr     r6, =(KERNEL_END - 1)9 Y5 j. j1 }3 c/ Z( S
  4.     250         add     r0, r0, #47 U0 m) W" i6 ]" |! t" |
  5.     251         add     r6, r4, r6, lsr #18# w3 x2 k- Q% t5 m" H
  6.     252 1:      cmp     r0, r6
    , I4 ^! t' L! d/ F- a! v5 ~
  7.     253         add     r3, r3, #1 << 20% k- W, i2 {: L6 n0 B" b1 i
  8.     254         strls   r3, [r0], #4
    4 F# s1 C- X4 k; ?
  9.     255         bls     1b
複製代碼

作者: gogojesse    時間: 2008-10-22 08:24 PM
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。
  c& P- Q$ f* s. Jline 280~283,將要 map 的physical address的方式算出來放到r6。: [% ]  G# O) X& K$ g0 _, J4 B
line 284,最後將結果存到r0所指到的pte。' [, D+ A5 P! @& ]: b

( t" k0 m( L+ \% N, R以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。0 S$ d) W, v) K3 m

# Y0 I$ a+ T2 [% p2 yline 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 18$ x' D9 z( Y- C2 E, P# N& O& o! E
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)- f' Y, w- f; |) y
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)
    : N( ?: y8 b& W& J/ W
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)$ o8 ?3 p2 d7 s4 o
  5.     283         .endif
    $ d$ D* c5 Y/ |. v5 f! G
  6.     284         str     r6, [r0]& K6 Y! ~1 p, u/ X4 p: Z
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
作者: gogojesse    時間: 2008-10-22 08:37 PM
自create page table返回後,我們偷偷看一下接下來的程式碼,7 i' j8 C# o9 [5 ^
line 99, 將switch_data擺到r13
6 }7 @' @) I! ^line 101, 將enable_mmu擺到lr$ |/ d& I6 s. d( P4 q$ Y% v
line 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去* k3 R& [) t# C* H
& w6 Y4 B9 ?" ^, x1 h
其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。 0 N+ j) Z( v7 s$ q2 C% k$ Q
$ F' `3 r* }/ W
switch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after
    8 \! l3 \" m) w* y% y4 J5 G
  2.     100                                                 @ mmu has been enabled
    5 v7 [& B) @& K8 p# V) T& i
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    4 h: m$ _: c' m+ L
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼

作者: gogojesse    時間: 2009-7-4 01:09 AM
老店重新開張~# E6 ]$ D( g3 e/ x3 K9 N9 E. B: F7 I$ ~

7 V+ a% C/ Z& Z/ d花了一些時間把舊的貼文整理到一個blog/ d! ~* M( H9 M4 _
有把一些敘述修改過
- _1 I( s0 R% s, w+ ?; \希望會比較容易集中閱讀
1 l8 j7 h8 h# C4 s6 B目前因為某些敘述不容易
) q1 v9 G& }, n1 h& R還是比較偏向筆記式而且用字不夠精確1 [; ^' h9 I9 y0 K; I2 X' q1 s
希望之後能夠慢慢有系統地整理, C! k; u4 B2 F6 M1 X+ s0 y
大家有興趣的話
, A2 G% V6 m# E5 B! \$ P可以來看看和討論
5 c7 {) M* z# N- r. c# \2 J8 ~# fhttp://gogojesseco.blogspot.com/
; _' R) H0 z# E
- G6 {' O* R5 j/ s% V以後可能會採取  先在chip123貼新文章4 U9 b9 T, |7 b
慢慢整理到blog上的方式
6 x- C( |7 N8 m6 Z因為chip123比較方便討論 =)) K9 J, x1 O1 y6 u$ k' [: g& f
blog編輯修改起來比較方便
5 u+ M/ u8 H+ [% g& m  e0 E閱讀也比較集中   大家可以在這邊看到討論
4 |/ B+ s6 T  ?, b" Z8 \) A然後在blog看到完整的文章 (類似BBS精華區的感覺)
作者: gogojesse    時間: 2009-7-15 05:07 PM
隔了很長一段時間沒update5 L5 m8 [: A  u, j% \' c; T  a6 t
之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after+ |' k1 \& r# g" E/ t# O# K
  2.     100                                                 @ mmu has been enabled
    6 R7 b+ j7 w, E2 ~: a8 s
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address' i, Y6 N" R4 j8 Z$ y( o
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)* Z8 Z& C8 C! ]% q. F) C% C
line 101, 將__enable_mmu的addr放到lr。(留作之後用)
+ C; b) e, J1 Y, a, Rline 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& H3 B3 v9 i+ z
  2. 374 __arm926_setup:
    ( L3 Y7 p/ g1 ~$ C8 s# L" ~
  3. 375         mov     r0, #0+ w# q5 b3 m/ {' _1 {
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4
    * K* o+ i, J' }, [
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4
    , ]8 N; P+ L3 L7 @3 u. @
  6. 378 #ifdef CONFIG_MMU
    : m# P6 w+ g6 Y: `' I
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4
    5 f4 S! Q" c/ N$ j5 X2 L: E
  8. 380 #endif
      c! y/ N1 S9 T8 O

  9. 5 _, X4 A; t' }1 d# ~) C* e5 r: Z" G( O
  10. 388         adr     r5, arm926_crval
    2 O6 p! g& Q5 U* C% N1 V: N
  11. 389         ldmia   r5, {r5, r6}
    % N* r: ?% O3 O, R
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v47 ?, ?6 ?+ d. Y8 h( o" ]
  13. 391         bic     r0, r0, r55 s* y8 r* a( Q* w0 `
  14. 392         orr     r0, r0, r60 Y8 K2 C& e! k# _$ A6 A/ o9 d
  15. 5 [+ U0 t! S3 G3 n8 _' I
  16. 396         mov     pc, lr/ g  D6 v7 G* R2 p
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,9 I6 h& M+ U" Q4 p$ J
line 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。
7 o- x9 x8 b9 B' uline 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)+ N/ Y# h3 b! Z
line 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
作者: gogojesse    時間: 2009-7-15 05:29 PM
  1. 155 __enable_mmu:1 n# A7 I) J$ i; F# s- C
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
    8 ^( r. d5 O+ i, s" U5 e4 z
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
    0 Z- {& S  n- j, z0 u
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
    / v3 c7 c5 ?, f* {# i6 P# f+ X
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))/ w2 j( l/ k- m' [) [
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register& O. h' `8 S: N: m2 ^; L( m
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer
    * l! x/ u3 F' r
  8. 176         b       __turn_mmu_on
    8 S6 I6 }$ i0 c7 ~* v
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)
% O* e* d+ j9 q9 m9 B; Wline 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:) Y8 S: S/ d7 \$ w5 q
  2. 192         mov     r0, r0
    + q) d2 @- |* {2 Q9 }) |# `3 o% p
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg
    . C6 Q# q) R, t! \
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg
    $ Z  D: @4 W8 D2 q
  5. 195         mov     r3, r3' j3 _0 X8 t* A  H* T) _  C; a
  6. 196         mov     r3, r3) s8 `+ R+ T8 m% S0 h, F+ Z: l" W
  7. 197         mov     pc, r13
    1 T, f1 _+ R6 T1 G
  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:$ _0 ^% s4 b( w/ ^
  2. 19         .long   __mmap_switched* j% r/ u0 U6 `! h1 ]
  3. 20         .long   __data_loc                      @ r43 \, Q, r6 P4 C( K3 [
  4. 21         .long   _data                           @ r5
    & g1 i# n- J2 n9 G# f$ g* d9 ~
  5. 22         .long   __bss_start                     @ r6) ]5 p! N- o3 @) v5 R2 v& u- Y! n
  6. 23         .long   _end                            @ r79 s! z  O" G5 E5 o0 ]. E( @
  7. 24         .long   processor_id                    @ r4  y4 b; _+ }, s. g" r3 T# i; L) ?
  8. 25         .long   __machine_arch_type             @ r5- @8 y$ @% `3 Q% j; w. M# a
  9. 26         .long   __atags_pointer                 @ r6
    + V) }# E) a' }4 J$ _, ~
  10. 27         .long   cr_alignment                    @ r7
    / ?9 v" h+ y- u) O; s
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp
    3 k! E$ d" J1 n/ ]. {
  12. 291 Z6 \2 T2 B% z* M( q( m
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
作者: gogojesse    時間: 2009-7-15 05:30 PM
  1. 39 __mmap_switched:
    7 p3 }, d9 y. k
  2. 40         adr     r3, __switch_data + 4
    . j5 y$ Q" n7 d- Y3 Z% `
  3. 41
    1 i& W( C0 d0 `. _
  4. 42         ldmia   r3!, {r4, r5, r6, r7}1 T- q$ C7 y. Q" K& m+ p# p
  5. 43         cmp     r4, r5                          @ Copy data segment if needed
    . A$ B2 c) P9 L& Z& t+ ]
  6. 44 1:      cmpne   r5, r6* K8 z. v# @8 F. X; [8 ^
  7. 45         ldrne   fp, [r4], #4
    8 ]9 t, e# q# t
  8. 46         strne   fp, [r5], #42 M; F$ t! A4 {' a6 m7 q
  9. 47         bne     1b
      j8 J; h( h- k7 o
  10. 48
    . p# i* o. _8 n  R6 F
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)
    * |  N" Z6 X5 K% _  `
  12. 50 1:      cmp     r6, r76 K' q9 Q2 B5 B
  13. 51         strcc   fp, [r6],#4
    ! Y( c$ Z* L  s3 X+ N" D/ {; k
  14. 52         bcc     1b
    / i. _4 y1 ^' ]! P8 `* d0 F
  15. 53& v/ B5 X- Z0 Y# Z# C5 H. i
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}- s+ A( J; k% S; x6 u$ a
  17. 55         str     r9, [r4]                        @ Save processor ID
    ( ~( h: K. Z+ w; j
  18. 56         str     r1, [r5]                        @ Save machine type6 J5 ^* g' G7 M* }, Y, N
  19. 57         str     r2, [r6]                        @ Save atags pointer
    ! T# f. t: d" h0 O# |7 B
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit( _* \. i2 R% u: |. H- k4 W
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values. C) c) f  x! o1 e" V
  22. 60         b       start_kernel$ Y4 ~0 l3 m$ a8 ]" @  C
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。# ^( H" I, P; }1 {8 H7 s% T
line 39,將__data_loc的addr放到r30 y+ r) [6 r& l/ p. c
line 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r75 d% _. _, |6 h
line 43~47,看看data segment是不是需要搬動。
: m4 ?1 f9 t3 @; I. M+ G( D: Jline 49~52, clear BSS。9 x( I: n. {9 t# H6 T& A

3 G! {3 v' {% f( j( _' p; d8 ^: J由於linux kernel在進入start_kernel前有一些前提必須要滿足:
) L" A8 A' u6 `. F$ |r0  = cp#15 control register
9 v6 F& f/ v' ]) [r1  = machine ID
4 N2 C9 i1 g$ g, ir2  = atags pointer
4 b) @8 h# d  h: ]" }( q% pr9  = processor ID
. w, G9 D% j  r/ b$ ^$ E8 f$ T3 a1 @8 D7 s7 y
所以line 54~59就是在做這些準備。0 K* o! w" ~" n  f
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)$ d, P6 \6 A/ S" o, N& U) x5 [

3 r4 x/ f: {5 X, D, `看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示5 R% X' r" q  s+ z2 [) c  {
我們真正的開始linux kernel的初始化。
& Y: e, u: b. _6 j  P! O) D像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。8 z% c8 P6 g7 a: o
到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。




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