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最後一行居然是: t' k; \( H2 P' a3 H( Z
『mov pc, r4』2 q$ X! B5 h7 j8 R( N! A0 W
r4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!- V1 N8 L+ o: ?& b2 \3 A9 B
. i5 C6 X- v9 J& ~7 _0 K1 R2 R
所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。
  C1 R) V- J9 ]. _3 U/ m' X5 g# K- Q) \1 c6 M7 X0 J6 F4 c2 O
有興趣的人可以看一下 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)7 u3 L9 T# \% O* H
我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。
, u! X6 k6 H5 Q4 B3 g# j於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {/ X7 \1 Z) [$ a2 h5 U) J4 t
  2.      27         _stext = .;% U) K' L# t" o" l: X. u) ~8 L: y
  3.      28         _sinittext = .;9 E2 I! X; a/ G% t
  4.      29         *(.text.head): |, ~5 a) B" @0 V$ U" ?2 g/ @
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"9 J8 E4 C" \$ D/ x- y
  2.      78     .type   stext, %function4 e1 m* }" U/ n& ]
  3.      79 ENTRY(stext)
    & k% d5 L' d" R5 Q2 t
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode- n3 J+ P7 I4 V3 }# t9 x
  5.      81                         @ and irqs disabled3 z/ F( n. [' q3 `$ c8 d
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id1 W7 f% j  l) C6 `3 w" t5 h
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    2 Z( c% U4 R' S+ x5 W( H
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?. b( h! t5 I$ ^3 s
  9.      85     beq __error_p           @ yes, error 'p'1 a, p& v) I* j& z# W9 x8 n
  10.      86     bl  __lookup_machine_type       @ r5=machinfo
    1 m5 U/ q# ]/ ^9 U3 n
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?9 c) P2 M  j' g6 b9 g& u
  12.      88     beq __error_a           @ yes, error 'a'
    + T! r: f, N4 y; l# ^3 V& t0 |6 L" J
  13.      89     bl  __vet_atags
    # C( M5 E+ Y- g( E
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。3 p* `4 B- E8 m& x% [% T
3 Z' u7 W9 W  v9 T% B
看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
作者: gogojesse    時間: 2008-10-9 03:32 PM
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。1 D2 Z+ j5 x9 l& n
& C; ~: I9 `: o' D) a
可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*/ [6 W, ~7 X6 B  O8 Y
  2.      60  * Kernel startup entry point.
    . K6 Q. J/ [( L% l
  3.      61  * ---------------------------5 k* D4 I" @' e1 |% U. h
  4.      62  *
    - S' t% S) m- f
  5.      63  * This is normally called from the decompressor code.  The requirements
    * t$ s7 Z: Q7 w- g
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,' N+ [$ i' Q* W; |5 e- F
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。
0 F; ?7 G- @* L) i9 U, v( `% b$ gline 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)5 m  y9 E  r& A4 z2 S5 B+ ?
line 82, 讀取CPU ID到r9
$ G6 s  ]. h; S# d# \" yline 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"
    : o% v+ M- q6 }" O
  2.      78     .type   stext, %function5 U# @& g$ O# T6 a. d
  3.      79 ENTRY(stext)6 G1 W3 L- j  o- n2 Q0 x% {
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    , Q: l9 V( ~" d) k& z+ D# j! z: `3 K
  5.      81                         @ and irqs disabled+ A8 m7 s; d4 j, a0 a% \, B
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id) t) Z1 }. l1 U" k
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,
$ ~" T7 R: c5 R: r7 a9 Hline 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。. X0 f1 }# J4 T6 @! d& m0 y" O
line 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)* d8 j" w+ V8 D4 N! u/ \
line l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。
* U: a4 ?: z) bline 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S' M7 R) i' H3 C3 ]! z  h3 }, O
line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。
6 B+ E+ ]" B, R6 ^/ c+ h9 E: R9 i; J$ }) P8 n
__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
    - t3 C: g5 S( z2 \
  2.     157 __lookup_processor_type:% j% r4 e# Y! H8 f$ X' t
  3.     158     adr r3, 3f* v: q2 x5 h2 m+ [* {' [3 P# X
  4.     159     ldmda   r3, {r5 - r7}
    4 {9 i1 o* D% |; d; V8 |$ t- G
  5.     160     sub r3, r3, r7          @ get offset between virt&phys
    * a. Y+ P$ k* b# {) w& y# x
  6.     161     add r5, r5, r3          @ convert virt addresses to
    * X0 \7 b& z' ]: \) s( o8 y
  7.     162     add r6, r6, r3          @ physical address space
    9 X3 _' `& ?) p& j. b) f
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask
    6 E" L7 p4 A' n: T
  9.     164     and r4, r4, r9          @ mask wanted bits8 l( e7 V1 g- Q8 w$ B# d
  10.     165     teq r3, r4
    8 X4 Y% k8 [( M9 G8 Z
  11.     166     beq 2f
    $ u2 o; x9 w# k+ P2 E2 {3 F. V
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    ( \3 ~' U3 Q( }+ u, G6 A  a
  13.     168     cmp r5, r6
    : ]+ X' `- h7 d9 N1 o9 B
  14.     169     blo 1b
    , A/ U) v4 B6 @2 A9 Q
  15.     170     mov r5, #0              @ unknown processor9 g( w0 `" i4 n) |
  16.     171 2:  mov pc, lr7 p0 _  b8 O4 E) |  M: X# m6 W

  17. & {$ G, I8 X! `  h& S: v
  18.     187     .long   __proc_info_begin
    9 w( N8 j+ u% E; e$ b6 G
  19.     188     .long   __proc_info_end
    , S3 q+ ~$ b5 t8 C6 d
  20.     189 3:  .long   .
    4 i5 i9 _& H3 ~* h8 ^- \3 y/ p7 X
  21.     190     .long   __arch_info_begin
    ; T+ e, j+ ^: F5 V0 r) W
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
作者: gogojesse    時間: 2008-10-13 05:20 PM
我們從 head-common.S返回之後,接著繼續看。: `& ?, V( ?) O& W4 E3 P3 _

5 x) r* j3 A& G  Q; Yline 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。# z/ B" y9 c& s( A, {- L) q1 P' s
line 85, 就是r5 = 0的話,就跳到__error_p去執行。& h3 I2 }( D) L) W. u& L) g
line 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid
    9 W7 g4 @0 V. l$ W
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?- e' N& m# \3 K
  3.      85         beq     __error_p                       @ yes, error 'p'
    1 J. h9 y, u! |. V. X% O
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是4 J/ @; E) U$ A6 f7 Q6 N) I/ j1 b" e
1 `2 j9 z! a2 I* t# U# `/ t
1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。8 V9 o! O6 w9 W
$ f( J5 |# S+ W0 `, ^
2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */
    4 l9 Z- _0 K% o2 h6 ~+ z
  2.      50 #define MACHINE_START(_type,_name)                      \
    ) C& u: p- r1 M
  3.      51 static const struct machine_desc __mach_desc_##_type    \
    % W, y  t/ N) \. ?# L: T" `
  4.      52  __used                                                 \
    ' ?- W# Z- k9 \$ j: L  o
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \# g3 R$ r) V# Z9 U5 p
  6.      54         .nr             = MACH_TYPE_##_type,            \  W" }  b" J% L
  7.      55         .name           = _name,. ~! x1 m; O3 C: ]
  8.      56
    % _8 }. x9 ^  f7 \* C
  9.      57 #define MACHINE_END                             \
    9 U  N% M6 \) i
  10.      58 };
    2 S& A( i* ^1 f  D. G8 t# H
  11.      /* 用法 */4 X$ }6 M$ x  g5 a1 `, T# ?
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710"), G0 C! h$ j' e8 a0 H# I$ K
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    5 x5 x8 ^5 ~: Q, L- \
  14.      95         .phys_io        = 0xfff00000,: H% ?" s* y2 }0 Q0 v! z( {
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,
    8 a) d) Z0 {# j, f+ _4 h
  16.      97         .boot_params    = 0x10000100,
    ' e: Z3 x  c5 ]& D: N
  17.      98         .map_io         = omap_generic_map_io,
    1 n- w" m+ d) g5 @. L8 u
  18.      99         .init_irq       = omap_generic_init_irq,
    ; m: Y/ L; |) n$ J
  19.     100         .init_machine   = omap_generic_init,
    / b; z' d. e8 E6 [  y
  20.     101         .timer          = &omap_timer,
    3 d/ T2 b) |! _; Z% I1 J$ W
  21.     102 MACHINE_END& e- ?) {" }8 p7 f2 K: ^7 d
  22. : Z4 Z2 C4 F4 Z4 Q' X+ k( |# m+ C
  23.     /* func */
    1 u% Q+ f7 i3 Z
  24.     204         .type   __lookup_machine_type, %function
    # m' {2 s6 z6 L& |1 {- K
  25.     205 __lookup_machine_type:
    : B7 }* p( q$ j3 o8 u! B# a- e5 H
  26.     206         adr     r3, 3b, X6 [% V+ l6 R/ @$ F; \* r8 O, J
  27.     207         ldmia   r3, {r4, r5, r6}
    7 E- ^* U( c7 z9 J2 r
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys
    * [, e' z; S1 B2 |1 |! n* Z- |$ R
  29.     209         add     r5, r5, r3                      @ convert virt addresses to
    * W+ `8 L* m; d+ _/ w( V8 C4 u( M
  30.     210         add     r6, r6, r3                      @ physical address space
    4 c) C! J4 C: n2 L
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    ! @4 J/ [  _" [( S
  32.     212         teq     r3, r1                          @ matches loader number?
    9 W  c' Q+ F7 i- \, `
  33.     213         beq     2f                              @ found
    % P4 W( l2 K7 J" Z: g) l! ^: d
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc6 [$ l$ V& y& `8 X7 E# n) t+ U
  35.     215         cmp     r5, r60 q& D- D7 `% {* C8 w
  36.     216         blo     1b5 n" N/ r: K% G: d
  37.     217         mov     r5, #0                          @ unknown machine  U! @; N0 i' H0 v: q5 e2 l1 H
  38.     218 2:      mov     pc, lr
複製代碼

作者: gogojesse    時間: 2008-10-13 05:56 PM
接著我們又返回到head.S,
- _; f) f$ `$ Q) Z6 pline 87~88也是做check動作。- z1 U/ s0 ~" J' ^
line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?- d. u. [4 ~* i/ [$ u( ~5 s
  2.      88         beq     __error_a                       @ yes, error 'a', v, {( W$ I" d  C$ `. S
  3.      89         bl      __vet_atags
    : J/ [9 ~# P" x. `1 I  G. z
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。
$ @+ a' r* \2 a) y# |+ [line 246, 沒有aligned跳到label 1,就返回了。$ A3 e) L/ V# J  h% c
line 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
( s7 w" W8 M/ p6 [9 o1 x7 {line 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。5 c9 `/ y( {& n
(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x54410001
    , f8 ^0 g2 H6 m( M) e+ S  D
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
    ; |3 W6 {* M. g8 i

  3. " ]9 g3 z$ y# p6 D. r; a7 i
  4.     243         .type   __vet_atags, %function: O' l5 i' H+ q
  5.     244 __vet_atags:$ O) }* j2 ?) E
  6.     245         tst     r2, #0x3                        @ aligned?
    ! p' ^$ L/ X! Y( _) G) R7 j
  7.     246         bne     1f$ d& i" L8 n  W2 Q
  8.     247
    * R" W' h/ a( f8 b! F
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?, s2 K; s" u6 R: w) N
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE6 d& V, R6 e, ^( J* Z% W
  11.     250         bne     1f
    . V! D9 v, R, a
  12.     251         ldr     r5, [r2, #4]
    5 S" ~7 ^6 ~4 `
  13.     252         ldr     r6, =ATAG_CORE. M; I( }/ L# \
  14.     253         cmp     r5, r6
    . Q1 h/ {* x! W* V" X1 S
  15.     254         bne     1f2 M7 j: t! [6 h+ f1 w. d
  16.     2557 }( Z  B- \! J8 B
  17.     256         mov     pc, lr                          @ atag pointer is ok& C6 U9 G( `9 `( a
  18.     257
    * G$ H9 S' [4 n: Q/ h1 d
  19.     258 1:      mov     r2, #0( z- f) f& u7 e' t( A# w
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  / I; G- I3 o' M2 b
line 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)5 [) T' `7 b; x+ d0 I6 q
哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
作者: gogojesse    時間: 2008-10-14 12:13 PM
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:' {* l+ y) a9 ^7 P* \! `
  D1 y2 l3 h# G9 P
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。# {! N( V$ Q& b) s: ?8 M* ?+ b
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。/ t. w- t1 n9 B# m% o; N; m# A
3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。
9 N. O3 z. y/ J+ Q) a: j& f
- r' h: e* w. V( f8 `以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。
' q# _& m- r5 e& ]' D& [, Y
. O8 I! w& W, M" x' e2 y由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
作者: gogojesse    時間: 2008-10-14 12:14 PM
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。0 V# B/ x$ ^3 W* L) B1 b
; U0 e- ?" H. ^
『產生page table到底是要給誰用的?』
, v. w- A4 t) m1 p7 f5 W4 U
6 u- F# p& g1 r$ Y8 @其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。8 h8 Y; s) E% @: x

; {  U0 x/ W% U  ?" t& p這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。: i4 [& n8 L1 i4 J9 a- [
0 b0 v9 j/ a0 u: z9 f: U- c0 V
到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
作者: gogojesse    時間: 2008-10-14 12:15 PM
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。( \0 ^% Q- s3 `( c  l
, k6 V( U5 t7 i4 l1 Z- s4 h/ I
現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。: ^+ M5 |+ C* N
/ z# d/ d- O- `- X8 r5 D. Z
知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。/ ]! Z5 O/ g' i  g1 s) G" Q% q
8 G0 \0 x$ Q6 @+ q, [5 M
p.s. 字數限制好像變短了。   (看來很難寫)
作者: gogojesse    時間: 2008-10-14 01:56 PM
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
作者: gogojesse    時間: 2008-10-14 01:57 PM
現在,讓我們跳入create_page_tables吧∼
% v3 [; w! c- P( y+ lline 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。/ j% v, Y, g9 {. U# z/ F, r. l
" E1 ]" k! u; `+ x  z
只是這個位址因為你硬體規劃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 */
    # V6 r3 c( g: E* s( j/ L
  2.      95 textofs-y       := 0x00008000! A0 _; g- u( t( R+ b" l$ m
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */) j+ Y' y. F0 T9 L8 l" n9 R
  2.      40 #define PHYS_OFFSET             UL(0x10000000)5 o8 w/ S6 z- Y& o. h2 D% r* N* e

  3. ; ^, B3 W+ S) Q' q- l, n3 Z4 C
  4.      /* arch/arm/kernel/head.S */+ ?, v0 r# @2 G
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)2 T7 x' I+ F# `5 G
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)
    6 @$ A* k% H+ c, e

  7. & Z5 C: y7 W/ h
  8.      47         .macro  pgtbl, rd7 N( x8 e% E: k- a4 Z
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)
    6 D0 P1 b0 j1 o0 {6 {6 j
  10.      49         .endm( }+ U  `3 f( _. G  L

  11. 1 r+ \; K* S2 Z  G$ N: C0 {
  12.     216         pgtbl   r4                              @ page table address
複製代碼

作者: gogojesse    時間: 2008-10-14 02:16 PM
得到pg table的開始位置之後,當然就是初始化囉。
$ t; F1 }& b4 k3 Bline 221, 將pg table的base addr放到r0.  f% K7 r2 Q" j( Z) ^- U
line 223, 將pg table的end addr放到r6.
$ r# w4 P7 E% d1 oline 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4
    , C6 N8 |2 ?1 r
  2.     222         mov     r3, #07 U/ y. r. l, d; X& a) D
  3.     223         add     r6, r0, #0x4000
    1 @6 t! {# y+ e' |' t/ ]7 _
  4.     224 1:      str     r3, [r0], #4! G' Z' e, }( e1 p( e1 T
  5.     225         str     r3, [r0], #4
    2 }4 Z+ F" }: A* f7 A
  6.     226         str     r3, [r0], #4% e: {% q; H* V4 y5 H  T
  7.     227         str     r3, [r0], #4* j5 Q* H( i! r8 {. T+ l
  8.     228         teq     r0, r6
    : p: m& G. {: s. u$ C
  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
問題怎麼填值??
, \) T. }& ^" Z. q% @8 Q! {1 ^拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。
" ]1 X6 e6 r; q- K0 Y" @
& @' [) u* D) t; |/ I+ A念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教), @9 m1 a0 k$ ^- P( k  C- b7 C
1. [31:20]存著section base addr; A" Q4 Q' ?& U5 R: @0 g  T- M; n$ `
2. [19:2]存著mmu flags
) ]" w7 m! x' `. S3. [1:0]用來辨別這是存放哪種page, 有四種:7 ]3 C  R6 Y# x; r# p) W
   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)
: U) w5 o' {. _: E% Y6 }4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址! g1 g. Y% H0 j  d/ ]4 [- p$ i/ L

+ R9 B! d  @3 A0 O1 ~: V來看code是怎麼設定。
# V9 K6 b1 E5 P( ]! P  F  I
4 i$ @, o& f0 z, e. cline 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。5 e2 z! K5 V: g) g& p# V
line 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。
' n* U+ @$ r5 }- A5 C所以前面兩個做完,就完成了bit[31:2]。
/ m+ s- ~5 D1 \4 ]# e4 K) l6 aline 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20
    8 F/ \* E' [8 A# j6 r- c. P
  2.     240         orr     r3, r7, r6, lsl #20
    * G0 m3 ?: i5 h- n8 ~2 B# c
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
作者: gogojesse    時間: 2008-10-22 07:47 PM
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼, r6 [  r6 g" d/ q. C0 ^7 \7 c3 S
5 R' O! j* Z/ h. h% a
上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看9 H( y9 R3 n" T
line 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。& j1 r% b% L  W3 r$ J

1 K% Z6 I8 I: ]4 l7 r+ s  Jline 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- t- _1 `0 q+ u* W" c
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
    5 i, Q9 S" Y1 b9 g
  3.     249         ldr     r6, =(KERNEL_END - 1)( g; D8 J: F. l
  4.     250         add     r0, r0, #4$ N' X3 I1 ^8 D( ]
  5.     251         add     r6, r4, r6, lsr #18% }0 q% }. P0 E. h# g
  6.     252 1:      cmp     r0, r6, I8 A6 [7 ~7 N1 G, f
  7.     253         add     r3, r3, #1 << 20
    $ _3 y* h; I1 |
  8.     254         strls   r3, [r0], #4; o' L- q% P, r$ y7 W
  9.     255         bls     1b
複製代碼

作者: gogojesse    時間: 2008-10-22 08:24 PM
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。
, O* d  ^& l( vline 280~283,將要 map 的physical address的方式算出來放到r6。
, U2 c& V6 G% V' @# y, Zline 284,最後將結果存到r0所指到的pte。
4 o& ~2 ~( ^- K/ x" e' f: n
4 k8 ^6 _/ H) y以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。8 l$ k# ?+ ?# r6 L8 R  j4 d4 Z3 V

* f% U8 x  ?, |6 D7 Y( F$ uline 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 184 P: r6 U+ _# @  t  X
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
    $ L. I/ S) b3 R, s1 J
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)2 }& h& m2 M* j% T, `9 b
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)
    # f7 a  ?* R! R% C1 _
  5.     283         .endif
    ( f2 [7 x  E8 x1 \* E
  6.     284         str     r6, [r0]
    6 Y1 }: R# N/ z! H7 X+ k
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
作者: gogojesse    時間: 2008-10-22 08:37 PM
自create page table返回後,我們偷偷看一下接下來的程式碼,
# R. y$ y& ]& [, \  q: }line 99, 將switch_data擺到r134 z2 z3 M6 l) L
line 101, 將enable_mmu擺到lr
* N8 Y5 _' g; @! t7 K' Vline 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去
* x, k" u  s% N5 ~( U: v0 D
: y# p, J# u/ \& I$ k; v其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。 1 \) h5 D+ t  j: t0 Z! q: Z! a
  h1 b- f6 [7 ^& w2 d8 |! a
switch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after, {0 w, C5 q0 g
  2.     100                                                 @ mmu has been enabled  q% G& [+ W" t0 Q: |) T8 h! q1 c; H
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address" a- \0 `  ?: S9 H2 Y4 n9 v0 d
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼

作者: gogojesse    時間: 2009-7-4 01:09 AM
老店重新開張~
/ W0 g# [' ?: M9 F6 Y3 A, j! X! }! ?% n' ~2 M! r6 ~0 L8 S
花了一些時間把舊的貼文整理到一個blog
6 U- D+ Y  n7 k  p1 ~/ P0 `( g3 s有把一些敘述修改過
# e; {8 [" g4 \2 F/ I0 k希望會比較容易集中閱讀
3 \; d3 n4 t" `5 u目前因為某些敘述不容易: v; C. q8 J5 `# A/ d. t. w% R! e
還是比較偏向筆記式而且用字不夠精確( t& T( S. E. [9 @
希望之後能夠慢慢有系統地整理* L  u. k/ L& S6 `
大家有興趣的話
1 T1 \& E0 F1 T2 C. p可以來看看和討論
" o3 ~6 x; `8 n7 t7 qhttp://gogojesseco.blogspot.com/7 h1 C) M; T& J* z7 H) x- R$ T
: s  I2 M) z- |( @& S' Z
以後可能會採取  先在chip123貼新文章
* t" s' }& F2 w/ o) j慢慢整理到blog上的方式3 Z, o, [2 e" m% f9 m$ v
因為chip123比較方便討論 =). i  z7 z# Z) W; v: r
blog編輯修改起來比較方便. k1 a8 a' L. d0 F: z
閱讀也比較集中   大家可以在這邊看到討論
1 ^4 E; J# @  R( b1 C然後在blog看到完整的文章 (類似BBS精華區的感覺)
作者: gogojesse    時間: 2009-7-15 05:07 PM
隔了很長一段時間沒update
, b3 E7 C( ^* q3 j之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after
    1 }0 d+ Z) }+ J* y
  2.     100                                                 @ mmu has been enabled- o9 \- f8 d/ \- }
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    . s( u% c$ f) Y1 N( `/ }0 ?" F4 a
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)
# A& }9 ]6 E& j; T4 l) zline 101, 將__enable_mmu的addr放到lr。(留作之後用)
2 p0 {, v3 C1 _' q4 Z$ Fline 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
    ' c) S5 W1 A7 y2 c: w
  2. 374 __arm926_setup:
    3 l  @* a; a% @5 O9 J. U
  3. 375         mov     r0, #0
    ' i$ @6 ~& c: G, \% ^0 y* F' D! @7 E  k- m
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4
    . ]6 k* J( @* r5 a) [" W$ N& j
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4' \9 e5 r* D* J: k; @! y$ {5 O
  6. 378 #ifdef CONFIG_MMU
    ; S/ w- k9 a: c% `# T
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4. X- P/ m) q. c, S: o/ G
  8. 380 #endif2 q# u  T1 a' L; S' z9 l

  9. 2 n- d3 a$ d( d8 }0 k4 I
  10. 388         adr     r5, arm926_crval
    8 n8 K0 D" l) i0 h* [6 L
  11. 389         ldmia   r5, {r5, r6}. {( q  j- i, _& }$ r
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v4
      f7 T# s; k1 R# C5 Y3 T. w2 j
  13. 391         bic     r0, r0, r5
    , L- ?0 X+ o# J1 f& q/ }4 J
  14. 392         orr     r0, r0, r65 Q8 d! i! ^  _% E( @
  15. ! a7 D: _( {; ~" A' X2 y
  16. 396         mov     pc, lr
    / C- y. u' f, }. K6 E
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,
! }- H# f# Y( h% gline 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。
; F1 ]/ d2 O9 ]  Yline 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)
$ x" o' N; W3 P' R, {$ K! ^line 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
作者: gogojesse    時間: 2009-7-15 05:29 PM
  1. 155 __enable_mmu:/ o+ |0 g$ _- @. {+ @
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \8 d- C: {" F2 e# N, h
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \6 \$ `9 m5 S, o4 }* q, d
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \" k& l6 \: Z/ A! M1 [
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))9 ^3 ~. V. f7 B3 F7 ]
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register) k! d/ P! D7 r% `" H( c
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer
    7 X. i% x# |  Y' Q5 Y
  8. 176         b       __turn_mmu_on. y* U2 o0 @2 r1 U9 k' D
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)) h5 T9 A+ A' Y& x( t9 l) t
line 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:) V0 \' r# V% \# |. [" y
  2. 192         mov     r0, r0
    + \6 u+ p# L; i
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg3 S% |0 `. {1 N0 J( v$ [  {
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg4 G, j" @7 S- K% `
  5. 195         mov     r3, r3
    # r( {1 M" u1 X* c0 {; c# T5 Y9 b
  6. 196         mov     r3, r3( s2 q# j- {5 o* Y! W- c5 j2 e
  7. 197         mov     pc, r13  \1 z$ x( \# a7 H( D, q0 j
  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:
    6 ?8 ]$ }$ y# L2 c
  2. 19         .long   __mmap_switched: r  Q2 e' E8 U, L  c0 S
  3. 20         .long   __data_loc                      @ r4( Q0 A6 x0 E" i
  4. 21         .long   _data                           @ r5
    ; U# T7 t( p! |* U  O$ T4 d8 W+ T  v/ J
  5. 22         .long   __bss_start                     @ r6
    ; M, Q4 ~' s2 Y9 e3 _! l. J- Y- E
  6. 23         .long   _end                            @ r7
    - `8 ?. b( V% r; [* w
  7. 24         .long   processor_id                    @ r4
    8 {* V- K5 t' ~, {; s- y) X
  8. 25         .long   __machine_arch_type             @ r5; O: `# X- [9 I6 U
  9. 26         .long   __atags_pointer                 @ r6
    * S1 v( l& v! c3 H) V
  10. 27         .long   cr_alignment                    @ r7$ u' Z1 i0 A1 e* t2 b
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp
    ( J8 j8 ~5 c0 \; o; K
  12. 29* b, l3 |5 X  m9 a6 W
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
作者: gogojesse    時間: 2009-7-15 05:30 PM
  1. 39 __mmap_switched:
    % q: i' Y! z/ l( R0 @+ `% z
  2. 40         adr     r3, __switch_data + 4
    1 v4 ~; {' P' w6 D
  3. 41
    / C: M# n3 V" S5 _4 s
  4. 42         ldmia   r3!, {r4, r5, r6, r7}; X- @; H$ V0 ?) _6 m* N
  5. 43         cmp     r4, r5                          @ Copy data segment if needed' x0 _5 b% y! r8 [- L1 ?: \/ k/ E
  6. 44 1:      cmpne   r5, r6! N/ O: Q1 `" ?0 G
  7. 45         ldrne   fp, [r4], #4
    8 S1 Q2 b. Z/ g9 U, y0 O" d
  8. 46         strne   fp, [r5], #44 N. s, X/ A, P* Q7 m) [8 y' m
  9. 47         bne     1b
    8 z& h: n3 g! X& v  X4 u+ x( q
  10. 48
    , c0 K0 ^- Y+ c! w* \' `* D$ N
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)
    ) ]+ Y( Q# }' ^( t- T
  12. 50 1:      cmp     r6, r79 q3 n2 y- }" s. m
  13. 51         strcc   fp, [r6],#4' v6 @& I7 f& U- D7 F. g
  14. 52         bcc     1b0 R- H( C2 e/ h6 B5 g; S- M
  15. 53
    6 V5 a: H/ I. m* Y1 n
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}% G4 y" a$ [& y1 `. r
  17. 55         str     r9, [r4]                        @ Save processor ID6 s) S9 Z! ~8 S- X" r
  18. 56         str     r1, [r5]                        @ Save machine type
    + Z# D; @" d& K. c5 E
  19. 57         str     r2, [r6]                        @ Save atags pointer- D6 y( e8 s& n4 @0 S
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit. u2 [# ]: m& z: K3 V9 M( Z/ i
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values- w9 a; M0 c2 `( p7 @& e! ^
  22. 60         b       start_kernel
    5 t( N# U# U$ U- Q
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。4 N' L- {- T# E8 o5 D
line 39,將__data_loc的addr放到r3
$ T4 O7 h' Q, g- [8 rline 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7* y5 L. T' A- E
line 43~47,看看data segment是不是需要搬動。7 y0 |& e- r8 U* Y+ w4 x: {$ n
line 49~52, clear BSS。
5 [+ {+ R' ]" }0 Q' c) I$ n' B7 ]1 i! ]' s" t6 F
由於linux kernel在進入start_kernel前有一些前提必須要滿足:/ m8 |0 I5 Y5 a& S
r0  = cp#15 control register
4 z5 g4 o3 Y) x3 M1 Rr1  = machine ID" y% B4 `! q1 @7 |5 I  x
r2  = atags pointer- O8 l" S0 L% O$ o  K2 j2 R
r9  = processor ID
& b% j% P1 j5 c3 z. _( b/ W1 _" y6 v$ o6 i1 l# T" z1 D. s
所以line 54~59就是在做這些準備。
0 c- v- P' i5 g3 p最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)
$ j2 _( {; o# O: X% y7 }, I/ o9 I4 R, {
看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示8 T+ `0 y+ V6 o
我們真正的開始linux kernel的初始化。
% h' w# M  z2 m" P6 P/ H. D( B像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。) z1 D4 N$ }( z/ M2 ]' Q
到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。




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