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最後一行居然是
/ W3 K% f8 F& Z9 A( `『mov pc, r4』
4 x5 w$ J9 D2 a2 X( S# fr4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!4 Z; N2 J7 f4 a8 F/ Y0 p$ }6 W

8 z& I7 C4 K4 t  {, E# f所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。
8 M3 D6 d* B! `# {/ g! `% C0 E+ n
9 T5 d8 q. Y/ f4 B有興趣的人可以看一下 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)
+ d# @* y2 Z5 a' x我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。
8 {6 \7 X4 y" [7 M- W1 n+ B於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {& v; |5 l% \5 E5 E- a3 U7 P3 F# X6 R
  2.      27         _stext = .;( ?* U, @( @1 i* N  U( Q
  3.      28         _sinittext = .;# f5 ^; }6 h; e0 j9 c8 q
  4.      29         *(.text.head)
    2 M& Z# B- a  _8 k+ r' y' n
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"$ w1 [6 r9 M' D$ s% H
  2.      78     .type   stext, %function
    ) |1 g5 Z! L- _3 `% _
  3.      79 ENTRY(stext)
    1 M& m4 q6 l! `- _+ }: e& A+ u
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    / |5 n5 @6 G9 Z4 x
  5.      81                         @ and irqs disabled
    ' X9 w" L- ^6 D; B8 _
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id- ?" u/ \+ h" x7 }$ t
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    ) z' B6 z# j  q0 B" `
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?$ F% d5 _6 c7 @9 ^
  9.      85     beq __error_p           @ yes, error 'p'
    : I  C2 E) }7 @7 K
  10.      86     bl  __lookup_machine_type       @ r5=machinfo
    * i* U6 B! d% R! {8 c
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?
    8 T% g; h6 ~  A; B$ ~9 P
  12.      88     beq __error_a           @ yes, error 'a'
    ' L' n. |4 m$ W5 f
  13.      89     bl  __vet_atags
      [; N# D) i3 E7 c; e
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。
( n+ K7 h0 D+ V: E: r4 w: p  S4 f8 i1 z2 p3 _
看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
作者: gogojesse    時間: 2008-10-9 03:32 PM
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。
3 W. f# G4 `2 z( H$ {- x6 y& C. Y4 w4 r3 F( M" y
可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*( q' k: j7 b9 W- ~  j, w
  2.      60  * Kernel startup entry point.7 s) R1 o* Y0 p1 V
  3.      61  * ---------------------------
    5 U- l9 P" v3 m+ T9 }9 u4 ]
  4.      62  *
    ' [' n3 j1 ?2 _9 U; {
  5.      63  * This is normally called from the decompressor code.  The requirements% x( O  X) o* d5 Z
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,. E( M* I0 ~! I: m% ?
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。
3 e0 K& i! f2 Q4 T$ K6 B4 Bline 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)
: H3 {5 W- B3 pline 82, 讀取CPU ID到r9
' E0 i8 t& x& b0 }7 hline 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"
    3 j& n" u' ?' U& s6 U+ A: m
  2.      78     .type   stext, %function
    $ D. K% r! r' K
  3.      79 ENTRY(stext)5 d; Z& c! k' @6 z# V
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    ( V4 v( W5 t# U6 J& X( }( z
  5.      81                         @ and irqs disabled
    0 j# P8 F1 v. z7 x2 B6 Q# X3 r
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id
    4 O9 G/ |  a- N
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,
% d7 ?7 t2 p" N# M) q; eline 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。
/ m0 u* B; g" oline 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)
! t7 C/ \6 i7 P$ m) M0 d9 ], Oline l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。
' T0 U; t( [* R: \8 _/ A* ?+ [line 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S* E8 @; u& k, N* o+ H& u$ H! y2 u1 z
line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。
6 f: ~( X& |; b% k, S& P
/ N( H. f- ^, B__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
    ' f0 j1 ~3 X0 _9 O
  2.     157 __lookup_processor_type:8 u' P& f3 V3 s9 y; {
  3.     158     adr r3, 3f
    $ A' X, P' Z8 @) K  ^
  4.     159     ldmda   r3, {r5 - r7}$ B. B+ f- T/ M/ T* _, [
  5.     160     sub r3, r3, r7          @ get offset between virt&phys* H% V+ ]5 K# F8 k2 B, Q8 ]% x
  6.     161     add r5, r5, r3          @ convert virt addresses to
    0 B3 A# [5 T5 h: \2 n/ q7 `
  7.     162     add r6, r6, r3          @ physical address space2 w. Z# l9 G1 D% E
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask
    . P/ ]9 k9 w% k
  9.     164     and r4, r4, r9          @ mask wanted bits1 x; n6 v, |6 _6 f
  10.     165     teq r3, r4
    % ^( j! J- \$ J5 v7 I% {) X
  11.     166     beq 2f! F% z3 I8 T' J; O# x, A5 W0 G
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)8 C4 C4 ~7 k$ ^9 ?. M
  13.     168     cmp r5, r6
    ; m4 H: S9 }/ [/ T6 i3 M( H
  14.     169     blo 1b. N& d! x1 {7 ^( F/ `
  15.     170     mov r5, #0              @ unknown processor) w# g$ t" o3 U, j  Y
  16.     171 2:  mov pc, lr8 |8 ~( J; H9 ]: b6 `
  17. ; y: |6 `" ^4 r/ t
  18.     187     .long   __proc_info_begin  Y3 h0 B/ k4 Q0 ?) @
  19.     188     .long   __proc_info_end5 C" a' v# X9 x9 i
  20.     189 3:  .long   .
    ! ~5 `9 b) Z* a% K
  21.     190     .long   __arch_info_begin0 E$ C, Z& I1 |
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
作者: gogojesse    時間: 2008-10-13 05:20 PM
我們從 head-common.S返回之後,接著繼續看。
- ~" X+ F! l; g( K: p
3 w/ i5 C7 H7 Xline 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。
# |9 f/ z$ R# U, S2 V/ Tline 85, 就是r5 = 0的話,就跳到__error_p去執行。( Q' x! l, ~9 `  b  O- h- P
line 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid/ C1 X8 R9 M5 R! S/ K
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?& E% y# y0 U6 }& e2 w
  3.      85         beq     __error_p                       @ yes, error 'p'0 ~' U# J) u& p7 C0 L
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是5 q6 W* O8 R1 `9 L
" ]& ]+ c0 H; I4 F; J
1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。
0 D) V: A, |% T+ t' R3 ?% Z5 o) t, ]; A, F6 q' N, f6 t- }
2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */( f3 O: R. ^3 R3 S* \" C- V
  2.      50 #define MACHINE_START(_type,_name)                      \& W& o+ t( s3 G9 b7 G! K' ^. w
  3.      51 static const struct machine_desc __mach_desc_##_type    \
    $ `7 c$ }: y0 s* v& @+ N3 V5 e
  4.      52  __used                                                 \
    1 g' Q8 ?+ o1 w* @# H% T* }
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \& C0 Y$ a& C% c. f) E5 M% L' Z/ ?4 O2 Q
  6.      54         .nr             = MACH_TYPE_##_type,            \9 A1 N2 l! W' ?) o6 s8 ]
  7.      55         .name           = _name,- _, _( X% M& s0 q0 N: j7 b
  8.      56: y% v' Q6 q/ H
  9.      57 #define MACHINE_END                             \* d4 p, X: y- j
  10.      58 };- T* l2 s0 l$ x$ h" w
  11.      /* 用法 */% L0 S) F' }. e) ^$ k
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")
    7 ~9 U5 c0 i4 r
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */* U9 ~# {1 `# X" |! G* @; e# Q
  14.      95         .phys_io        = 0xfff00000,' B" y. _. N) n
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,. A. H  d4 j. l8 O* Q$ `2 y
  16.      97         .boot_params    = 0x10000100,: L* e9 [& o, j
  17.      98         .map_io         = omap_generic_map_io,
    " d: B5 P1 V. h* R1 S
  18.      99         .init_irq       = omap_generic_init_irq,
    , l2 n& o# ]( g  V
  19.     100         .init_machine   = omap_generic_init,/ D# H! e0 I. S# `% `
  20.     101         .timer          = &omap_timer,
    8 c& ?* T! A5 a3 A
  21.     102 MACHINE_END3 c1 p* T' C) ~6 e* N

  22. # N2 z  }% p" b- D% R, t
  23.     /* func *// t( V- D7 I  O, `: \, c9 F
  24.     204         .type   __lookup_machine_type, %function" B$ j! i) H$ t5 C( [
  25.     205 __lookup_machine_type:  F% F( g6 R: H) J
  26.     206         adr     r3, 3b
    3 Y# N* P& n0 a+ N3 d* [5 {
  27.     207         ldmia   r3, {r4, r5, r6}
    5 ^( E) |  i2 E( C8 x7 D
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys: Y+ ~6 d. C) k* l# A- Z
  29.     209         add     r5, r5, r3                      @ convert virt addresses to
      K0 Z# M" @- P3 W: ^: a
  30.     210         add     r6, r6, r3                      @ physical address space* a: d$ m& D4 _' I4 Z6 K
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    + Z8 E6 e$ ?% \5 C9 u* y
  32.     212         teq     r3, r1                          @ matches loader number?
    ' F( j3 r8 q7 U) H& ]
  33.     213         beq     2f                              @ found" }( e2 [/ }6 E, F/ T
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    5 |, M* I% R8 @) v* t
  35.     215         cmp     r5, r6
    3 k/ M- ]3 a$ E! Z
  36.     216         blo     1b1 }' d; t! ]1 U7 R$ s. i2 }: ^7 y
  37.     217         mov     r5, #0                          @ unknown machine0 A( g& A. T4 B8 R
  38.     218 2:      mov     pc, lr
複製代碼

作者: gogojesse    時間: 2008-10-13 05:56 PM
接著我們又返回到head.S,
4 D$ f- `5 ]! l6 `& vline 87~88也是做check動作。
# U. @6 {. R* U7 W9 m, }line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?  `/ J1 _2 I  ]. e3 ]4 g' w
  2.      88         beq     __error_a                       @ yes, error 'a'" H8 W7 @) M5 u6 E& b0 j
  3.      89         bl      __vet_atags, q: s% j) n1 z8 F5 p5 e1 X
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。
) a; U, r0 M9 g, eline 246, 沒有aligned跳到label 1,就返回了。) v7 y, ]% t2 d# y
line 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
9 [2 c; }. g* c% Tline 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。5 ~! U/ |$ J# w) t. j
(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x54410001
    5 ?/ H( {8 Q$ W* a
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)9 P2 N& U+ x/ z7 f

  3. 8 Q! u" h; k) A' _2 V& f, W
  4.     243         .type   __vet_atags, %function
    5 s! A. r, [+ ~4 T' c% D
  5.     244 __vet_atags:, k: R$ @& w5 J: o4 d, |/ U
  6.     245         tst     r2, #0x3                        @ aligned?& C# y/ D2 W3 v7 t
  7.     246         bne     1f$ Z5 i' x9 n5 k, ^
  8.     247
    ) H# [. R* A5 O; A
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?
      `' D: X# D$ p( c9 s, Y
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE- M4 r( p+ j+ J9 Y7 a" |8 S5 l
  11.     250         bne     1f
    0 H$ \' w/ N5 R
  12.     251         ldr     r5, [r2, #4]9 E& C* u( }' F4 T+ P" w
  13.     252         ldr     r6, =ATAG_CORE
    $ Y% l/ N! ]1 \7 G/ X% Y8 p: f0 i
  14.     253         cmp     r5, r63 }2 M4 G- I% G9 ?4 [
  15.     254         bne     1f2 f' p# }1 n( r* R' \5 d% q0 R* A
  16.     255
    & h6 @1 |6 @5 C) b1 `& h2 I
  17.     256         mov     pc, lr                          @ atag pointer is ok
    " L* N( ]7 d" |# Z  ^
  18.     2570 s5 D: v# ?! `  W( F
  19.     258 1:      mov     r2, #0
    7 F( ~' g: Q/ t( I6 V# ]4 @$ I
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  ) d2 _0 `$ L3 N# w' H. ^) h
line 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞); m1 m* u2 P2 l' M9 Y7 w
哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
作者: gogojesse    時間: 2008-10-14 12:13 PM
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:, ^: O7 ^: s5 `! z) |8 J

$ Z2 ]6 ]2 w3 X; K9 G: U4 |/ h1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。8 m$ K8 J- Y: j8 V' p8 z. P
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。
! f1 p7 Q) t/ X+ C; J* i3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。
0 y  g: L3 E# S$ Q' M# S- @" s( Y) D# R" y9 Z7 T; }/ U* H! g+ k; Z
以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。
; _& \9 F8 J) t' U' J( i* U* d: P. X! G5 J
由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
作者: gogojesse    時間: 2008-10-14 12:14 PM
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。
; C( X. f/ B+ @" i( I) B7 W2 _& E# p8 |' [( O' O4 K; y2 e$ W  u
『產生page table到底是要給誰用的?』4 `' D. q; h& M' \: w/ ?8 B, B8 V* V

! X, \+ f  s" p3 P1 n其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。
4 ?# }/ u8 j7 U; M
* x9 g; }: X3 B3 v% d/ U' R這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。7 u# `% S+ C3 f+ E$ N
. \' V" u' D$ p% P" Z# P5 K, r
到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
作者: gogojesse    時間: 2008-10-14 12:15 PM
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。& |# }; B; Q2 U# v

' z5 a. _4 ]1 D9 P3 N4 H' n! O; `現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。. |* C; @+ P4 {/ b* W/ o! }/ S. t
8 ]! q+ A$ U1 ], M" K
知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。
0 e7 H. k8 V) P$ G  W3 |, W! ^$ }
+ d# |! ~  S. j4 h- lp.s. 字數限制好像變短了。   (看來很難寫)
作者: gogojesse    時間: 2008-10-14 01:56 PM
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
作者: gogojesse    時間: 2008-10-14 01:57 PM
現在,讓我們跳入create_page_tables吧∼7 F; O, r( I8 q. l
line 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。
9 `! g7 l  q7 |# Q- ?$ r4 C0 H& T8 W8 X8 E% y  l8 E
只是這個位址因為你硬體規劃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 */
    % {% p3 p0 }2 ]+ A2 S
  2.      95 textofs-y       := 0x00008000
    ) R4 z( ?% c8 [( k/ A
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */8 C* T4 u, V; _! E! F9 F  e0 t
  2.      40 #define PHYS_OFFSET             UL(0x10000000)' C$ o0 \# q; _' q# _" t

  3. # g% b' J+ G0 I. S$ b: p3 u
  4.      /* arch/arm/kernel/head.S */# `! h& H9 P) B6 q- ~3 n% o, b
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)# I2 @: H0 W; D
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)8 l, R; Y! x; U: e
  7. % A2 [3 Z5 v; m
  8.      47         .macro  pgtbl, rd8 [) P) @+ ^7 e  [$ p! \- Z
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)
    ; ~; e* O* b9 q/ ~
  10.      49         .endm
    6 P) E# j2 H; R+ i0 G
  11. - e' e4 X' W( T6 [# D
  12.     216         pgtbl   r4                              @ page table address
複製代碼

作者: gogojesse    時間: 2008-10-14 02:16 PM
得到pg table的開始位置之後,當然就是初始化囉。' O/ Y$ n7 f. X( O8 m
line 221, 將pg table的base addr放到r0.
+ v: _! g* i6 n3 s' _( qline 223, 將pg table的end addr放到r6.8 m( D/ O5 k' E) F
line 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4  U3 W+ s5 z) c  V  a
  2.     222         mov     r3, #0
    - ^5 j' N- I: r( Z  r9 O1 b
  3.     223         add     r6, r0, #0x4000# a. I) l9 z8 v( f& C% p$ W# w& b
  4.     224 1:      str     r3, [r0], #4" o; c" P! @: p3 g2 J
  5.     225         str     r3, [r0], #43 i' `! {* [) k% H( B) x# `! z
  6.     226         str     r3, [r0], #4$ Y/ J2 V/ S4 z* o4 s
  7.     227         str     r3, [r0], #4" Y- B0 p, t3 s9 X: ^- T
  8.     228         teq     r0, r6
    8 X* q5 q" {0 Y
  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 T- z5 O1 B. H拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。 " o# Y/ \8 j3 v2 @2 H. ?

6 w- r$ ?; r- Z* Q. e念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)
1 z. u& |0 H7 W" h: [) z/ O6 Y$ s1. [31:20]存著section base addr  h' \1 a- o: v2 M& k
2. [19:2]存著mmu flags& ?! c: h" P9 Y
3. [1:0]用來辨別這是存放哪種page, 有四種:
  ]' h5 R# o/ @* |   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11); @5 X. H* r! n" H! G# r" K. F
4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
6 A6 m2 p3 ?1 R5 B8 ?
$ i, H5 J# Y8 F" j* @  t" @. d來看code是怎麼設定。
, ^& C4 J# U( |; H& D1 `$ u# F$ I) y  b% a9 O/ O+ t& r6 l
line 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。- s8 G8 F; A' {3 k
line 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。( N1 Y  I0 l1 g8 ~
所以前面兩個做完,就完成了bit[31:2]。
. S" s, t( K5 N1 vline 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20
    * [+ K6 _5 k7 S/ o1 U5 k
  2.     240         orr     r3, r7, r6, lsl #20' _" |7 I2 G* c) D3 r7 U# Z7 u
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
作者: gogojesse    時間: 2008-10-22 07:47 PM
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼
  [5 b& X5 H- d9 a" n8 j9 }' [3 g( L. q( z2 C3 }
上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看
* t4 _9 |$ \5 o  J8 l0 xline 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。* a: e" r; y) s8 u/ ?8 U
2 @( a  X$ ^1 v0 |' w
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/ c- L$ y) }( Y, r
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! 2 W) ]* t8 Q7 m! x
  3.     249         ldr     r6, =(KERNEL_END - 1)8 q8 b5 W2 I7 w, k& X
  4.     250         add     r0, r0, #4
    # g" E3 H% K- [% Z9 _+ i5 E
  5.     251         add     r6, r4, r6, lsr #18
    ! H, m$ X6 m! A; H. z) i
  6.     252 1:      cmp     r0, r6
    - b  i# ?5 o8 w1 ~6 c! E3 x' L
  7.     253         add     r3, r3, #1 << 20
    ( k( \7 [; X/ q3 M
  8.     254         strls   r3, [r0], #4) m0 `0 h' @: N2 t
  9.     255         bls     1b
複製代碼

作者: gogojesse    時間: 2008-10-22 08:24 PM
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。
* D" N) `$ V5 N/ p, ]: Lline 280~283,將要 map 的physical address的方式算出來放到r6。
3 U6 l5 i8 x4 `! s! d8 s0 K, Yline 284,最後將結果存到r0所指到的pte。1 W& j0 l4 ]/ X1 W! ^  G
% \7 H) D  B1 l! n) g/ ?
以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。& a7 z6 S# z; L
4 C$ B5 x- t! r# V6 H2 n* u
line 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 188 l% U) b& ^; f* B0 Y
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
    2 `  ]0 O6 b" S7 n2 L8 L
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)
    1 \( F; f# t( e( l
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)
    5 F! d3 A% m" M/ X! [
  5.     283         .endif
    & b% w9 P% u( P
  6.     284         str     r6, [r0]9 @$ y! b: @/ g6 Z1 h" \
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
作者: gogojesse    時間: 2008-10-22 08:37 PM
自create page table返回後,我們偷偷看一下接下來的程式碼,
" f* b0 Q. h3 L4 eline 99, 將switch_data擺到r13
. v+ H+ A( b2 _, B) `line 101, 將enable_mmu擺到lr
5 N  o) E. J! n$ Y/ v  z* ^' yline 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去  E& G9 \8 G+ b# N# m4 [% \

7 f. X0 N: V4 \& e其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。 ' A/ c, s( n0 V, y0 \" T4 T

7 K3 s9 Z2 c( K9 D% `switch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after! b/ |! l3 G# I- v9 |' ]
  2.     100                                                 @ mmu has been enabled* Q! z% p( ~" Y, _1 D6 g
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address3 s6 c7 ]' c9 b) E  b  F1 r
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼

作者: gogojesse    時間: 2009-7-4 01:09 AM
老店重新開張~  [2 J6 D2 A9 m9 m$ v
0 e7 E2 Q; H7 K6 {+ I  F" e0 d1 }
花了一些時間把舊的貼文整理到一個blog
9 @; N( A5 `4 b4 r6 x, y. y$ |' F有把一些敘述修改過5 ^9 j' @- g+ f
希望會比較容易集中閱讀, ?! Z/ ^6 Z$ A9 x( P) f
目前因為某些敘述不容易$ g4 P- n. K! A/ O: C1 O! |) u7 V
還是比較偏向筆記式而且用字不夠精確
# _* v, k- l+ Q7 J  B9 T6 g希望之後能夠慢慢有系統地整理# T9 q4 `& V4 y! h: M( b
大家有興趣的話- Y6 d: n1 z* C9 x  I
可以來看看和討論
0 c. Y; Y5 U/ Q' Shttp://gogojesseco.blogspot.com/, b5 I- h: ~3 h; w3 X2 e
1 C) j7 w' G) K+ V( k
以後可能會採取  先在chip123貼新文章' d: @/ o, u- S& \; o4 S0 q
慢慢整理到blog上的方式6 c1 b7 p* Z  R
因為chip123比較方便討論 =)
8 U! ^8 ?- z7 b5 Y) [blog編輯修改起來比較方便
" v5 `, J3 v$ C4 ]閱讀也比較集中   大家可以在這邊看到討論
  t# H0 @+ x6 T8 S, x$ x然後在blog看到完整的文章 (類似BBS精華區的感覺)
作者: gogojesse    時間: 2009-7-15 05:07 PM
隔了很長一段時間沒update3 O4 |" T; n5 [4 H* n6 ~
之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after, ^' U& S8 }4 H  K, I
  2.     100                                                 @ mmu has been enabled" P* X( J# L4 _- K# q2 ^7 U
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address* V  Q3 f! C; c6 A9 }
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)
5 U7 i6 J3 q0 u- Kline 101, 將__enable_mmu的addr放到lr。(留作之後用)
) [: u7 a5 ~! Cline 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 [$ E- r' ]* H! l& a
  2. 374 __arm926_setup:  a! u  W/ e/ G
  3. 375         mov     r0, #0+ P/ W# V! ~- S0 j: p# a9 o
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4. G* B. s; ^) p
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v47 R/ n' d$ y& X: k/ ^, Z5 I- s
  6. 378 #ifdef CONFIG_MMU4 P! l: s1 k, {, E- x9 a  A% X
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4: ?. p0 `- ~. d
  8. 380 #endif
    # b2 ^* c% q( a5 \, B; g) Z4 m$ S( |' e
  9. * N( N' B8 y( g! [# ?3 R4 v* F
  10. 388         adr     r5, arm926_crval
    & U$ |6 Q; j2 t/ i# U
  11. 389         ldmia   r5, {r5, r6}4 e/ c' {5 t& }: Y: V3 R
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v4
    # C0 P$ [; C; `9 ^+ o6 C0 z
  13. 391         bic     r0, r0, r5
    ; |" _0 u. |9 @3 X! r) |! n! W$ Y
  14. 392         orr     r0, r0, r6
    ; Y7 ?3 c9 n: H' L' Z' {8 Q$ U3 M. {# T6 ?

  15. 9 U/ Z; n: t6 x1 ]
  16. 396         mov     pc, lr/ t' c* q# q* M
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,% S+ {2 B$ [% U+ G: V
line 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。6 N! R3 G2 d3 G$ s
line 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)4 L) B; O6 |8 N) E  U7 n9 c; Z9 [
line 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
作者: gogojesse    時間: 2009-7-15 05:29 PM
  1. 155 __enable_mmu:
    1 }$ A  m* w. V
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \0 D6 Y; I& f* F  C0 G; [
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \9 q+ N* t5 R" H+ W& s
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \1 R. v$ y# O& p6 J0 B
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))8 q( F: a( v& D
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register- s: S9 J, y0 u) l- g3 I/ G
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer! w0 E1 j7 v- B+ W" h1 r5 ~
  8. 176         b       __turn_mmu_on: B# [" s+ C' C8 o& ?
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)
; L6 m! Y* I% ?. Q2 L) C: Yline 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:
    2 V; M7 d4 C7 X: j* Y: X+ m
  2. 192         mov     r0, r03 I# d* v# p1 k/ A) S
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg
    ' N1 q5 t6 k: J/ ^' k' @( D
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg
    ' m  l; `+ A  U3 u3 B' B$ j- u' x
  5. 195         mov     r3, r34 ~1 v2 K6 R# M& r/ Q' T
  6. 196         mov     r3, r3
    # f$ o/ [5 m4 O
  7. 197         mov     pc, r13
    ( L- ^. r  C' M9 O) y
  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:
    ; b, Z: M8 G: A) o; k1 U
  2. 19         .long   __mmap_switched5 F3 u& c6 t  ]1 V, M
  3. 20         .long   __data_loc                      @ r43 r# C( j! i& K
  4. 21         .long   _data                           @ r51 o9 h, v& n6 N
  5. 22         .long   __bss_start                     @ r6, g1 F* i3 m9 U* s$ R1 w3 f  y5 R" |
  6. 23         .long   _end                            @ r7
    ! d3 ~1 J& Q! @: t* u
  7. 24         .long   processor_id                    @ r47 r& e! i( S% r% S
  8. 25         .long   __machine_arch_type             @ r5% m: ^5 k1 O! a4 A: }" a
  9. 26         .long   __atags_pointer                 @ r6
    9 i' v' v0 p8 X* m% x
  10. 27         .long   cr_alignment                    @ r7
      Y0 \* J. n, W
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp
      A7 }8 H! A: d5 l
  12. 29
    ' L; A, B% u( H
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
作者: gogojesse    時間: 2009-7-15 05:30 PM
  1. 39 __mmap_switched:
    / A9 N- l( P- V( ?4 c
  2. 40         adr     r3, __switch_data + 4
    " W) f+ y) D: s+ Y6 m: P. ^
  3. 41+ k; k2 e5 @. I9 x( `: @) f6 j
  4. 42         ldmia   r3!, {r4, r5, r6, r7}
    + G2 L' V; Z9 \
  5. 43         cmp     r4, r5                          @ Copy data segment if needed
    9 |- e* |7 ~, ~5 @- P. ]
  6. 44 1:      cmpne   r5, r6
    7 t9 L& b% d; H& k" ^! [
  7. 45         ldrne   fp, [r4], #4
    0 k: x" z& E4 [0 u) F6 u
  8. 46         strne   fp, [r5], #43 z( \3 z% \" |2 T
  9. 47         bne     1b  G: {; b  c* d# G) s
  10. 48
    8 e/ \3 S5 e0 [, K; B4 r8 P& L$ {
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)
    , v. e+ k" s* W( D2 [% |
  12. 50 1:      cmp     r6, r7! \7 f' E: _3 p/ r% D3 L1 I; }: h
  13. 51         strcc   fp, [r6],#4
      Q( D0 B! o  V
  14. 52         bcc     1b
    ) b( i5 V% v6 `, q4 Q' V, O
  15. 53; O$ c7 k5 j* f6 j8 ~
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}( L$ X# K. Z3 j- k, D. i; D
  17. 55         str     r9, [r4]                        @ Save processor ID
    9 a1 @$ ^$ j6 W  n: t: }
  18. 56         str     r1, [r5]                        @ Save machine type
    / p. i. i) X( {- k
  19. 57         str     r2, [r6]                        @ Save atags pointer$ R" t" p7 f" ?7 o" x2 s) z
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit# y4 @/ i7 N* O  h0 N" @. @+ ~  z
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values8 F+ {; i0 M& p4 p/ q
  22. 60         b       start_kernel
    6 f5 f8 t# B& h; f7 B, M
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。6 G+ `8 z' m! U
line 39,將__data_loc的addr放到r3
9 o* l8 x& }/ M2 P7 }line 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7( A" P9 u2 G/ w: Z  I! ^8 x9 Y5 h
line 43~47,看看data segment是不是需要搬動。
: I  a4 i, N4 H- O; |% yline 49~52, clear BSS。
& V& A$ K* p1 R+ v  j* n
7 ~# l) t9 E$ i由於linux kernel在進入start_kernel前有一些前提必須要滿足:
/ C$ W- [' h2 R, ^r0  = cp#15 control register
. x0 w' h6 o3 b) w% Xr1  = machine ID3 j; Y- t- X: v  e6 s' S
r2  = atags pointer) |5 D+ B# v7 s! z* t
r9  = processor ID
+ E' z5 ?" `% P6 G+ N$ U$ Z$ T. A9 G0 u7 Q1 f
所以line 54~59就是在做這些準備。1 Q/ v  |, T  B: C2 u
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)  N* n# z  C. N7 m# Q* f
0 n$ e, `  J, F
看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示8 i, R. b4 c/ j: {
我們真正的開始linux kernel的初始化。
. @; R. R4 K! G. Y; J* K3 n0 F像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。
7 N9 |$ P. ^- N& n7 o' H到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。




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