Chip123 科技應用創新平台

 找回密碼
 申請會員

QQ登錄

只需一步,快速開始

Login

用FB帳號登入

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

trace linux kernel source - ARM - 03

[複製鏈接]
跳轉到指定樓層
1#
發表於 2008-10-7 14:00:18 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
到目前為止,我們已經進展到kernel幫自己relocate完,並解將自己解壓縮到一個地方要準備開始執行,那疑問來了?到底是跳到哪裡去了,因為./compressed/head.S最後一行居然是
. K1 _" o4 l" s8 U『mov pc, r4』& p6 n' Q: w/ |6 }8 x) {% @% ]3 `
r4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!
1 o1 G6 A3 N9 q! \9 r
# P' D% c6 M. ~; f& Z. s$ r; y$ z所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。) }1 ^7 l# k: |! k

# V9 x: P3 o6 t) Q- V2 [& f8 I有興趣的人可以看一下 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)
& i9 |& b3 z+ v* r! [' k6 z4 M我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。
  k; _, [5 j0 E. K7 w於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {
    4 G% U% W; S1 [# y: W2 ?
  2.      27         _stext = .;
    ' _4 p$ U* ^2 @+ ]
  3.      28         _sinittext = .;  M0 h8 i1 k7 G( l' |# n
  4.      29         *(.text.head); n: X5 Q8 B' l/ M$ w2 k% Q
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"
    ) f0 \" v4 y0 W5 E% X' U
  2.      78     .type   stext, %function
      Y7 f' _: `. K4 a4 V
  3.      79 ENTRY(stext)$ S9 R$ B/ [% }0 P2 T* k. e
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode9 D# {2 B! r, t, n0 _  G; s. r
  5.      81                         @ and irqs disabled( {2 @/ f. t! u& \" N/ u9 x3 y
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id
    8 o/ }2 s  ~* B: C( a) c* J% Q) F4 _/ P6 ?
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
    % E/ [8 J4 n  D4 [. {
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?; G' v+ k! q5 \8 o% k! S6 u  N
  9.      85     beq __error_p           @ yes, error 'p'5 i2 H# r1 v$ @0 @, G6 S, [
  10.      86     bl  __lookup_machine_type       @ r5=machinfo
    % k* v! j. \# l+ }! _
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?
    $ f: l4 P# X. H
  12.      88     beq __error_a           @ yes, error 'a'
    7 P$ A# x; p3 N
  13.      89     bl  __vet_atags
    3 J, _: B( o. u* x/ h: f! h
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。
! Q  ]; E$ |6 z0 |+ S9 _0 j2 X" c* e3 J* U# L
看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享分享 頂 踩 分享分享
2#
 樓主| 發表於 2008-10-9 15:32:16 | 只看該作者
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。
/ U3 |% w9 y) }# C+ F
" C0 V1 S3 ?) E; d' ]0 k1 H可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*
    # u8 A: G! w! c( K" @' U
  2.      60  * Kernel startup entry point.& H& H& ?. D2 I0 f2 s
  3.      61  * ---------------------------# i# F* T* ^% d& F! y" f
  4.      62  *
    6 S7 @6 U( p3 ~1 O/ u! \4 H& u
  5.      63  * This is normally called from the decompressor code.  The requirements
    9 H4 s+ }. `' [. c# V( E6 @
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
    8 H9 Z7 x8 y6 C& t
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。
9 k  z, K0 F5 n4 Q7 u4 |line 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)
' p3 x, a2 c0 g8 jline 82, 讀取CPU ID到r9; x! P$ v/ R7 I7 x' [
line 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"
    ) r2 L9 M9 C6 ?- ^% ~8 C
  2.      78     .type   stext, %function7 H2 }6 T; F' N7 V" R# d" d
  3.      79 ENTRY(stext)! E& g# b% X! w6 P! l6 l$ ^0 _
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    % ~# Q1 d9 i- T
  5.      81                         @ and irqs disabled
    5 m1 ]) O6 h! z: d0 U3 ~- Q, I# o$ R3 r
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id- P5 w) Q9 y+ I  N" |3 T
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,; L' U$ g$ ]7 j' N
line 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。& E1 _, J. I" ?7 ]: ]1 k+ N
line 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)4 G6 C  x5 @& K9 ^  g- S$ X
line l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。
0 `# _7 G3 e5 h, g# eline 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S- z) }( d5 P' U; P
line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。2 p3 h: b; u$ [: r+ {0 L; l

$ v7 V3 N( A" D0 R+ c__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* u, n' \+ p- G% g5 B2 ~. V8 ~, `
  2.     157 __lookup_processor_type:1 k6 G0 `: J4 Q* j8 Y9 l8 f
  3.     158     adr r3, 3f% R# }# y9 o$ H' f5 I
  4.     159     ldmda   r3, {r5 - r7}
    , v2 j- g2 i# u4 h$ N- n
  5.     160     sub r3, r3, r7          @ get offset between virt&phys
    7 h* O3 ]7 d. e4 w' a8 d( d
  6.     161     add r5, r5, r3          @ convert virt addresses to
    : s) I, |8 \$ b* r3 m
  7.     162     add r6, r6, r3          @ physical address space% H6 i( ?) ~* ]1 _5 \8 A8 _& v
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask6 q, C. O+ [/ r& i
  9.     164     and r4, r4, r9          @ mask wanted bits
    5 R0 \! S. z% @1 F, H2 `
  10.     165     teq r3, r4
    3 H) z  V7 p  N" m& o$ a3 ]. v
  11.     166     beq 2f) n8 _4 K6 P* v
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    5 F! R+ `2 w+ c1 X* o  @7 `
  13.     168     cmp r5, r6. P2 u& ~5 y( p2 s0 d- b/ ?2 @2 ?2 q
  14.     169     blo 1b7 y- [4 P1 t, G/ M" e0 @7 E- |; \2 e
  15.     170     mov r5, #0              @ unknown processor
    5 ?3 b6 \3 D" V( p% F  m, K
  16.     171 2:  mov pc, lr
    ; f4 o# i/ d  q& M

  17. : p3 S8 i$ v+ D, K0 S  s! ~: r
  18.     187     .long   __proc_info_begin8 w/ C  y0 L; w
  19.     188     .long   __proc_info_end8 n/ ~, g0 P5 v6 M3 U! }
  20.     189 3:  .long   .
    5 Y' s2 x' c2 y8 H
  21.     190     .long   __arch_info_begin  |0 |, D/ C- F1 }
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
3#
 樓主| 發表於 2008-10-13 17:20:35 | 只看該作者
我們從 head-common.S返回之後,接著繼續看。
2 u3 r! V8 F  o' w) O( m# b, B8 X  |
line 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。
' y% m. d2 y9 R* ~1 w" V7 aline 85, 就是r5 = 0的話,就跳到__error_p去執行。
5 |1 y/ R# q& C3 pline 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid
    " G2 u$ a: E5 s+ o6 w7 L
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?1 u0 n% U4 ?7 G+ m% c/ Z0 }2 ?9 G
  3.      85         beq     __error_p                       @ yes, error 'p'
    . p! l4 q% U: u  S* a+ ~0 X
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是
4 a0 ^' x: t+ a  u& K
9 S7 x3 v# ]# q2 G1 v- ^. w1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。1 Q6 u  }. X/ ]) j0 j

7 o; i; G. B. x5 a1 r# |2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */
    8 U; J1 y% ~9 U/ Q7 F
  2.      50 #define MACHINE_START(_type,_name)                      \
    8 J5 d# a2 U( r1 X0 D( ?
  3.      51 static const struct machine_desc __mach_desc_##_type    \5 e/ A- N0 T' i! T' f5 g, \: Z
  4.      52  __used                                                 \
    # Q) [8 O6 _: |3 k/ D
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \
    6 X* @4 I# V" I; v; I
  6.      54         .nr             = MACH_TYPE_##_type,            \
    4 I0 U$ _. O( v# e
  7.      55         .name           = _name,7 v# c. Q( D1 K$ i, X6 r
  8.      56# e" v  S9 w6 l+ F/ i+ q
  9.      57 #define MACHINE_END                             \) @) G; I; F5 a' {0 ?3 l
  10.      58 };
    . h- s, X; ^; `' W! t
  11.      /* 用法 */* @4 t1 e5 A9 z" L) H
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")
    1 b0 E1 ?  [& p
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    " A) f( o  B, i# {" F
  14.      95         .phys_io        = 0xfff00000,
    . ^' [$ S# z5 r# _  _0 {
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,
    5 ]3 h# H8 G5 k; z; m! [  m/ I
  16.      97         .boot_params    = 0x10000100,, n: c4 |+ F! P0 e4 C5 b- `
  17.      98         .map_io         = omap_generic_map_io,
    6 {$ ]6 }; X; V: [
  18.      99         .init_irq       = omap_generic_init_irq,# Q# f: {0 f3 r# K. P
  19.     100         .init_machine   = omap_generic_init,; s6 Z* }) U% M; t& e0 g
  20.     101         .timer          = &omap_timer,. P( L/ m% q# l' w% Q0 e. r
  21.     102 MACHINE_END4 f# U* b5 N7 D. T/ m2 V

  22.   i3 `6 L- X! u: Y) d
  23.     /* func */+ r$ x' W5 j9 ^% [
  24.     204         .type   __lookup_machine_type, %function, t2 r, T+ r: U* u+ z* w4 m
  25.     205 __lookup_machine_type:4 A# d+ c3 D6 s0 ^; n6 G* v
  26.     206         adr     r3, 3b: g3 x2 n! q. X8 P1 Y/ I
  27.     207         ldmia   r3, {r4, r5, r6}: N2 ]: ]* o9 T' Q  B( r. p; l' M
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys6 @+ N# ]/ W& |! ~
  29.     209         add     r5, r5, r3                      @ convert virt addresses to
    ' \3 Q- Z$ l5 }! T4 L* E5 _
  30.     210         add     r6, r6, r3                      @ physical address space
    + e7 C6 v; K, ?$ F( \
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    ; {1 y+ y/ e' q! W9 m
  32.     212         teq     r3, r1                          @ matches loader number?
    ( a  ^; G* E2 [7 ^$ Y* |
  33.     213         beq     2f                              @ found
    7 l- a7 ?7 w" W0 L. w/ c! h
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    ! T. w) O3 `# ~* o, I- R
  35.     215         cmp     r5, r6
    9 s' v( N% t2 k0 r
  36.     216         blo     1b) J6 d" r* @1 s2 b& T
  37.     217         mov     r5, #0                          @ unknown machine; m$ S; o, U$ \$ F, j5 I* f
  38.     218 2:      mov     pc, lr
複製代碼
4#
 樓主| 發表於 2008-10-13 17:56:46 | 只看該作者
接著我們又返回到head.S,5 \) q! z/ m' k: y: L
line 87~88也是做check動作。
) N. A$ p% j5 ^/ bline 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?
    : @) u! `' o# {2 N% R3 a( ]* [
  2.      88         beq     __error_a                       @ yes, error 'a'* L( L9 H- y2 \4 J- b* V3 A
  3.      89         bl      __vet_atags
    ' n: M, F* z6 K! [4 n% f" K
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。: S8 C" z8 J( P- h4 q5 }
line 246, 沒有aligned跳到label 1,就返回了。: o+ z3 i9 c0 F% N
line 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
1 F- \% k  Y, r* A/ d1 `) o  oline 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。. D6 i0 x  M* g
(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x544100019 Z9 c5 S1 c3 O/ m
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
    5 p' a, x" {7 c& L; T, N9 A
  3. / i, U. k6 U4 B; `/ }& S/ z
  4.     243         .type   __vet_atags, %function
    0 Z7 N! \9 c! Y. \+ R1 }3 O
  5.     244 __vet_atags:
    4 T6 g- \% `6 m) n. g
  6.     245         tst     r2, #0x3                        @ aligned?
    1 }* y2 R! U+ O4 }
  7.     246         bne     1f  P. d" m/ x6 l: R# L- D9 d  @
  8.     247. Q9 J$ T' R  F4 n( o
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?
    / p$ x. G, _, g. e4 E
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE
    * r/ z" @. J: T- x$ q" N
  11.     250         bne     1f3 L5 I1 E1 Z0 g, G
  12.     251         ldr     r5, [r2, #4]
    . {; A( W  J& Y  B; S1 B. k
  13.     252         ldr     r6, =ATAG_CORE
    $ T6 `7 p! S4 r% Z7 \; c. H
  14.     253         cmp     r5, r6
    2 A# g( O3 s0 J
  15.     254         bne     1f* I5 {6 N( G) O+ r5 v
  16.     255
    3 I1 [3 }# T2 H9 S( g# T
  17.     256         mov     pc, lr                          @ atag pointer is ok
    9 M  Z: N/ y1 N. V! C5 Z! F% ~
  18.     257( W! f, G+ g$ Q. O
  19.     258 1:      mov     r2, #0
    ; L! A; m9 u2 i& T  L9 R7 \9 Y* u0 k
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  4 T5 z6 H* E. W" I( x' E* k: [
line 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)' d1 C$ z! A% B& D) d/ z
哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
5#
 樓主| 發表於 2008-10-14 12:13:51 | 只看該作者
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:4 T8 y& Z/ R! @6 B
0 o9 Y! c  q4 y) x& s$ D
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。5 Q3 @: D. M  ^6 x6 W
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。
* U# C0 j$ L# {$ e: C2 C3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。
( ~" j: R4 N3 E: k! Q+ u* C3 w; o1 k; h5 R( Q  D% c
以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。' ~4 @3 S& e! w! O* w7 L

% b8 h" D$ p. L5 C1 K) Q. w5 P由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
6#
 樓主| 發表於 2008-10-14 12:14:47 | 只看該作者
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。1 @7 Z5 B: c# e6 F' D  h
  q& o% t/ O3 Z
『產生page table到底是要給誰用的?』
& d/ t- T: Q- e9 L$ a- B- X! R2 t, _% P4 n8 e
其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。
) U9 J6 E8 F/ y- Z6 _5 G( ]2 ~' Z7 o2 g2 ^6 G* ]
這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。- o6 u' f  W: [6 g

% Z7 {9 X) O7 H! m到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
7#
 樓主| 發表於 2008-10-14 12:15:51 | 只看該作者
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。
, w5 _  ]7 A( y3 Q- K- R4 W$ U1 B$ r; [; r& e$ `5 h/ t
現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。' Z- M* P  H: n1 ^7 Q- u
' _. F5 y0 P0 w" h, |& a6 J( m
知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。# ^1 P' R7 i7 y: z8 |! G

) n% L/ ~% ?: X. z% d# Yp.s. 字數限制好像變短了。   (看來很難寫)
8#
 樓主| 發表於 2008-10-14 13:56:17 | 只看該作者
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
9#
 樓主| 發表於 2008-10-14 13:57:39 | 只看該作者
現在,讓我們跳入create_page_tables吧∼0 s1 Y8 M4 p. g- S
line 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。
& r, l1 C( {/ o7 u5 P6 h
( |" k4 U- i, h- f只是這個位址因為你硬體規劃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 */
    & |7 m+ P3 Z8 h+ k& [# @# j+ o3 e# x
  2.      95 textofs-y       := 0x00008000- N' a% w0 A7 G  D
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */
    6 o3 K, s1 Q  A. k
  2.      40 #define PHYS_OFFSET             UL(0x10000000)7 n. j$ k$ c- j( p6 ^

  3. 0 [$ W: ~5 A: y) s# Z3 X
  4.      /* arch/arm/kernel/head.S */4 p, c( N6 ~' Q6 |# L3 l
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)
    2 d0 m# e3 z% L( H: G
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)) T  M3 G/ s( ]
  7. - A- J. K2 d* @; J/ @+ [( D
  8.      47         .macro  pgtbl, rd
    * ?0 e6 \3 ?# A8 r
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)* i- B5 h: k$ C7 T0 ?: N
  10.      49         .endm
    ! L( Z1 @  E0 G3 I- u
  11. + j; h$ n0 L6 ~3 o6 w) @/ ?& B
  12.     216         pgtbl   r4                              @ page table address
複製代碼
10#
 樓主| 發表於 2008-10-14 14:16:53 | 只看該作者
得到pg table的開始位置之後,當然就是初始化囉。& }! B7 @6 [  J& L% Y
line 221, 將pg table的base addr放到r0.8 b( V- d. \% P) Q- C4 ?
line 223, 將pg table的end addr放到r6.
. m" T+ j, p$ A( S; Nline 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4+ ~* _( J) Y. K6 S3 i8 z
  2.     222         mov     r3, #0
    / I/ |1 g( i5 l: h  o
  3.     223         add     r6, r0, #0x4000
    - Y( f8 B& `. f
  4.     224 1:      str     r3, [r0], #4
    4 q3 l+ [( g+ y7 q
  5.     225         str     r3, [r0], #4
    1 b. d6 S* A6 P" n! j
  6.     226         str     r3, [r0], #4
    - ]3 Q% R/ L' m
  7.     227         str     r3, [r0], #4
    : m  N& ^9 i8 k# M$ D
  8.     228         teq     r0, r6
    : [% J. @: Q4 O, ~, T
  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 | 只看該作者
問題怎麼填值??
/ y5 B9 \: Z8 Y" ^5 O& f拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。 , R; k+ Z2 y" B0 a( V2 V

: K# {/ y' p0 U# j念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)" Q8 U  Z8 V) ?  E" J8 ?
1. [31:20]存著section base addr
' G# K; Z& k/ I2. [19:2]存著mmu flags
6 P9 o, C/ |& Q0 C& B. h3. [1:0]用來辨別這是存放哪種page, 有四種:) ]9 i9 `% M" i! P; F# V1 h% K
   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)
- T7 Y1 T: y% @+ }4 W0 \. \4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
' K. Y8 |8 C8 k1 w+ w, H
3 H" V$ ?8 ]1 x來看code是怎麼設定。) f% v% t2 B' j- O0 q$ {
9 t, e/ R# B/ r; P8 b! ], v6 T8 G$ Z
line 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。
2 G& g  K4 N( g  H8 A/ W' G1 G, yline 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。
0 K& b0 c8 l8 U+ `所以前面兩個做完,就完成了bit[31:2]。
& E$ D0 h; B6 I- _" Iline 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20
    ' Z$ G9 D5 H9 I7 M
  2.     240         orr     r3, r7, r6, lsl #20
    ! m: i/ m+ d1 o$ ?- q
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
12#
 樓主| 發表於 2008-10-22 19:47:03 | 只看該作者
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼
8 p7 G- p6 \* A6 e1 M3 K1 `
& v( o) o: w2 \2 [7 h上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看  [7 Q' h- u% S2 V3 x
line 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。
' _+ |# _/ y" ?& H! a1 Q: n3 c8 P6 t( c" h6 x, W) d# r
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
    * r  [! U. G% I2 S6 F
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
    , ?7 ?4 ]1 h, k
  3.     249         ldr     r6, =(KERNEL_END - 1)' F" w6 }+ Y3 I1 k, ]
  4.     250         add     r0, r0, #4
    ; J2 n) p6 n9 t$ ]% Z7 N9 q. N
  5.     251         add     r6, r4, r6, lsr #18
    9 p) P, M$ Z) {* L, n
  6.     252 1:      cmp     r0, r6
    + ]( O6 Z) @1 u0 d4 M
  7.     253         add     r3, r3, #1 << 20# B, f: N6 Y) Z" U$ u
  8.     254         strls   r3, [r0], #42 l* y1 m# k/ B4 q
  9.     255         bls     1b
複製代碼
13#
 樓主| 發表於 2008-10-22 20:24:58 | 只看該作者
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。5 Q# b+ B3 L4 M
line 280~283,將要 map 的physical address的方式算出來放到r6。% z8 }. I, v& C# `, J
line 284,最後將結果存到r0所指到的pte。+ l, d( h$ I7 f1 V
# x% q! ]6 Z" ~" V) _+ T4 r# u
以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。# b3 M& c  G3 F9 p- r/ T' K
% ?; E' p- S5 M* q1 y" B
line 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 18
    ' j, z+ \, m( ]9 c3 z& S
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
    ! N9 ^: B* T! H# {3 r* r% c; ^+ N
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)! ]7 Q" o. c1 M0 M' |0 q, K
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)% ?+ m+ r2 A; A7 B
  5.     283         .endif4 C! n/ ^% B8 f
  6.     284         str     r6, [r0]
    4 v: z8 k9 @' I  O
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
14#
 樓主| 發表於 2008-10-22 20:37:08 | 只看該作者
自create page table返回後,我們偷偷看一下接下來的程式碼,
+ o% b4 q* Q. ^) x( f1 rline 99, 將switch_data擺到r13: h" H  `3 Q  y- ]
line 101, 將enable_mmu擺到lr3 U# z* V5 l7 c! e! E
line 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去
$ q" r) w. J9 s4 g5 o* `6 f
  n+ L9 j$ \) O  f& q其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。 $ O: F, B9 \- q8 t& i( z4 N" L

& S# C; G" Z& W( J- S/ F  b/ aswitch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after
    , X4 B) ^/ e3 k' ^1 [2 t
  2.     100                                                 @ mmu has been enabled
      w" V  Z& v$ \8 N3 }; _4 l
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    / _. w9 T. k3 Y% u9 T* }! ^
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
15#
 樓主| 發表於 2009-7-4 01:09:36 | 只看該作者
老店重新開張~
, h# m8 B0 J4 ?  v2 k$ r3 V# M! `
花了一些時間把舊的貼文整理到一個blog
0 }+ k" g) I9 J$ v0 d- I+ q有把一些敘述修改過
- l% a3 p  ?4 v希望會比較容易集中閱讀
+ k0 T/ {5 d" u目前因為某些敘述不容易1 |7 w; j$ a7 P5 W3 y
還是比較偏向筆記式而且用字不夠精確
8 a- `# ^* x( Q$ x5 C$ ]" [希望之後能夠慢慢有系統地整理
7 u- x3 C7 g% t大家有興趣的話" I3 P" {" R1 ]
可以來看看和討論
3 u3 g- @0 u1 I0 @9 d4 bhttp://gogojesseco.blogspot.com/
: u# U! x2 c: ^$ \- _5 \* G/ Y& R: G, R" x: V. ?" i9 j
以後可能會採取  先在chip123貼新文章9 d4 e+ y. }* [" N. L
慢慢整理到blog上的方式% E& g) I# D6 _% y
因為chip123比較方便討論 =). o3 y  K4 T6 {( `  v
blog編輯修改起來比較方便  [: j$ ]& ~9 I. z) Z. _" H5 \) T
閱讀也比較集中   大家可以在這邊看到討論. t  T7 {7 h* w5 o5 a" z' Z8 C
然後在blog看到完整的文章 (類似BBS精華區的感覺)

評分

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

查看全部評分

16#
 樓主| 發表於 2009-7-15 17:07:03 | 只看該作者
隔了很長一段時間沒update
% q& K1 F  M) y1 U) H之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after' e$ K! ?  j- }: k* X
  2.     100                                                 @ mmu has been enabled
    * a' {/ R) i: O( p( I3 M
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    ! V, [* V- E+ X5 Z
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)
$ K4 ?  c/ T" h0 W( vline 101, 將__enable_mmu的addr放到lr。(留作之後用)) W; h6 h6 `( I9 H( ^5 {& ?! j
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, #function
    # Q4 R: a# Q% x+ S. Z' [5 @
  2. 374 __arm926_setup:  J4 ]6 r: P# }' [% k' M
  3. 375         mov     r0, #0
    : x9 l/ D- `! ?. Q
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4
    4 r- }; U9 {' u9 i# [* x! l4 T
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4
    - y- s. X: T6 D) g# Y
  6. 378 #ifdef CONFIG_MMU
    * Y/ S9 b7 E8 }) B! b  m
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4
    / S- z: w# K& W( @, t
  8. 380 #endif' q" G# I- B% u

  9. ; {1 y( L9 H' K8 z3 D
  10. 388         adr     r5, arm926_crval
    : n$ ~2 w) u' L  l. K0 l) X
  11. 389         ldmia   r5, {r5, r6}( H2 ]9 ^( v; B* B. f) p6 w
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v48 U$ |" E& ~! _6 [8 }0 C; ~
  13. 391         bic     r0, r0, r5& u# v) n; C2 a4 C3 U, d
  14. 392         orr     r0, r0, r61 s7 U1 H$ X2 K8 r- Y
  15. # L5 C1 y& O% g1 i" x' \
  16. 396         mov     pc, lr
    4 T6 ~8 `3 N9 z9 h  X- v1 O! I
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,; P+ k% F4 c) W; h
line 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。: O6 |9 U6 L: o8 z  t. Z
line 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。). a7 G' G) ~* }% {
line 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
17#
 樓主| 發表於 2009-7-15 17:29:45 | 只看該作者
  1. 155 __enable_mmu:1 w0 ]) Q3 U  C! s, a
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \( j3 h/ \0 j8 }1 u/ ~
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \  l; {8 O( i4 [% S
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \) r2 h  J& L! c. R/ s( l9 R' ?  j
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    5 U8 R8 d, W/ G/ Y
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register
    / Z; u! h7 d+ ]5 F9 B7 J5 b
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer
    ; M, {1 S% c7 n3 T) b8 q4 _* y9 H
  8. 176         b       __turn_mmu_on: o$ O4 `  ?) M2 r
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明)
% e! D0 X+ m( Pline 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:
    , w" e! M* x; f7 D# q) x0 \) H$ \# }
  2. 192         mov     r0, r06 m- \9 k$ i8 t% c( [, `
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg; t" O& ^: i2 ?, Q$ L
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg9 x1 R7 j0 n6 V
  5. 195         mov     r3, r3
    ( g7 p' H& s- p0 }/ N" B1 j3 c- M, F
  6. 196         mov     r3, r3# |3 a& e) C- q8 h. m% S
  7. 197         mov     pc, r130 [4 s/ O% j' {& ]' W: r3 n
  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:
    # t. T# f9 F! J, Z# w5 g3 t6 U
  2. 19         .long   __mmap_switched
    * I# b  W. F8 x4 m2 s; n/ `3 j
  3. 20         .long   __data_loc                      @ r4
    , ~' ^: J* ?+ \2 a( X( Q; u* e
  4. 21         .long   _data                           @ r5: G" V  _: j' U( {$ S& Q. X8 s
  5. 22         .long   __bss_start                     @ r6
    ) K/ C1 t1 ?% |+ I" S
  6. 23         .long   _end                            @ r7, R* i% y0 n& X1 k9 T6 ^5 h
  7. 24         .long   processor_id                    @ r4
    ; f6 I& K2 S" @
  8. 25         .long   __machine_arch_type             @ r5
    3 @: p( n. e; m# |
  9. 26         .long   __atags_pointer                 @ r6
    / I, A7 Z9 G" i+ j1 Z$ H/ x9 J
  10. 27         .long   cr_alignment                    @ r7  l# h* A; Y7 M- W( ~, P$ w
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp
    + Y  P* [% X4 J9 Z
  12. 29
    4 w5 \# ]/ x: |
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
18#
 樓主| 發表於 2009-7-15 17:30:00 | 只看該作者
  1. 39 __mmap_switched:$ i8 V/ S0 F' ?# C- ~
  2. 40         adr     r3, __switch_data + 4
    ( D% K" a* ~( [7 E4 t
  3. 414 D% u7 P( a8 h) ]
  4. 42         ldmia   r3!, {r4, r5, r6, r7}
    ) O2 @; U5 u4 g/ I4 ]9 R% B
  5. 43         cmp     r4, r5                          @ Copy data segment if needed
    $ t$ F6 Z- X6 p0 P+ r+ f
  6. 44 1:      cmpne   r5, r60 @9 e1 D7 M' L" V4 s! j( D6 S
  7. 45         ldrne   fp, [r4], #44 X7 u/ g2 X. _' r0 e0 n
  8. 46         strne   fp, [r5], #4
    ! L0 j( h: O$ v* D6 E, n
  9. 47         bne     1b
    ) ^; q9 O3 n5 K8 @
  10. 48
    8 Q- S' ~1 o* X
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)
    / e1 A, i/ E7 J" C  F
  12. 50 1:      cmp     r6, r70 I2 e( J1 `2 z4 I8 H
  13. 51         strcc   fp, [r6],#4/ i+ U: w* I/ b/ z7 G* S: x
  14. 52         bcc     1b
    , {8 C! s  ]" Z5 ]0 ?0 h1 f- z
  15. 53' U8 |9 Y$ z0 T/ g# _$ u6 G
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}
    ( H. n8 [& I( i) g& M( \* @
  17. 55         str     r9, [r4]                        @ Save processor ID
    ) B. y, P# h" B* W. g% W9 ~; P
  18. 56         str     r1, [r5]                        @ Save machine type
    . z6 l- c( N) m( v  ?, `5 e5 _
  19. 57         str     r2, [r6]                        @ Save atags pointer. Q4 D9 J6 P: Q" T& L1 i/ Q) u4 Z" c; l
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit
    , @9 ~5 ]5 u  D3 i9 S0 }) j
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values1 \2 u* ?7 G; N; O8 N. V0 M+ M
  22. 60         b       start_kernel
    6 u. d+ Z5 O9 u
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
) d/ V# T" w/ @! Uline 39,將__data_loc的addr放到r3
" j% {* @: K" f  c8 J) Qline 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7" u( P5 S5 x% ]( H! d
line 43~47,看看data segment是不是需要搬動。3 R1 b# H. u9 O% K" [3 J
line 49~52, clear BSS。
% ?; z: r! [& [3 s. P3 o4 X' B) C
; S1 Y" ?9 V: i由於linux kernel在進入start_kernel前有一些前提必須要滿足:8 V3 F2 c" O6 x3 [
r0  = cp#15 control register! j: U  v- `9 |( x& x- `& e* h
r1  = machine ID, ?# e/ B% y2 f( q/ G
r2  = atags pointer
" w/ k) I2 j7 \0 }: Tr9  = processor ID' q* A) w- H2 x

1 ^  N  u3 J5 |2 F' l- V- l+ R所以line 54~59就是在做這些準備。. I+ V6 l% ?# D; h- \! M( R7 O# `- K1 w1 {
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)- ]/ d$ x! n: i/ \
. \# [. j, y5 V
看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示
3 l7 ~2 b1 ~; A9 m我們真正的開始linux kernel的初始化。
) t9 q3 p9 `5 Y& H0 U像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。
4 p' Q; i) z4 m到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。

評分

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

查看全部評分

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

本版積分規則

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

GMT+8, 2024-5-3 05:31 PM , Processed in 0.137008 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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