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最後一行居然是
& s$ G7 a9 I/ R- S5 @9 }『mov pc, r4』9 c+ X2 J' Z" M! j
r4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!
! d: i& w/ p* K+ J; F. U) w1 X3 [# I0 k9 c. K# K8 ?3 i( X1 Y
所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。+ h5 a. ]# h2 B9 B: r( x

+ \- b/ q7 d7 o; \3 U有興趣的人可以看一下 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), I7 P! [( r+ V4 g1 h' ^# @
我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。, t( n  |8 s- Z
於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {
    4 X$ @, z9 l* [+ a! `
  2.      27         _stext = .;' q' N9 `! y5 m; P
  3.      28         _sinittext = .;
    $ F1 r0 o6 v+ _4 Q( q1 |5 B
  4.      29         *(.text.head)9 i! S3 O  ^$ W7 p/ ^
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"
    2 I! d; \* i; P* B9 O# [
  2.      78     .type   stext, %function3 Y, S" e: U. D, q6 F
  3.      79 ENTRY(stext)/ D& c1 w. V5 B/ Z6 m8 R% d
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode8 `  a! ^7 u; S+ D3 B
  5.      81                         @ and irqs disabled
    / ~* j. B. V% O9 D
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id
    : l  }9 W' A8 c, q
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    2 E( N+ M1 ~% _5 a7 V; a
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?$ M, E# G* \! E1 \2 W( B" i
  9.      85     beq __error_p           @ yes, error 'p'& }8 e& E* {) H4 H: x1 ]$ _7 m, E
  10.      86     bl  __lookup_machine_type       @ r5=machinfo
    " E5 U# H& A# x- p+ B0 Z- [! a
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?- Z# Q% F. @2 M6 ^+ M! B
  12.      88     beq __error_a           @ yes, error 'a'
    % U; e# D! Q# a$ g' M8 b
  13.      89     bl  __vet_atags
    7 G1 l  Y+ {4 {9 U1 X. f5 R! h: X. L
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。- J. U: i' o0 d& t9 j

9 u* k8 B( R* K/ m  T看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
作者: gogojesse    時間: 2008-10-9 03:32 PM
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。9 ~& @/ v* a# k1 @% s
( b1 S" q9 d) K
可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*
    $ n1 |: a/ ^9 J4 E4 |
  2.      60  * Kernel startup entry point.; ~* w  ^3 X2 L6 F
  3.      61  * ---------------------------
    ; l" L6 L# h3 C/ G  P( Y% k
  4.      62  *
    ! _' r* M; g8 k  @) w4 n
  5.      63  * This is normally called from the decompressor code.  The requirements+ c+ t: h! c( \# p
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,5 J/ b) w5 a- ]9 p' C
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。1 I! o6 |0 Y! R5 ^7 `6 ?0 e
line 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h). B; c! e  ^* D9 `1 t# e
line 82, 讀取CPU ID到r9
6 v, A7 L1 G: Y3 Y% |line 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"0 r4 f9 Q# m; X& P- [+ G
  2.      78     .type   stext, %function$ K; T: C3 ]3 w0 A3 x; ?
  3.      79 ENTRY(stext)
    3 S$ ]8 X3 a5 r6 s
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode. _' v8 [9 R2 ?4 s9 q3 r, f- V) s
  5.      81                         @ and irqs disabled) Z8 x; h9 x' H
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id" b& j& e5 T2 j7 q# a
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,$ s" z* J. D  w) _
line 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。
6 k/ c" V5 T8 t4 uline 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)
0 k$ i& s7 d' L% ^' w, mline l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。0 L; n3 g' X. m. X& [! h6 |
line 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S! X6 z  c! Y: \+ \+ Q" ?) J
line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。
8 M  ?- o. J/ B7 F$ c  `: l7 B5 _4 k/ S+ @& w; m) ]
__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' w! v5 J5 V8 L* C
  2.     157 __lookup_processor_type:
    8 g0 T* V! d1 Z$ _
  3.     158     adr r3, 3f
    6 o! O9 A- p3 f6 x
  4.     159     ldmda   r3, {r5 - r7}* w4 F& k+ L5 A- G( D3 x
  5.     160     sub r3, r3, r7          @ get offset between virt&phys  G8 I: \+ \: E3 p
  6.     161     add r5, r5, r3          @ convert virt addresses to( g0 E4 `# ~+ Z! e' X4 f! o6 m
  7.     162     add r6, r6, r3          @ physical address space
    , ~* _+ \! @( f" X
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask
    % n* H- C! ~4 N
  9.     164     and r4, r4, r9          @ mask wanted bits* t+ e4 ~4 l; l8 K$ o! H
  10.     165     teq r3, r4) x5 h% ~. |- }& l: j% {) u
  11.     166     beq 2f
    4 W" x% \, s, q: t  d* L2 E
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    , @, u6 M& P+ F+ U% Y8 u
  13.     168     cmp r5, r6: Z7 b& S" x0 z3 n. z4 {4 {3 W! K. i2 _
  14.     169     blo 1b& j. d  u1 r* }9 s0 c* ~
  15.     170     mov r5, #0              @ unknown processor
    ! h+ x& E" y1 F& s
  16.     171 2:  mov pc, lr
    & _# w1 j2 p% t  H

  17. ' e5 W5 j; f0 U; U
  18.     187     .long   __proc_info_begin1 l% T0 z) o% `8 {4 y
  19.     188     .long   __proc_info_end
      b7 k% w9 \" t3 ]; }2 u# X
  20.     189 3:  .long   ./ \) _  u- ?- ]+ Q" ^; P$ H
  21.     190     .long   __arch_info_begin, _' V" i1 ?% b4 f" q
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
作者: gogojesse    時間: 2008-10-13 05:20 PM
我們從 head-common.S返回之後,接著繼續看。, T' z9 m7 `+ b
4 r3 A  X8 D/ {  k! U* G- {2 A
line 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。: N7 h# p' b% q
line 85, 就是r5 = 0的話,就跳到__error_p去執行。
5 O! p; e0 r  J2 T4 zline 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid: j7 U3 M- k9 N! ~/ }
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?
    0 |/ p" q0 `% Z8 |
  3.      85         beq     __error_p                       @ yes, error 'p'' d  ?0 M# Z9 f3 T( L. l
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是* K) F5 X; {/ n% c1 o
9 E$ T" C5 C$ Z1 B
1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。
$ h1 Z7 C8 E6 m  s* l0 R
( d: ~4 h5 O+ E3 _- c' Y% L/ j2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */2 Q! e7 n$ n$ G0 ^/ ?
  2.      50 #define MACHINE_START(_type,_name)                      \
    4 {: b( e8 i9 q' c
  3.      51 static const struct machine_desc __mach_desc_##_type    \  W+ d6 o$ q3 [
  4.      52  __used                                                 \, h: }/ n! O0 r) x
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \
    4 b2 i4 q+ l9 m, {0 j3 m4 }5 {
  6.      54         .nr             = MACH_TYPE_##_type,            \* s' S7 f0 r3 w+ w
  7.      55         .name           = _name,
    7 V" p3 ^' K/ U$ a" [
  8.      56, F( C1 L0 j) R1 D" O2 P
  9.      57 #define MACHINE_END                             \7 J1 h2 e: [. \1 ?7 L* t6 S. R
  10.      58 };/ f' m, z' }. }6 C$ c
  11.      /* 用法 */
    0 [6 H' {9 N$ B) D& F0 t+ j9 B
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")+ h' }8 X) M+ `' j; n( B5 [) j
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    ' {  e6 N$ V$ z# u3 t
  14.      95         .phys_io        = 0xfff00000,: n0 J1 F, W) W' H. K. z& r
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,
    2 m* }( d' t- x7 W; l
  16.      97         .boot_params    = 0x10000100,
    2 O: H- T* z6 V6 ~* E  o- g& [
  17.      98         .map_io         = omap_generic_map_io,2 s0 w7 B; X! ^8 g4 b- l
  18.      99         .init_irq       = omap_generic_init_irq,
    ' a0 Z9 M' `  w# t- G* r! v
  19.     100         .init_machine   = omap_generic_init,
    . _2 [$ Q) S- g' \3 L6 z2 {' Z/ V
  20.     101         .timer          = &omap_timer,  [% Y% |0 y  _  S: M
  21.     102 MACHINE_END1 i3 c9 H3 Q" o+ n
  22. , p' z/ y  _' Z* Z
  23.     /* func */5 q) l$ F5 M1 [* J3 `
  24.     204         .type   __lookup_machine_type, %function7 I1 T2 G  o( n1 `+ A
  25.     205 __lookup_machine_type:
    9 k, Y4 T% |* k6 {; O* @& D
  26.     206         adr     r3, 3b* A2 W6 @. p; i' p3 j" e. q- Y. P
  27.     207         ldmia   r3, {r4, r5, r6}$ S; C: @' x( O+ W& k+ l* L, \
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys9 R0 x# j  T3 P0 `. j/ X
  29.     209         add     r5, r5, r3                      @ convert virt addresses to
    # U# j  x: ?' T( i' Z, d
  30.     210         add     r6, r6, r3                      @ physical address space7 a$ |2 P4 I: O
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    1 Z8 X! V& o) ~
  32.     212         teq     r3, r1                          @ matches loader number?
    # x) g: i  ^- T& C6 x0 A
  33.     213         beq     2f                              @ found
    ' N( M' t6 Y  i1 Q" |. u" Z0 ?
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    4 x, k9 I* r8 U6 t& P7 @& i2 {/ c
  35.     215         cmp     r5, r65 T# B' @  y$ k- ]. S; z" ?
  36.     216         blo     1b
    ! u% E6 \3 H  ]/ N/ u. i
  37.     217         mov     r5, #0                          @ unknown machine' F  B& ?; J; ^' K. ]. }+ k
  38.     218 2:      mov     pc, lr
複製代碼

作者: gogojesse    時間: 2008-10-13 05:56 PM
接著我們又返回到head.S,2 l" s1 M: H) q) v9 b1 N
line 87~88也是做check動作。* `3 P0 J0 u4 {' a1 v& ]
line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?
    * Z! }8 A2 b9 `( m, @7 D  I
  2.      88         beq     __error_a                       @ yes, error 'a'$ u% b% u  @0 V! k
  3.      89         bl      __vet_atags
    3 y( G5 z3 X: @& ?) A9 o0 V
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。& w6 P# v2 E+ e4 f# ^. s- g
line 246, 沒有aligned跳到label 1,就返回了。6 }( r) h/ F: \4 t
line 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
1 `7 a8 U  S4 M; e, [0 W& w* Rline 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。
' X( y7 w# U. J3 Y0 e/ S(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x54410001
    % m4 M# N7 T. u7 ^/ l8 C% n/ x. G
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)9 g0 c* V5 \& q/ ^) L& L1 Z
  3. % Q5 Z; w* D2 X) J' n/ p) M$ `
  4.     243         .type   __vet_atags, %function
    1 T1 S# A; o2 E( b* B, I. k
  5.     244 __vet_atags:  L& _7 z) x: b" j9 T3 M- c
  6.     245         tst     r2, #0x3                        @ aligned?# A. s4 ^4 Z/ ^+ P0 L3 ]+ q3 ?
  7.     246         bne     1f) \9 s+ j0 o4 F. u. r& O
  8.     247: Y8 w2 J7 b5 v. v' a! T$ g
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?( b$ D  C3 r7 \
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE
    ! Z$ O" d8 V* Z0 |: X5 `! ?9 g/ x
  11.     250         bne     1f2 ^9 T; R- E% s  p9 @+ `; R" t4 t
  12.     251         ldr     r5, [r2, #4]
    # l4 N( N7 Z9 \8 B& n3 |
  13.     252         ldr     r6, =ATAG_CORE
    1 j6 s/ _( m8 ?. C9 H" \0 [4 s
  14.     253         cmp     r5, r6
    : A, v, X* w, u; g4 Z' Y
  15.     254         bne     1f+ p6 V* A4 T9 [, ^1 g
  16.     255
    : j/ @, X/ ^  S1 i' Y% d+ y; _
  17.     256         mov     pc, lr                          @ atag pointer is ok
    + S6 {, k7 |, N; d
  18.     257- I& B- h: Q& `2 r/ V. E/ @
  19.     258 1:      mov     r2, #0
    $ i9 g0 |9 f. d# {7 C% x+ ^
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  
3 Y- c: c3 U, q+ @9 [3 uline 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)
% E  a5 c' n  C% z5 B哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
作者: gogojesse    時間: 2008-10-14 12:13 PM
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:
! j: l& ^9 f4 P9 X( `; H' x. W7 O6 Y. _/ g
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。2 P; {& P; G* U6 x3 `' p" H
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。
4 k: x" X6 `, Q5 [) q3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。
" r$ v* r$ M3 D2 l4 B5 A( L3 |( G& t5 b9 ~
以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。9 ]1 {9 `$ _( n7 r6 h7 Y

6 v1 F! P" N9 g由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
作者: gogojesse    時間: 2008-10-14 12:14 PM
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。8 Q3 d8 b: k- e0 J
% S( Q  [( C( b9 f- M  z
『產生page table到底是要給誰用的?』( v+ e. n# Q" P

5 Z! q+ z4 ^1 J8 L0 B6 s其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。
7 d0 v& D7 T! F; B8 m$ Q/ R+ ], S; A) X* ?
這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。
# P) b$ v- N5 O- n! l  |9 n9 v% Z& {" Y$ X
到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
作者: gogojesse    時間: 2008-10-14 12:15 PM
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。0 j# u- N2 Z7 ]& Q3 I( @

( t3 {- @8 i; I( c) Z2 U# a) w現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。4 E$ h9 p- K% I2 S! I# Y% s
/ |2 q# e: N0 K/ r  t  N9 N5 E
知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。6 }( ]6 e% G. _/ r2 z3 X  q3 V& ^
% j$ q# A* L6 E0 r6 K9 i
p.s. 字數限制好像變短了。   (看來很難寫)
作者: gogojesse    時間: 2008-10-14 01:56 PM
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
作者: gogojesse    時間: 2008-10-14 01:57 PM
現在,讓我們跳入create_page_tables吧∼  z0 @( c5 U- h/ Y
line 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。* e7 Y; c5 w6 S5 S2 x

+ Z' U- z# R/ S; J只是這個位址因為你硬體規劃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 */
    + d- S5 y5 P# z: h, L0 j, f* M
  2.      95 textofs-y       := 0x00008000! Y8 W: e  ^9 y
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */6 s" C6 W  k6 F
  2.      40 #define PHYS_OFFSET             UL(0x10000000)
    # ?" O& ?' k1 L, |- J: {

  3. . {+ Q# E* |) K! b
  4.      /* arch/arm/kernel/head.S */' H7 p% v; F  X; o
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)
    2 I6 o( {6 i! O! ?" L
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)9 o& f& v6 }1 m2 a2 D
  7. / z0 m2 S+ E, }
  8.      47         .macro  pgtbl, rd; }) T: z' {# _* g
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000). k+ @) a6 z5 [( h% b
  10.      49         .endm
    6 d& {1 N$ w) ^! O1 v/ e

  11. ! k4 V* D( p: i1 l( f8 }
  12.     216         pgtbl   r4                              @ page table address
複製代碼

作者: gogojesse    時間: 2008-10-14 02:16 PM
得到pg table的開始位置之後,當然就是初始化囉。
, R. e$ L2 y* \1 gline 221, 將pg table的base addr放到r0.: E6 K$ H% N6 E8 b5 Y; c
line 223, 將pg table的end addr放到r6.$ q8 F$ K/ I9 g6 ~+ `
line 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4
    2 @, b7 }" d, L9 q5 L+ M) |: y6 W6 l$ P
  2.     222         mov     r3, #0
    5 c3 ?3 ]. a. d) f0 j* I- R% _
  3.     223         add     r6, r0, #0x4000% p2 G; x! H4 i0 I1 l  h, k- j6 U
  4.     224 1:      str     r3, [r0], #4& V3 q3 x6 {7 h2 q4 G' d
  5.     225         str     r3, [r0], #4
    ) D( S! q/ G1 o  D
  6.     226         str     r3, [r0], #4
    8 B1 K7 y# ]  }% Z5 k# W1 l
  7.     227         str     r3, [r0], #4, h$ z: W" ?; T' {3 s- [
  8.     228         teq     r0, r6! I2 Q. _2 X& c+ N& J/ [
  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
問題怎麼填值??
$ Z' K; j. U* i; W% g% `拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。 : A9 j+ y* \( a- r

* G6 f# n$ u( {( Q4 m' {念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)
. k8 J  K, w) T1. [31:20]存著section base addr
& Y( }8 t" a8 |2. [19:2]存著mmu flags
% @" j0 o0 q  `. A+ A8 _# ?3. [1:0]用來辨別這是存放哪種page, 有四種:2 f! b! |  Z* t' d7 N
   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)
! S2 e4 R8 M2 u( |- ~/ X4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
. M% N) S. N4 Z  \, A: e
& T1 i7 T, G" u* Q' k7 m( ]來看code是怎麼設定。
7 ~8 r$ \5 U6 r! E. s3 c5 M) x& Y
% P7 ~! X* ]2 g1 R. uline 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。0 l7 a  a6 i% J' u  Q
line 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。
% H) x$ B8 R- Z4 G所以前面兩個做完,就完成了bit[31:2]。6 S5 u7 A+ @1 ^7 h) X/ Q' ^
line 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20
    ' ?3 y7 B2 L. ~
  2.     240         orr     r3, r7, r6, lsl #20/ |9 S% @+ y( j- g
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
作者: gogojesse    時間: 2008-10-22 07:47 PM
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼$ E: q/ k, H. l! F- x3 S: N8 |

9 m- |4 S( u+ O# }上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看
! ^7 i0 R) A; M# i. ?* y! Qline 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。
! v1 Y- I: @6 L; v% X: i( y6 f, q8 f: M, g- g! K  A
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) >> 181 n, _6 g/ r, @* g: _2 |
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! - \& [: Y. Q0 D0 S$ _2 I$ G* @9 t6 k
  3.     249         ldr     r6, =(KERNEL_END - 1)
    % B" N$ w5 Y4 D2 s5 B/ l# W
  4.     250         add     r0, r0, #4, l3 I# l' d) N1 o& g2 G8 \, ]
  5.     251         add     r6, r4, r6, lsr #18& \+ m% E" i* M. W
  6.     252 1:      cmp     r0, r6' m" }- Z0 c4 _( F: W/ b
  7.     253         add     r3, r3, #1 << 20/ D2 T2 D$ p1 |* s
  8.     254         strls   r3, [r0], #4' l8 }! _  L" Q  l  Z, F3 ?+ T9 Y
  9.     255         bls     1b
複製代碼

作者: gogojesse    時間: 2008-10-22 08:24 PM
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。( z" }6 N! @( C' e
line 280~283,將要 map 的physical address的方式算出來放到r6。
; X- |% D1 V- h/ N+ b3 Y9 y3 q  C3 lline 284,最後將結果存到r0所指到的pte。
) [7 N- I# `* h& `
' A" A* i4 ?; [' e  o2 Z- F& z以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。
3 E8 M, F' g+ R6 I: i2 a, X" @
$ P; E- |5 T4 c% ?- J' i# c) {9 Lline 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 18# @( S0 ]$ v, Z/ G! z- X# y
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)+ K3 ^" v' {$ q, G# M5 Q1 j
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)9 p  {% C' d/ C9 R; k* I
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)
    + v( c2 e+ a' P5 t
  5.     283         .endif. L5 w& J: ~; Z2 u, t* y
  6.     284         str     r6, [r0]( H3 P8 y2 Y" M8 E  t9 V) `, m
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
作者: gogojesse    時間: 2008-10-22 08:37 PM
自create page table返回後,我們偷偷看一下接下來的程式碼,
9 h+ M' l1 x/ Z7 n8 zline 99, 將switch_data擺到r13
. _+ |' w. q9 rline 101, 將enable_mmu擺到lr
" S, }( L+ N  S! oline 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去+ Z3 Q. l, C& @3 N9 M6 K

( }. A. b, u; x# G, _其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。
- v6 X9 f3 ^5 N/ @3 z
* O% _7 y$ N# ?5 ]* Dswitch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after
    % h9 P( M, d) Z* c- [8 O% g
  2.     100                                                 @ mmu has been enabled' m. C+ u- t: r% h! v
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    * g1 b) q7 g& i$ \% O6 b  d
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼

作者: gogojesse    時間: 2009-7-4 01:09 AM
老店重新開張~
, D! o1 Z  k/ P- J, g* }: R; l  v4 I, a3 h
花了一些時間把舊的貼文整理到一個blog  I, {7 a- H& T' d. r+ Z# c: a, n: C
有把一些敘述修改過
8 u, t) K- T; k( F2 M1 r/ l希望會比較容易集中閱讀7 ?' Z# ]2 T) g* O8 F2 p# C
目前因為某些敘述不容易
# \7 J( f7 e, @2 p還是比較偏向筆記式而且用字不夠精確  Z' K5 l3 ~8 Y9 r. s5 a1 D
希望之後能夠慢慢有系統地整理9 _* U0 V% F  ]" ~- O( h( s* w
大家有興趣的話, t% a- k4 A* @3 [  Q
可以來看看和討論 8 }8 r! Z" w- a4 F# f  u
http://gogojesseco.blogspot.com/
- g& U3 F' ?* o( I8 a6 f. y8 K) C1 g5 p
9 C3 G6 H# g4 F0 d4 g7 ~以後可能會採取  先在chip123貼新文章6 J3 E; v4 V& r: f* |0 |
慢慢整理到blog上的方式
4 d7 E7 z9 C2 g) {) v+ i# w, X因為chip123比較方便討論 =)
0 Y9 V! ^# d: c2 d7 f* ?blog編輯修改起來比較方便
6 d& t1 E/ r" ~  Z! [/ r閱讀也比較集中   大家可以在這邊看到討論
/ N" |1 F7 t$ a- }, b$ _, j& T然後在blog看到完整的文章 (類似BBS精華區的感覺)
作者: gogojesse    時間: 2009-7-15 05:07 PM
隔了很長一段時間沒update/ j& C, Q$ O. G9 O* \) J
之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after" D8 a* t+ }' z: c# o
  2.     100                                                 @ mmu has been enabled( o7 r, ~, g, O4 x4 T
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address: a3 p( C, b* {- p5 e, v% ^3 X# x5 L
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)" n; j6 C/ p. b
line 101, 將__enable_mmu的addr放到lr。(留作之後用)
9 |6 J  P7 f& uline 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, #function0 l1 z* c$ P% g% x, B# w
  2. 374 __arm926_setup:
    + j* A9 ?/ e8 F  e& U1 L
  3. 375         mov     r0, #0
    9 U" Z3 A9 k( d- o6 t2 h$ B
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4
    " F  \) Z9 w2 ?1 v9 h
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4
    * `8 _; l0 j! ^7 K! h, b' a. @! X
  6. 378 #ifdef CONFIG_MMU
    4 o- l! ~' z4 \3 a+ Q+ m  M
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4: i2 \2 v! P7 c- K, c+ y
  8. 380 #endif
    % L7 A' k+ w% H3 h

  9. ) }4 K, l3 [' v6 J6 z3 V+ Y8 V( }
  10. 388         adr     r5, arm926_crval
    ) H/ N( m  c  ~$ ]# t5 I$ O
  11. 389         ldmia   r5, {r5, r6}* r7 l+ Y4 c0 M4 F
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v4
    2 m9 z; F% Y2 H2 F/ P) j) C
  13. 391         bic     r0, r0, r5
    ! @- J2 m. Z* h9 {$ `
  14. 392         orr     r0, r0, r6
    1 s4 [  g( ]1 l" m) G- ^0 }! n6 U% o
  15. + d" Z9 R; J( y8 x
  16. 396         mov     pc, lr
    ' w7 i9 V& `* x
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,4 v- I2 Z, z  [' x
line 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。
# a+ k$ [/ d0 Y, Z! xline 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)
2 y" C( Y) \; |, [" m( Xline 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
作者: gogojesse    時間: 2009-7-15 05:29 PM
  1. 155 __enable_mmu:  t: \: O! `! u( F& U
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \* B4 E8 Q9 D+ c& F! C8 J
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \7 q# ~4 J/ |' c
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \* i" _( w( b) \; H0 i+ {
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    : k( X$ Y4 F6 S3 Y: r
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register
    2 ]. G4 {7 d/ G) V! p- W$ |
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer
    % Y2 T& ]% W- k9 h5 {" G- R
  8. 176         b       __turn_mmu_on
    9 Z+ C9 X) Y5 M' T# m
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)
" b* T8 ]4 v: E. ^line 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:) |; E6 j' z. x- ^; _  E
  2. 192         mov     r0, r0
    1 B" T' {# p6 o; L
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg2 N4 c1 q8 b: I3 P+ P2 k
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg% Z# E  t( W% s  M2 I5 [) I
  5. 195         mov     r3, r33 @9 T6 I- Y* l1 a4 t+ o
  6. 196         mov     r3, r39 j8 A# [0 P/ B2 }% X& F
  7. 197         mov     pc, r136 S1 G: ~- K* ]' L6 k" @
  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:* S1 g+ H  w) C* e
  2. 19         .long   __mmap_switched8 E( W3 Y' Y6 r' x* _5 p# f
  3. 20         .long   __data_loc                      @ r4* I0 b1 m# z6 |: s, S  W
  4. 21         .long   _data                           @ r5
    ' d% p: t. B. [/ V8 i' r
  5. 22         .long   __bss_start                     @ r6, u9 w: _: q* n( B( L0 L
  6. 23         .long   _end                            @ r7
    ; }, M5 L7 {5 l% D8 K5 ?: o( j
  7. 24         .long   processor_id                    @ r4
    6 h/ G2 T+ I6 S( P, q; b6 ^" w
  8. 25         .long   __machine_arch_type             @ r58 x, b/ F+ x% p/ W6 f' U, y
  9. 26         .long   __atags_pointer                 @ r6
    $ [  R! b' d7 p% i  l% i' t
  10. 27         .long   cr_alignment                    @ r7$ R3 x5 A) f- N, t$ f7 ^8 t
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp6 X9 d( i9 Q2 C. r
  12. 297 n; c% p; B' @  q
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
作者: gogojesse    時間: 2009-7-15 05:30 PM
  1. 39 __mmap_switched:! l5 \- F8 U; r- F: K
  2. 40         adr     r3, __switch_data + 4
    * z! y* h* j' u3 r
  3. 41
    ) e4 a5 V" ]4 ~) s
  4. 42         ldmia   r3!, {r4, r5, r6, r7}
    " l8 f  \6 W6 ^; a( B. X  C
  5. 43         cmp     r4, r5                          @ Copy data segment if needed1 g, [; `+ {. s- N' w' |1 ~0 b; @
  6. 44 1:      cmpne   r5, r6
    ) y7 C0 z" h' p$ V2 Y/ |4 R
  7. 45         ldrne   fp, [r4], #4
    3 Z# D& C6 n2 d$ p  l6 c6 ?6 c% i
  8. 46         strne   fp, [r5], #4
    4 E3 P) w: `% C: |) \" {
  9. 47         bne     1b
    ; H. ~( u0 \4 v; O  v; R
  10. 486 ~2 n! o$ B' T4 u$ P0 g6 A2 y% O. C
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)' n2 H; Q' p/ N5 Z. \# z" t
  12. 50 1:      cmp     r6, r7
    6 ?: M+ O7 D! L2 b, g
  13. 51         strcc   fp, [r6],#4
    % n8 Q9 ?5 m( {7 P1 q
  14. 52         bcc     1b
    ) k8 v" n2 M* p0 t( s& p6 Q7 U
  15. 53
    ( y7 A! r- B- g
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}9 Z) P* n& ]! }8 w; Q; i4 W
  17. 55         str     r9, [r4]                        @ Save processor ID) S- b  ]. a4 d1 W
  18. 56         str     r1, [r5]                        @ Save machine type
    8 k# b  B: G$ N! O+ x
  19. 57         str     r2, [r6]                        @ Save atags pointer
    # R5 e1 @1 ^' c( K0 e
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit
    # D/ E) F  m1 Y9 e& v1 x
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values1 J( @4 J1 s; Z
  22. 60         b       start_kernel
    % a8 q; [" Y2 z+ h5 X
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。7 v/ v  @' ?9 _/ J% r' h
line 39,將__data_loc的addr放到r34 [7 ]/ V) k4 k/ W% |
line 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7
; b' a, i- @% k$ X4 g1 m0 hline 43~47,看看data segment是不是需要搬動。
& ]6 e* F" \. G9 ]' A/ Vline 49~52, clear BSS。1 E: m5 D: s" P* u

  x6 p" G. Z; M: F8 e. V- V由於linux kernel在進入start_kernel前有一些前提必須要滿足:2 F$ C3 C; T( g6 V% \) g3 ^
r0  = cp#15 control register: C4 [9 j2 [0 `" C  x& Q
r1  = machine ID: x! W+ N  K" D
r2  = atags pointer; {2 e4 {! e) }: z, }. _
r9  = processor ID- P9 _! G( |. O
% m7 U( D' C# x9 `' l8 v! O
所以line 54~59就是在做這些準備。5 Y* v$ T2 W* g, _
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)
, Y6 C7 G/ H- l) y
5 S$ E7 W% w1 e" }/ ?/ e看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示
0 [5 C7 ?2 J9 R0 o# P' N我們真正的開始linux kernel的初始化。
. |- f5 i2 Z+ i8 O# g像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。6 M5 Q! f2 W$ t
到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。




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