Chip123 科技應用創新平台

 找回密碼
 申請會員

QQ登錄

只需一步,快速開始

Login

用FB帳號登入

搜索
1 2 3 4
查看: 7394|回復: 17
打印 上一主題 下一主題

trace linux kernel source - ARM - 03

[複製鏈接]
跳轉到指定樓層
1#
發表於 2008-10-7 14:00:18 | 顯示全部樓層 回帖獎勵 |倒序瀏覽 |閱讀模式
到目前為止,我們已經進展到kernel幫自己relocate完,並解將自己解壓縮到一個地方要準備開始執行,那疑問來了?到底是跳到哪裡去了,因為./compressed/head.S最後一行居然是
+ w8 Y% Y9 e- S! P) H; ?) f/ c0 d『mov pc, r4』
4 a6 A0 I8 _1 r- @+ Dr4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!
* L- E, `- k; T) K
( i" g1 X5 _: Q- z; I所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。! z8 M) Y. U" `+ y# p
( `) K1 L. Q' O+ c. ?0 c* ?; R3 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)( |; c! a4 D0 `* L
我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。
- D) x3 I7 t3 Z* W, ?於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {! y3 Z( S0 S5 h
  2.      27         _stext = .;- T: Z, w$ |- d8 N% T% A; j
  3.      28         _sinittext = .;% O; W$ T( H4 \
  4.      29         *(.text.head)# T" i6 G! o- f9 j7 B
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"
    ; }) a4 y; W: S- ]/ L: B
  2.      78     .type   stext, %function) \, z8 _( ?4 J! ]: s
  3.      79 ENTRY(stext)) Y  Y7 e  F$ @
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    ) [1 O. J4 T' J, p
  5.      81                         @ and irqs disabled* L: m9 @- R  z3 v+ x" g  j
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id$ z( R% [: Y, X
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid# ^5 T6 w+ q6 Z4 ?
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?1 w! _4 p0 Y) T$ d% G
  9.      85     beq __error_p           @ yes, error 'p'
    3 K, s5 j) P( i. a) p# L; V# K0 D
  10.      86     bl  __lookup_machine_type       @ r5=machinfo
    6 q' U0 o, ?7 W# T4 d4 D
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?# f' e( L6 W( y
  12.      88     beq __error_a           @ yes, error 'a'
    - u1 Q" n( U5 ?
  13.      89     bl  __vet_atags* y4 v" d" H* u& E( y6 l
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。9 {& |% b( ?8 b- W
0 T: f2 U0 N+ K8 H" N
看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享分享 頂 踩 分享分享
2#
 樓主| 發表於 2008-10-9 15:32:16 | 顯示全部樓層
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。
( e0 b; u8 f5 N
( M% ]( ?5 z  e可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*  l0 Q4 |1 Z- y. @8 [1 W3 A
  2.      60  * Kernel startup entry point.
    7 k9 f  X+ }( y/ _6 {
  3.      61  * ---------------------------
    9 a6 w4 r8 ?' E$ ~9 r7 f  J( }
  4.      62  *
    + y( _( c; Q% u7 b4 q1 `& \
  5.      63  * This is normally called from the decompressor code.  The requirements* T$ q, `1 g1 i5 ?( Z
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
    , q: r$ m7 s5 X" ]
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。" o4 P& [+ p" C# g! s1 E
line 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)
9 d- P5 K, g' {line 82, 讀取CPU ID到r9
$ f$ S1 ^$ [7 \+ i1 v0 q# b, rline 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"
      \! h1 i- O+ F
  2.      78     .type   stext, %function
    - T, h! I; a9 N6 O5 ?" R
  3.      79 ENTRY(stext)# Y# e7 g$ C$ A& y
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode) t- k, b8 |9 |& ]
  5.      81                         @ and irqs disabled7 R: b( ~. c; G8 q$ {
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id- K2 x4 u$ l( A5 b
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,
7 V- m. i6 ?/ R! i. H8 Iline 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。; B+ n& h$ R; U0 U0 D6 C- v
line 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)
. {( k; Y" Y7 ~. G7 l  Y' K2 H$ Lline l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。# l2 g+ P% q4 {% [; ~3 ?
line 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S( A4 z$ W& N! K
line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。, R9 m. \0 J1 c* b' A/ T% V+ D  v
4 s3 N2 W8 a- s3 H
__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
    ; D% p& @4 ~, r- E! \% e' ]( N
  2.     157 __lookup_processor_type:! H8 t& O5 V* L, I0 j6 ?; n6 R
  3.     158     adr r3, 3f" N/ s6 ?' Z6 v! \7 v1 z
  4.     159     ldmda   r3, {r5 - r7}
    1 S/ N5 w. a4 a6 c, v# Y
  5.     160     sub r3, r3, r7          @ get offset between virt&phys
    ; L, N7 U. D- E0 e9 @6 ~1 `
  6.     161     add r5, r5, r3          @ convert virt addresses to6 |, ?0 k1 Y7 i- d! h
  7.     162     add r6, r6, r3          @ physical address space* Q" E" V% R% ~8 ~  y7 V
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask8 r2 j9 K* X, N# r$ Y
  9.     164     and r4, r4, r9          @ mask wanted bits
    9 q* d0 u& q  Z( z+ q0 D
  10.     165     teq r3, r4
    0 w4 m/ l& R+ D( R
  11.     166     beq 2f
    ) S& o  G0 `: q- W6 h$ Z, h, r& y
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)! j" ^' D: g! d4 b4 u5 {
  13.     168     cmp r5, r6
    . v( [' T/ i1 r
  14.     169     blo 1b9 M% K, x/ v2 |% f* ^* V9 Y& X# A
  15.     170     mov r5, #0              @ unknown processor; I5 t4 U4 P& }; Q% W/ X- |
  16.     171 2:  mov pc, lr9 Y, u/ G# [7 |1 Y

  17. : Z5 E% A9 H- p8 W3 v0 Y) Z( B
  18.     187     .long   __proc_info_begin
    , M: Y; b, N( O
  19.     188     .long   __proc_info_end6 `' W7 u" W- p# @
  20.     189 3:  .long   .
    2 |6 H: r" _% s; Z4 n, }+ Q
  21.     190     .long   __arch_info_begin
    ( T% g; S+ w! u5 P) _+ C$ _, O
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
3#
 樓主| 發表於 2008-10-13 17:20:35 | 顯示全部樓層
我們從 head-common.S返回之後,接著繼續看。( F- O: S: d7 D* S* f. s

! [, e/ l- k( N2 @9 i' L. J* wline 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。* `1 u$ V* {' z
line 85, 就是r5 = 0的話,就跳到__error_p去執行。
3 T. r4 q$ b0 ]6 E+ ]line 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid
    / A% t' u/ T5 c  Q* M) Z) D" G: k
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?
    $ [- D$ ]. y0 @7 ?
  3.      85         beq     __error_p                       @ yes, error 'p'
    6 }! {" a/ @, u. r0 W
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是: k. S! {2 d; ~1 i

, W8 w7 Z; v" z7 q1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。
  s0 A' X) v7 N! o8 j2 E* Y
( H4 c+ x- X) d, @  F3 V2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */
    ; C9 v& M/ M( o
  2.      50 #define MACHINE_START(_type,_name)                      \
    $ h( T: u7 g. v4 ]4 W8 R
  3.      51 static const struct machine_desc __mach_desc_##_type    \$ x  P" x5 C/ w, {, ^% i
  4.      52  __used                                                 \% Q! y* `+ v) }; A* f+ H
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \
    6 v: T& o3 Y; @- y: I, }
  6.      54         .nr             = MACH_TYPE_##_type,            \
    % _0 }3 y6 b; @  c0 K, Z
  7.      55         .name           = _name,$ {1 p8 v; _: D$ x" [
  8.      56' Y* b2 \9 U; ?
  9.      57 #define MACHINE_END                             \
    3 X! m. o4 D4 M6 A2 X8 p4 C
  10.      58 };" m9 ~9 G/ y1 T7 C" ~) h/ q% j8 z
  11.      /* 用法 */# I) \. P/ m0 H5 `+ e* c. C! I
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")
    % n# ]* M% q/ B# d" [: ?
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    - x0 |1 {4 B% k4 a: X( w
  14.      95         .phys_io        = 0xfff00000,+ G) d+ U& v: {* N% p' O  M
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,- ^6 K5 q( n0 F) U" R" ~. J
  16.      97         .boot_params    = 0x10000100,9 {+ e* I( @  ^* B8 ^. Z, ^
  17.      98         .map_io         = omap_generic_map_io,6 K7 |7 Z# Y. k6 l4 C" B) l. j
  18.      99         .init_irq       = omap_generic_init_irq,
    6 A8 c8 Q& \; i3 F6 G% R6 {' L
  19.     100         .init_machine   = omap_generic_init,
    ' o- s% J+ O/ Z7 R9 e( F
  20.     101         .timer          = &omap_timer,
    $ D1 y# @* g$ g+ _% c
  21.     102 MACHINE_END
      }0 E2 ^( L' c) ^* d
  22. ; X; W* u9 o5 K! R, e
  23.     /* func */
    ( W2 s$ e/ W/ o8 W2 e' x# K
  24.     204         .type   __lookup_machine_type, %function5 _8 }9 N  k, K; Z
  25.     205 __lookup_machine_type:- A! i: G* H+ h0 ?* v
  26.     206         adr     r3, 3b
    # Z( O4 \1 Y6 K+ X: A
  27.     207         ldmia   r3, {r4, r5, r6}/ l" i! t) ], `6 |; C
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys
    * z) e* M9 x/ v/ S% z
  29.     209         add     r5, r5, r3                      @ convert virt addresses to/ y: H1 H0 @& c! ?2 V5 o/ p! A. t, A
  30.     210         add     r6, r6, r3                      @ physical address space7 U" z5 b( u! P! J! e  N" W% `
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type# }& ^4 O3 V) }
  32.     212         teq     r3, r1                          @ matches loader number?
    & B1 q2 h8 @/ y1 z
  33.     213         beq     2f                              @ found
    $ c: L- S5 C; w. K+ o8 K
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    ( ?. S' r" `3 a
  35.     215         cmp     r5, r6
    - @* X1 q" _* a/ L
  36.     216         blo     1b
    & w  Q2 _/ g7 ^; q9 H' l  M
  37.     217         mov     r5, #0                          @ unknown machine# \8 T, Z1 y2 A. Z8 }/ h
  38.     218 2:      mov     pc, lr
複製代碼
4#
 樓主| 發表於 2008-10-13 17:56:46 | 顯示全部樓層
接著我們又返回到head.S,* q/ ?# F7 ]) C( ~% c
line 87~88也是做check動作。" @' L6 N0 {2 @* ]7 p% a8 L
line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?  z$ h8 _$ J' ?' I$ H% h- `
  2.      88         beq     __error_a                       @ yes, error 'a'3 F9 D, s" E) g* N5 ]$ G& [- _  q
  3.      89         bl      __vet_atags2 o1 q3 W$ p# W8 t) @# t
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。1 P7 Q0 G; H& U! [# n  {. ?  k
line 246, 沒有aligned跳到label 1,就返回了。
$ B! |+ V4 C, r+ q/ E' a7 l+ Nline 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
2 Y2 `; H/ o2 M; |) Z! [line 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。9 O9 k+ L, K/ ~$ E2 _5 s
(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x54410001
    ) @* u. l; F6 G3 s
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
    / L9 l  H2 \& v$ p* E

  3. 0 n( R+ P+ K+ G
  4.     243         .type   __vet_atags, %function  T" q4 b6 c. z+ y
  5.     244 __vet_atags:
    4 \! T+ _1 q4 k0 }; `
  6.     245         tst     r2, #0x3                        @ aligned?3 D4 v0 O: S% y" y- d4 Z9 ~! I
  7.     246         bne     1f1 ?7 U* b: \( k7 @
  8.     2479 Y8 w# @: h. y1 Q
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?8 j5 I) w/ R3 f
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE
    8 ~9 [( J+ f/ B/ ]: w
  11.     250         bne     1f
    : A8 V0 r9 |, e" i" _1 W
  12.     251         ldr     r5, [r2, #4], n9 G/ r5 L3 A8 {) h- p
  13.     252         ldr     r6, =ATAG_CORE
    7 o5 Q9 `9 d9 V. Z7 W; c% _) v4 d
  14.     253         cmp     r5, r6
      f* ?  X0 n$ ?4 w/ f6 |
  15.     254         bne     1f# d5 o7 F% P/ T6 x9 R5 J  j
  16.     2551 M# _; Y) u( a% f* P
  17.     256         mov     pc, lr                          @ atag pointer is ok
    5 Y. i, i* ^( s5 d3 U
  18.     257
    ; l9 F8 Q  d% `/ S
  19.     258 1:      mov     r2, #0, @5 p" g  {2 @1 E, {5 w1 W) l
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  2 Q& I* S  B6 J6 Q
line 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)
# p1 t+ ^: C) r; K! x哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
5#
 樓主| 發表於 2008-10-14 12:13:51 | 顯示全部樓層
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:
" g& ]7 J" f) ~, }0 F- x4 G' _- N  z5 i" X1 B5 S& H) B" T
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。9 t1 D! x* O/ s9 z& B. V
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。
# s# m$ M$ f5 Q; t  |3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。2 l! Q& Z* o# r& s
! I* i6 F3 F; s7 `+ z/ d
以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。, x: f" ?3 S/ C

% D5 k" r& M% c# U% s2 [由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
6#
 樓主| 發表於 2008-10-14 12:14:47 | 顯示全部樓層
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。% Y: A8 N' F4 n& m) R0 U* q
) I& B0 K% Z5 z
『產生page table到底是要給誰用的?』
, |2 H8 D- M* N- I  Y, n
! Q0 {: e) f: \+ {) J3 p! v& `: J其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。- T! E& B/ q! U$ A3 S
& u: H% O8 P2 V9 U9 Y/ ?+ U  R
這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。
8 S, W4 D0 ^: Z
9 l+ V0 G' m. D6 k2 B7 h到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
7#
 樓主| 發表於 2008-10-14 12:15:51 | 顯示全部樓層
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。2 G2 [* `/ K1 V# ?
) c0 Q8 B/ |2 P. S4 p- y/ v  N
現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。7 m! R' ]1 W* P" r# s; b
7 r" ?0 B8 x: |0 n& q! \9 e& M
知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。
1 B3 {5 C( G+ F' {: P' H0 G% \5 u! N# X- Z4 N# A& j) R# _! N4 D
p.s. 字數限制好像變短了。   (看來很難寫)
8#
 樓主| 發表於 2008-10-14 13:56:17 | 顯示全部樓層
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
9#
 樓主| 發表於 2008-10-14 13:57:39 | 顯示全部樓層
現在,讓我們跳入create_page_tables吧∼
  o! N8 z. H5 U6 Yline 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。
8 h% }  K+ U0 u7 [1 S) @1 w, c: X$ ]5 E$ z' [5 F! h! ~
只是這個位址因為你硬體規劃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 */$ w8 Z" p7 y4 o
  2.      95 textofs-y       := 0x00008000
    9 w5 y) s# b  ]* F4 v; a) p
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */
    1 F4 Q7 t/ p9 X  }# [% I
  2.      40 #define PHYS_OFFSET             UL(0x10000000)4 M0 \- ^6 D$ z# G& B/ T4 ?$ {- u
  3. # A7 K7 q& Z5 H
  4.      /* arch/arm/kernel/head.S */
    - |" H! t! u9 z# n% v- w
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)& f9 ^3 A+ ]2 ?: z3 q
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)( F; X" D2 a! P( ]# K- \  _

  7. 2 c9 f& y% V- t2 n/ H, e
  8.      47         .macro  pgtbl, rd
    ' J8 R; F5 ]4 I9 R
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)" ]) Z! E. K: v5 w. S
  10.      49         .endm
    $ u! P  k! Y. o# C3 W0 b
  11. 9 M+ ~1 I* N% [0 r- T0 |4 Q
  12.     216         pgtbl   r4                              @ page table address
複製代碼
10#
 樓主| 發表於 2008-10-14 14:16:53 | 顯示全部樓層
得到pg table的開始位置之後,當然就是初始化囉。
' n0 j$ t! F5 H- t0 P9 Rline 221, 將pg table的base addr放到r0.& ~. r  t- {. \+ f" Q7 ], o( s
line 223, 將pg table的end addr放到r6.
3 N/ |; ]7 x, U4 ]line 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4
    4 |8 h* v/ o3 J9 L& J6 s3 D2 }
  2.     222         mov     r3, #0
      H9 v# Z# d5 Q7 {6 Z
  3.     223         add     r6, r0, #0x4000  X. x' l) I+ l% T
  4.     224 1:      str     r3, [r0], #4- k# k: y. P7 L' c
  5.     225         str     r3, [r0], #48 x- i/ Z( E, d' u0 \4 u5 h
  6.     226         str     r3, [r0], #4
    + i; M& \3 o7 N0 n6 X0 ^& ~
  7.     227         str     r3, [r0], #4
    5 E% {7 O; J! O# X
  8.     228         teq     r0, r6( \6 H1 X. M( M# H+ ~; A8 }
  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
複製代碼
11#
 樓主| 發表於 2008-10-14 15:11:48 | 顯示全部樓層
問題怎麼填值??
9 U1 I6 P  v- |- |- Q3 M' X* `拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。
- ?" A% i2 u( o8 U- R& l
) _: u7 r: W7 Y1 i: \念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)
. h4 X. {( y) n' G5 S: o- S) u% {: D1. [31:20]存著section base addr
4 j& q$ c( ^$ f% p; d: D2. [19:2]存著mmu flags
! y, R1 h8 L* t9 D" {& K3 ?6 g3. [1:0]用來辨別這是存放哪種page, 有四種:
' J& v' S8 P, L7 I& S% K* Y   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)- j4 [5 \) ]* W+ H& j) ]( V7 ~( g
4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
! t/ i1 `3 @8 L" b0 W
' n- K+ w4 B& s- L, g來看code是怎麼設定。9 W% X/ T5 d/ y2 \1 H; X

9 Z' }' E8 o3 Q5 ~' p4 zline 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。  O5 H0 {! v! a
line 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。4 ~' a$ E+ R" D9 k: t& U$ g
所以前面兩個做完,就完成了bit[31:2]。
4 _4 V( a$ b0 t2 }/ pline 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20/ B1 B' H, C9 A9 S
  2.     240         orr     r3, r7, r6, lsl #202 h1 n6 f3 V* K" o& D- o' B
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
12#
 樓主| 發表於 2008-10-22 19:47:03 | 顯示全部樓層
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼
' @" W. T$ \* W5 G
, A% a8 d7 l3 ?  m8 q; F上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看
* U. P% Z7 h. ^+ n1 m% Tline 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。
. Z, J2 G6 S4 K4 E8 p
8 h9 z' j7 `' a' B1 S6 e: Mline 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* q0 o. J* a+ S
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! 7 H, l% V" {* U$ e) E0 x
  3.     249         ldr     r6, =(KERNEL_END - 1)
    6 i) [1 N4 y7 ]4 ]$ _9 ?( \! O& x
  4.     250         add     r0, r0, #4
      C$ ^: y- P; ]" u% E5 n- {
  5.     251         add     r6, r4, r6, lsr #18( ^. G1 W4 z6 |; L8 ]3 _3 V2 Q# z
  6.     252 1:      cmp     r0, r67 g, c+ J, i5 x6 b! Y& h
  7.     253         add     r3, r3, #1 << 20
    9 }% Z; z3 V9 [6 L8 j, B
  8.     254         strls   r3, [r0], #4
    . {* J! U1 `( d5 |; A/ a
  9.     255         bls     1b
複製代碼
13#
 樓主| 發表於 2008-10-22 20:24:58 | 顯示全部樓層
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。5 L+ ]' m, i. V1 h  B+ t
line 280~283,將要 map 的physical address的方式算出來放到r6。  R; ~& E) d6 f
line 284,最後將結果存到r0所指到的pte。
( g$ S; `1 Y5 p+ }  \2 R. x0 v- F
6 a8 l: z" _; ?! ]7 `以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。
- Z# N) x# C3 V: E$ ~+ |; Y7 L( X/ V) y+ |0 m
line 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 18
    & d- `( C' E2 h: W4 o9 H
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)! F) r7 q0 \2 ^9 v6 z# d- X  ^' U
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)
    4 M) n+ u! c* K+ v
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000), \8 Z% f& H: u/ e2 T% u* C
  5.     283         .endif
    / T- }  W' X$ w- }- c: M5 f
  6.     284         str     r6, [r0]& ?8 d$ J/ I! C* r% M" R# i1 X! y
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
14#
 樓主| 發表於 2008-10-22 20:37:08 | 顯示全部樓層
自create page table返回後,我們偷偷看一下接下來的程式碼,
# e4 d0 }% G$ r: R' N3 x( N3 ?* _line 99, 將switch_data擺到r13" c7 _* D- r% w- J  k) D
line 101, 將enable_mmu擺到lr& ~1 y/ d/ q6 u, x! V0 a
line 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去2 P# M1 m( ^6 X* ]/ y

1 C+ M  c! q( n& |- ^+ T' N其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。 $ g, D; T" y4 V  }( g
7 [3 y) w) k: c: X4 p
switch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after/ B, Q0 Y* i: t3 V
  2.     100                                                 @ mmu has been enabled$ Z& N7 A/ i" I$ ]/ A/ Y: O/ W
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    * H  u' s# @0 P- U2 o* R/ ?
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
15#
 樓主| 發表於 2009-7-4 01:09:36 | 顯示全部樓層
老店重新開張~
7 E/ {9 C  s; A4 s
" J8 ^8 Q' @2 x0 u花了一些時間把舊的貼文整理到一個blog
' T5 _9 C& H  \. m: b' f有把一些敘述修改過3 S6 |# Q$ K8 L8 m5 l
希望會比較容易集中閱讀
( a0 h4 R  o- {$ }  Z6 k& r! d目前因為某些敘述不容易
$ ?" `1 f# W/ O/ }5 ^; Z還是比較偏向筆記式而且用字不夠精確9 f/ `+ }2 \! R0 P
希望之後能夠慢慢有系統地整理. \9 J5 `, e8 c+ i+ |+ K/ I: W3 Q, u5 l
大家有興趣的話
9 @- n9 w+ [" ?可以來看看和討論 $ F7 o1 s2 ]+ m( X. W3 _
http://gogojesseco.blogspot.com/
, D5 L' ?: \' f8 Q; M( V- U5 D+ g/ ^, p
以後可能會採取  先在chip123貼新文章
. u3 X8 f' K; c+ i0 P慢慢整理到blog上的方式0 e; V- H8 f4 o( y" M
因為chip123比較方便討論 =)/ P. w* c, r3 m5 R% {7 d# m
blog編輯修改起來比較方便. r, U+ s& M# k$ L5 J& g3 j. A
閱讀也比較集中   大家可以在這邊看到討論
5 g: G& x6 Q5 k) G% l# i然後在blog看到完整的文章 (類似BBS精華區的感覺)

評分

參與人數 1Chipcoin +5 +3 收起 理由
jacky002 + 5 + 3 感謝經驗分享!

查看全部評分

16#
 樓主| 發表於 2009-7-15 17:07:03 | 顯示全部樓層
隔了很長一段時間沒update6 a- s! F2 P$ V4 _9 t5 a
之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after8 [7 t! s3 c3 C/ P+ [
  2.     100                                                 @ mmu has been enabled
    $ ?! y, y; k% a' j
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    ) V( b$ t* n* o
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用). X& T% n, V" E- ]6 ^% G
line 101, 將__enable_mmu的addr放到lr。(留作之後用)
/ w% o8 q$ x$ f4 u% ?4 g' G% k$ P& ?line 102, 將 r10+#PROCINFO_INITFUNC 放到pc,也就是jump過去的意思。r10是proc_info的位址。PROCINFO_INITFUNC則是用之前提過的技巧,指向定義在./arch/arm/mm/proc-xxx.S的資料結構,以arm926為例,最後會指到
  1. 463         b       __arm926_setup
複製代碼
所以程式碼就跳到了 __arm926_setup。
  1. 373         .type   __arm926_setup, #function0 \/ h1 |1 J0 k" i% K% m9 W: q! a
  2. 374 __arm926_setup:
    * q9 S0 q7 g1 T  B
  3. 375         mov     r0, #0
    / O1 [: [+ K8 F
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4; s) }& U% R2 ?; A
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4: i: n: N  o/ e( p0 s& k/ l
  6. 378 #ifdef CONFIG_MMU! E. o/ m& i) _8 [- e& @
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4
    0 N  B. i9 d. s0 B
  8. 380 #endif( h' Y( D! ]' Y0 S) s

  9. ; U6 X, G9 c- P& M6 O0 J! x7 t
  10. 388         adr     r5, arm926_crval: b% C4 f% t$ r. V
  11. 389         ldmia   r5, {r5, r6}
    8 s' R- B0 g: O- f- o  U. o
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v4
    $ O/ s( h. U" f& z* W1 {, d
  13. 391         bic     r0, r0, r51 o) E: p0 D6 b8 T; j( {
  14. 392         orr     r0, r0, r6% Q) m% [8 t& _$ g& E, l

  15. $ @# ?3 Z. o9 R& n8 w
  16. 396         mov     pc, lr
    4 M1 o' R1 v# c  ]* x/ _$ U
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,8 C2 b6 g2 Z  z3 n8 t
line 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。  X' w: n4 ~3 [8 h2 J
line 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)
, G# Y& ^. u  f. Y% r: `" Z$ Jline 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
17#
 樓主| 發表於 2009-7-15 17:29:45 | 顯示全部樓層
  1. 155 __enable_mmu:( \: T/ U% i5 p
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
    ' s5 T6 D% ~( ^8 J
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
    & B2 [2 }- [% n- F4 K
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
    2 _0 `3 o; n1 L% @% p( P4 u! @6 M+ X) c
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    8 ^# Z* n& o( s) \1 p
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register  s# J  [, {# l5 m
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer
    4 q' X! x9 G7 R) m
  8. 176         b       __turn_mmu_on3 w; E' L# E9 H; M% E& y
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)$ J/ X9 v: n' U# W% N) d+ H
line 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:# j2 f# J7 o5 R
  2. 192         mov     r0, r0
      |. a  [1 A; u7 ~6 u9 P
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg+ C$ @6 ?' _$ O2 Q* }, C# M
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg
    * _( O9 Z! k# ]% N9 R+ m
  5. 195         mov     r3, r3
    : M4 a* n, {/ @7 b$ P3 R: I6 S
  6. 196         mov     r3, r3; H! Z/ [/ a. [9 y0 O
  7. 197         mov     pc, r133 p5 j! a0 v! n2 A/ u! ]0 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:
    * H7 g3 g' I2 T
  2. 19         .long   __mmap_switched
    % U7 K. ^5 b2 Q+ n" o7 c: |0 e
  3. 20         .long   __data_loc                      @ r4+ Y( R6 `+ l# {5 Z8 w
  4. 21         .long   _data                           @ r5! _: v: F; V. N9 z
  5. 22         .long   __bss_start                     @ r6$ O! G6 S- x8 n3 v9 {% r
  6. 23         .long   _end                            @ r7* x  g6 T& I* n# v/ G
  7. 24         .long   processor_id                    @ r45 x- I2 P: r  q* d; ?( L+ \
  8. 25         .long   __machine_arch_type             @ r5! ?5 c. F* N5 e. @( i/ M2 ?
  9. 26         .long   __atags_pointer                 @ r6, \: p3 t9 L) z
  10. 27         .long   cr_alignment                    @ r74 E3 G  r9 \5 A0 X
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp! C% E3 h+ h3 F+ j- B" x4 ]
  12. 29
    ) ?6 }) T' [+ s6 Y) W
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
18#
 樓主| 發表於 2009-7-15 17:30:00 | 顯示全部樓層
  1. 39 __mmap_switched:
    8 r2 L( _: K. |  D! _
  2. 40         adr     r3, __switch_data + 46 ^4 V, w- ]1 ?* ~. \; N( k
  3. 41
    / S6 ~5 k* c4 M  [  Q
  4. 42         ldmia   r3!, {r4, r5, r6, r7}( p! `" F0 R% I! x/ @8 z
  5. 43         cmp     r4, r5                          @ Copy data segment if needed
    : K" L6 |$ |1 s' A$ ^! ]
  6. 44 1:      cmpne   r5, r6
    $ O8 }: O# d( U0 `! ?6 a
  7. 45         ldrne   fp, [r4], #4" {. J) m7 c2 s4 C3 k( ?, M
  8. 46         strne   fp, [r5], #4# a5 c: b( }/ f6 ]6 _2 ]  Q* |
  9. 47         bne     1b
    5 N- n0 U' H5 V% k% F; o9 S
  10. 48" [, ^; ^5 u3 J
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)) }0 S! Z  D/ i4 _6 R$ H
  12. 50 1:      cmp     r6, r74 j& e( N- w; E) L& t  @, j
  13. 51         strcc   fp, [r6],#4
    ) V, H# d2 Y  |, E6 Z& @3 L
  14. 52         bcc     1b
    : E4 n+ r! B! ]3 }0 X% V$ U% F
  15. 53. o' k6 l+ |# X7 l& k( ]; S
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}
    + ~! U/ [! r% m0 {' U* W
  17. 55         str     r9, [r4]                        @ Save processor ID
    9 O/ a$ g7 r# b' z
  18. 56         str     r1, [r5]                        @ Save machine type1 X4 U  ]3 F, k6 {; K- e
  19. 57         str     r2, [r6]                        @ Save atags pointer
    + X# Y2 P+ M* S% U; q
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit) X$ H) d, W5 S
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values7 j/ I) S6 t8 W, O
  22. 60         b       start_kernel+ R& }5 d" |5 L, @  L9 T
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
2 c9 p# J. Q* ~$ B" Z# Hline 39,將__data_loc的addr放到r31 \$ s3 {  L( `% W% N
line 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7( A3 A4 z( ^' m  B: i) n' x
line 43~47,看看data segment是不是需要搬動。- y% x$ R+ O3 j$ S
line 49~52, clear BSS。
4 o2 j  p: i+ O" l( p" D% z8 I9 s2 M( \0 X0 t
由於linux kernel在進入start_kernel前有一些前提必須要滿足:
8 q' B" t2 K- ~  A: Q/ L3 ]0 L* a* Ur0  = cp#15 control register% Y9 x! o! h- Y5 ?3 I& K
r1  = machine ID
2 S4 ?: W& Y4 h9 D$ fr2  = atags pointer
" n8 V/ f7 b$ O9 {r9  = processor ID
/ l& b. p/ z' H6 O( r( ~
+ E; F' G, R( X) J所以line 54~59就是在做這些準備。% Y0 _& x) K: d0 x$ G& e1 M" i
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)
0 G6 s2 O+ r% T1 _* o( L# Q$ {2 q2 q) `9 I, d+ F
看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示
9 j5 n' K0 p9 X* {! w我們真正的開始linux kernel的初始化。3 S2 Y0 L( S) s/ I7 w
像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。
+ x- Z* \& M' a( N9 p' p# `到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。

評分

參與人數 1 +8 收起 理由
card_4_girt + 8 感謝經驗分享,希望你再接再厲!

查看全部評分

您需要登錄後才可以回帖 登錄 | 申請會員

本版積分規則

首頁|手機版|Chip123 科技應用創新平台 |新契機國際商機整合股份有限公司

GMT+8, 2024-6-14 08:34 AM , Processed in 0.157520 second(s), 18 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表