Chip123 科技應用創新平台

 找回密碼
 申請會員

QQ登錄

只需一步,快速開始

Login

用FB帳號登入

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

trace linux kernel source - ARM - 03

[複製鏈接]
跳轉到指定樓層
1#
發表於 2008-10-7 14:00:18 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
到目前為止,我們已經進展到kernel幫自己relocate完,並解將自己解壓縮到一個地方要準備開始執行,那疑問來了?到底是跳到哪裡去了,因為./compressed/head.S最後一行居然是
' j  J' Z. }; u) @% I『mov pc, r4』
5 K' W5 D# U' \, `( J) `4 sr4只代表了解壓縮完後kernel的位址,那究竟整包kernel編譯的時候,哪個function哪個東西被放在最前面咧?!
6 G2 ]7 K+ q! O2 o$ G
8 q1 W3 q( U) @2 U  n) ?- k所以我們又必須開始找於kernel link的時候是怎麼被安排的,有了前面的基礎,我們可以從Makefile知道程式碼如何被編譯。至於link上的細節,例如有那些section和section先後順序等等,可以從 lds 檔來規範。6 j6 \" O$ V1 g: G* a  r
: p' X# k! n; N7 F% Q
有興趣的人可以看一下 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)4 ~' S' H6 x3 M( Q+ b9 F. a3 F
我們可以發現第一個section是『.text.head』,裡頭的_stext從目前的位置開始放。0 [9 f) t" C: m+ ]% p7 C% G
於是我們曉得只要找到屬於.text.head這個section,並且是_stext這個symbol的程式碼,就是解壓縮完後的第一行程式碼。
  1.      26     .text.head : {, W& b: K5 T: X5 w
  2.      27         _stext = .;& ~6 W% I# l1 r! o8 ?* c
  3.      28         _sinittext = .;
    9 ?, W3 R* l; B: T$ b/ o
  4.      29         *(.text.head)0 j- Z* f" M8 B2 p( |1 @# V" H
  5.      30     }
複製代碼
用指令搜尋一下,發現 ./arch/arm/kernel/head.S 裡頭有關鍵字(如下),結果我們從./arch/arm/boot/compressed/head.S跳到了./arch/arm/kernel/head.S
  1.      77     .section ".text.head", "ax"
    ( G# o  ?* a1 {. s' [
  2.      78     .type   stext, %function
    ' P0 S5 }2 l5 k# }+ K: r3 s
  3.      79 ENTRY(stext)4 ?9 y; x% B, Z8 x- W. E+ R2 d, x
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode7 a  u7 o8 W/ I7 `9 |1 o* |
  5.      81                         @ and irqs disabled/ P2 e4 {7 d8 Q0 Q7 l* [; Z- C
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id( y) J' \1 ?) G
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid% }) b9 T5 W, Y: m
  8.      84     movs    r10, r5             @ invalid processor (r5=0)?, g& z# J; K  X* u
  9.      85     beq __error_p           @ yes, error 'p'
    + ~/ p2 |$ s3 A& D
  10.      86     bl  __lookup_machine_type       @ r5=machinfo: C5 w2 ?6 Q* f1 ~
  11.      87     movs    r8, r5              @ invalid machine (r5=0)?
    9 ^2 U& z* q* W- h0 W# E
  12.      88     beq __error_a           @ yes, error 'a'
    ) F) W. y0 {8 ?& i2 H
  13.      89     bl  __vet_atags- p& t+ d& Z6 g
  14.      90     bl  __create_page_tables
複製代碼
既然找到了檔案,我們又可以開始繼續。% }+ L' W8 A( b0 n6 v/ N
- ~% S: W# h2 C% e& t6 W& F
看了一下,程式碼多了很多bl的跳躍動作,看來會在各個function跳來跳去。
分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享分享 頂 踩 分享分享
2#
 樓主| 發表於 2008-10-9 15:32:16 | 只看該作者
既然跳到真正的kernel開始跑,表示進入重頭戲,在進入kernel之前arm的平台有一些預設的狀況,也就是說arm kernel image會預設目前的cpu和系統的狀況是在某個狀態,這樣對一個剛要跑起來的OS比較決定目前要怎麼boot起來。# S8 h) [3 C- Z# b5 L( `

7 o- B$ v4 B) y; G; h$ ^6 v( m可以看一下comment,有清楚的描述。這也幫助我們了解為什麼decompresser的程式跑完之後,還要把cache關掉。
  1.      59 /*4 S; G1 ?2 N. M% s
  2.      60  * Kernel startup entry point.+ m* C) z7 q# }; O
  3.      61  * ---------------------------8 m* [; C5 |4 h
  4.      62  *& y6 K" h5 y% F
  5.      63  * This is normally called from the decompressor code.  The requirements$ l( k0 @$ }3 ]4 w7 [5 |4 V
  6.      64  * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
    " V: G1 ~/ a: \/ H% _# r4 k
  7.      65  * r1 = machine nr, r2 = atags pointer.
複製代碼
基於以上的假設,當program counter指到kernel的程式的時候,就會從line 80開始跑。
9 I0 n; C  ~) g4 ~; u' a5 J1 gline 80, msr指令會把, operand的值搬到cpsr_c裡面。這是用來確保arm cpu目前是跑在svc mode, irq&fiq都disable。(設成0x1就會disable,definition在./include/asm-arm/ptrace.h)- o8 P* K' b" W( x
line 82, 讀取CPU ID到r9
( B  a+ b. x, V, S/ l# e9 x8 C; hline 83, 跳到 __lookup_processor_type 執行(bl會記住返回位址)。
  1.      77     .section ".text.head", "ax"
    ( b3 |, u2 B) n# ~2 y
  2.      78     .type   stext, %function
    : i* E% A+ f/ Y4 Y8 x- P( P
  3.      79 ENTRY(stext)
    7 `, q& \: |4 E; _4 M" H6 }: k
  4.      80     msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode  W2 V8 Q8 h: ?3 m& d8 o& D; d
  5.      81                         @ and irqs disabled
    7 r) G- x! }$ j% S6 {+ r) v5 o. G
  6.      82     mrc p15, 0, r9, c0, c0      @ get processor id1 h! o+ {* [) N* Y9 X
  7.      83     bl  __lookup_processor_type     @ r5=procinfo r9=cpuid
複製代碼
接著會跳到head-common.S這個檔,$ h% j% G. U* z1 R  k* _. A& I8 C
line 158, (3f表示forware往前找叫做『3』label)將label 3的address放到r3。+ `. g1 J4 u3 ^
line 159, 將r3指到的位址的資料,依序放到r7, r6, r5.(ldmda的da是要每次都位址減一次)7 O( H4 s2 i2 n: g# l
line l60, 161, 利用真實得到位址r3減去取得資料的位址得到一個offset值,這樣可計算出r5, r6真正應該要指到的地方。
6 h4 {; Q" A& U* A# ^- w( e( @line 163~169,這邊的程式應該不陌生,就是在各個CPU info裡面找尋對應的structure。找到的話就跳到line 171,返回head.S9 U, Y: H  L& q0 E# ?1 P9 l5 t& m
line 170, 找不到的話,r5的processor id就放0x0.表示unknown id。' R' B, U  V, Z. E
1 x4 J5 r- U/ {: [8 |1 Z
__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
    % D5 Z7 ]7 S9 X
  2.     157 __lookup_processor_type:$ `# a5 y: f+ h2 @! s' w: O
  3.     158     adr r3, 3f# T9 v: N* J3 X* T$ N
  4.     159     ldmda   r3, {r5 - r7}
    * {# w- M; b5 Z9 T
  5.     160     sub r3, r3, r7          @ get offset between virt&phys8 B0 Y8 ?  Q+ n/ ?/ {( ]% q
  6.     161     add r5, r5, r3          @ convert virt addresses to
    9 m: _/ P. j& B; m7 p6 K
  7.     162     add r6, r6, r3          @ physical address space
      t; |0 m7 y5 Q$ r  [
  8.     163 1:  ldmia   r5, {r3, r4}            @ value, mask4 O+ F5 R; ]( R4 J* i& d2 W- N$ E; y
  9.     164     and r4, r4, r9          @ mask wanted bits
    1 ^0 @6 G  R7 C5 ?" p/ Y
  10.     165     teq r3, r4. _$ ~$ U5 i6 n* a$ p
  11.     166     beq 2f& H$ m9 `8 c. q
  12.     167     add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)4 @) ~2 K% _  ?& m1 D" _! N
  13.     168     cmp r5, r6) b. O# D$ w6 q$ D9 b2 L+ E
  14.     169     blo 1b
    8 P2 B' F: _* A, A4 o- E7 I
  15.     170     mov r5, #0              @ unknown processor/ |- [: o* y1 o# |- J; D. u
  16.     171 2:  mov pc, lr2 Z. c% e. T9 e

  17. , P: H( x" T$ c( \5 D1 A
  18.     187     .long   __proc_info_begin
    0 H6 q# ?% y5 r7 B, ]
  19.     188     .long   __proc_info_end
    ( Q4 P; g7 N9 u- N4 z/ t
  20.     189 3:  .long   .0 ]- @2 U- F' Q
  21.     190     .long   __arch_info_begin
    * ^- f) G. v& M, G& k# U
  22.     191     .long   __arch_info_end
複製代碼
跳了很多檔案,建議指令和object code如何link的概念要有,就會很清楚了。
3#
 樓主| 發表於 2008-10-13 17:20:35 | 只看該作者
我們從 head-common.S返回之後,接著繼續看。2 I5 f. A% P) k
" @6 ?1 n4 l( r4 K% z5 v- |
line 84, movs的意思是說,做mov的動作,並且將指令的S bit設起來,最後的結果也會update CPSR。這個指令執行的過程當中,會根據你要搬動的值去把N and Z flag設好。這個是有助於下個指令做check的動作。" P7 d4 ]8 S7 c! @8 L: f
line 85, 就是r5 = 0的話,就跳到__error_p去執行。
2 u, n% O% t& b/ k" f0 @0 Nline 86, 跳到__lookup_machine_type。原理跟剛剛找proc的資料一樣。
  1.      83         bl      __lookup_processor_type         @ r5=procinfo r9=cpuid/ y+ z# `6 t) l* y$ E
  2.      84         movs    r10, r5                         @ invalid processor (r5=0)?
    6 K. q: `. J" ^  ?8 h# |) q
  3.      85         beq     __error_p                       @ yes, error 'p'
    - G; T2 q: k, x+ \
  4.      86         bl      __lookup_machine_type           @ r5=machinfo
複製代碼
看得出來跟proc很像,有個兩個小地方是
! o$ S, B- C" C3 w5 O6 d- O: ?( d  [4 ^# }: g0 I% z, |" z
1. line 207,用ldmia不是用ldmda。原因就在於存放arch 和 proc info 的位址剛好相反。一個在lable3的上面。一個在的下方。設計上應該是可以做修改的。( M8 r9 i* k# ]4 M4 K
1 [4 V& R: k0 W" W% L: M
2. arch定義的方式是透過macro,可以先看 ./include/asm-arm/mach/arch.h。裡頭有個 MACHINE_START ,這邊會設好macro,到時候直接使用就可以把arch的info宣告好。 例如 ./arch/arm/mach-omap1/board-generic.c
  1. /* macro */
    3 [( g$ m$ \3 E
  2.      50 #define MACHINE_START(_type,_name)                      \4 v8 K* \0 f* {* W1 l& [9 V8 v
  3.      51 static const struct machine_desc __mach_desc_##_type    \( Y* d2 v9 z6 O/ [: x+ b
  4.      52  __used                                                 \) s+ f8 z1 a, T$ y: F
  5.      53  __attribute__((__section__(".arch.info.init"))) = {    \' U2 ]4 O4 d; g/ A
  6.      54         .nr             = MACH_TYPE_##_type,            \
    / k: ?/ ^( N/ I) k: a- a% d2 ?- C- @
  7.      55         .name           = _name,
    ( d, g3 I7 E# I
  8.      56
    7 q' I8 ?5 f4 ?
  9.      57 #define MACHINE_END                             \
    7 a: t6 w+ k7 F6 f! U1 z
  10.      58 };$ T5 R& y4 t( L
  11.      /* 用法 */
    / x) Z" y# u6 q+ e9 ?+ d% q
  12.      93 MACHINE_START(OMAP_GENERIC, "Generic OMAP1510/1610/1710")2 {& M6 J7 |$ f2 O: w- k! P
  13.      94         /* Maintainer: Tony Lindgren <tony@atomide.com> */
    ( x# b4 F9 {& Q9 g5 I
  14.      95         .phys_io        = 0xfff00000,% a( y! C3 D2 l
  15.      96         .io_pg_offst    = ((0xfef00000) >> 18) & 0xfffc,, X1 G* K# W4 V3 L3 W* d
  16.      97         .boot_params    = 0x10000100,
    $ e: t- ]6 Z! g
  17.      98         .map_io         = omap_generic_map_io,
    / X7 d% C. u- w
  18.      99         .init_irq       = omap_generic_init_irq,
    0 f) Q( D, }9 t
  19.     100         .init_machine   = omap_generic_init,0 _+ j2 l% t9 o+ F4 M3 b# s, K+ r
  20.     101         .timer          = &omap_timer,. U6 n0 _. x3 e+ ^' q& K& m
  21.     102 MACHINE_END) q8 ~6 m7 i. t" R5 Z7 q

  22.   U  S% K, @/ b+ `5 b, U' M  c) ~
  23.     /* func */
    9 \+ b1 V$ a+ F0 v! B5 ~1 h1 }/ [) c
  24.     204         .type   __lookup_machine_type, %function
    ) }* k/ `1 O6 s
  25.     205 __lookup_machine_type:
    ; y; S/ @- }* S! \0 D' c5 d2 U
  26.     206         adr     r3, 3b# i9 {5 B7 J* T; ^( a8 m
  27.     207         ldmia   r3, {r4, r5, r6}" O7 ^, b3 @, C6 s, H
  28.     208         sub     r3, r3, r4                      @ get offset between virt&phys! S( X+ J: K0 Z' ?2 }6 a2 I
  29.     209         add     r5, r5, r3                      @ convert virt addresses to- [* [$ l  D! X& m
  30.     210         add     r6, r6, r3                      @ physical address space
    # @& K  D" t8 K& p4 l
  31.     211 1:      ldr     r3, [r5, #MACHINFO_TYPE]        @ get machine type
    # E* E( V$ b5 P: Y. e
  32.     212         teq     r3, r1                          @ matches loader number?/ \: N4 {" a  A" ]+ d
  33.     213         beq     2f                              @ found1 s9 H  J$ C) p/ J
  34.     214         add     r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc
    . }5 @8 c. n6 n! T5 R) ~
  35.     215         cmp     r5, r6! Z' x1 t2 k* E# l2 Z1 b
  36.     216         blo     1b, z5 R3 e' r7 l2 G1 G* ?$ U
  37.     217         mov     r5, #0                          @ unknown machine
    8 [* s+ V9 g" ~2 v4 @) Y, c5 }
  38.     218 2:      mov     pc, lr
複製代碼
4#
 樓主| 發表於 2008-10-13 17:56:46 | 只看該作者
接著我們又返回到head.S,
) `1 }5 c# Z0 v# Y1 Z' iline 87~88也是做check動作。- a) Z- k( N/ \4 |9 j+ o
line 89跳到vet_atags。在head-common.S
  1.      87         movs    r8, r5                          @ invalid machine (r5=0)?
    " c- t$ Q0 P$ g0 {3 K
  2.      88         beq     __error_a                       @ yes, error 'a'- Z0 P9 R; i- E4 L
  3.      89         bl      __vet_atags, @8 A1 o" J# s  Y8 O) F+ q: g" _
  4.      90         bl      __create_page_tables
複製代碼
line 245, tst會去做and動作。並且update flags。這邊的用意是判斷位址是不是aligned。
* B, ]' X/ t: E7 V, }0 E9 Gline 246, 沒有aligned跳到label 1,就返回了。7 q* I- K% m, p3 j1 O
line 248~250, 讀取atags所在的address裡頭的值到r5,看看是否不等於ATAG_CORE_SIZE,不等於的話也是返回。
2 |6 i# L( \) n+ I* d& \% Y' \9 V9 Xline 251~254, 判斷一下第一個達到的atag pointer是不是等於ATAG_CORE。如果正確的話,等一下要讀取atag的資料,才會正確。
  [5 e& y: `% a$ D3 u(atag是由bootloader帶給linux kernel的東西,用來告知booting所需要知道的參數。例如螢幕寬度,記憶體大小等等)
  1.      14 #define ATAG_CORE 0x54410001
    3 _3 x8 q! M) O, `+ B, j
  2.      15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)- p, j' K2 G4 e+ r4 O6 r  u8 t

  3. . e" V: o: P: I  q( j% Z! \
  4.     243         .type   __vet_atags, %function0 K+ J8 j6 y; H+ U
  5.     244 __vet_atags:8 t7 Y: S, K9 w9 W0 p
  6.     245         tst     r2, #0x3                        @ aligned?7 `8 ]0 o1 H# b% L+ v6 A/ \
  7.     246         bne     1f
    " N' ~' U; N/ e" j! d
  8.     247
    , V! W) B! s9 `6 Z. u
  9.     248         ldr     r5, [r2, #0]                    @ is first tag ATAG_CORE?
    1 w* }0 ?/ }! F" H- h
  10.     249         subs    r5, r5, #ATAG_CORE_SIZE0 W. @  X: I: ]& Y  L& \
  11.     250         bne     1f/ G/ u" V3 h! d: W6 Q0 M
  12.     251         ldr     r5, [r2, #4]
    5 A9 F4 Y- W6 _" y" H
  13.     252         ldr     r6, =ATAG_CORE
    + ~, v* R9 v2 s5 Z
  14.     253         cmp     r5, r65 F$ Y, b$ y7 [6 U% z: y. ]9 ^- r
  15.     254         bne     1f
    1 A8 b6 L; {2 E$ B5 e
  16.     255: F1 Y# V9 J/ z
  17.     256         mov     pc, lr                          @ atag pointer is ok- ~0 t7 x/ q' L2 _( ?, @
  18.     257
    - ]- U  `' C9 V4 O* A! d  |
  19.     258 1:      mov     r2, #0
    8 s7 V' Y# n" i# v  t
  20.     259         mov     pc, lr
複製代碼
接著我們又跳回去head.S。  + A' Z+ r; z( e8 y! w8 F
line 90,又跳到 __create_page_tables。   (很累人....應該會死不少腦細胞)$ j/ o% Y/ d' M
哇!page table?!!如雷貫耳的東西,不知道會怎麼做。剛剛偷看了一下,@@還蠻長了,先到這邊好了,下次在繼續寫。
5#
 樓主| 發表於 2008-10-14 12:13:51 | 只看該作者
由於code看起來似乎越來越難解釋,會需要在檔案之間跳來跳去。建議一些基礎的東西可以再多複習幾次(其實是在說我自己 ),閱讀source code上收穫會比較多。例如:! P6 b/ p; x9 m5 A& s4 A
% K6 d7 N$ f$ i0 ?1 G, _/ W
1. arm instruction set - 這個最好每個指令的意思都大略看過一次,行有餘力多看幾個版本,armv4, v5 or v6。! v( s- s. s0 c8 Y7 k
2. compiler & assembler & linker - toolchain工具做的事情和功能,大致的流程和功能要有概念。
* }* e& b4 M# j% S3 A& h9 V' D; D" {3. Makefile & link script - 這兩個功能和撰寫的方式要有簡單的概念。9 ^" a2 d6 V! r/ G( z) c

7 t3 V! ?- C, Q+ W, Z  r* y% m0 L以上1是非常重要的重點,2&3只要有大致上的概念就可以,因為trace code的時候,有時需要跳到Makeflie&Link script去看最後object code編排的位址。! |3 h6 H, _( u! t6 f6 c

7 C  d/ Y& E# w0 W, L; \由於我們trace到了page table這個關鍵字,在開始之前,稍微簡短的解釋,為了幫助了解,儘量用易懂的概念講,有些用詞可能會和一些真實狀況不同,但是懂了之後,應該會有能力分辨,至於正式而學術上解說,很多書上應該都有,或是google一下就很多啦∼
6#
 樓主| 發表於 2008-10-14 12:14:47 | 只看該作者
page table本身是很抽象的東西,尤其是對所謂的 user-mode application programmer 來說,大部分的狀況它是不需要被考慮到的部份,但是他的產生卻對os和driver帶來了很多影響。我們從一個小小的疑問出發。
6 Q; \4 f0 V' e! p2 g) y7 t$ y" ?/ d
『產生page table到底是要給誰用的?』. p6 k( }7 T3 Q2 u" f" P0 _
8 m7 d% \3 a9 c8 j$ Z
其實真正作用在它上面的H/W是MMU,一旦CPU啟用了MMU,當cpu嘗試去記憶體讀取一個operand的時候,cpu內部打出去的位址訊號都會先送到mmu,mmu會拿著這個位址去對照page table,看看這個位址是不是被轉換到另外一個位置(還會確認讀寫權力),最後才會到真正的位址去讀寫。, D' c' @8 I3 |4 U' \

3 j# k8 J, d" f' @這樣來看CPU其實一開始打出去的位址訊號其實不是最後可以拿到資料的位址,我們稱為virtual address(va),mmu打出來的位址才能真正拿到資料,所以我們把mmu打出去的位址稱為physical address(pa)。那用來查詢這個位址對照關係的表格,就是page table。5 s- M* w  l; U9 y( D4 T& U) r5 ^# d

- a5 o6 j2 T! |+ J- A0 R到這邊我們有一個簡單的概念。來想像一個小問題,一個普通的周邊(例如你的顯示卡),因為他並不具有MMU功能,hw只看得懂pa,但是CPU卻是作用在va,假如我想去對hw的控制暫存器做讀寫,到底要用va還是pa?
7#
 樓主| 發表於 2008-10-14 12:15:51 | 只看該作者
這時,寫driver的人就必須要小心,設定給硬體看的位址必須要先轉成pa(像是設定DMA),給CPU執行的程式碼要使用va,這對一開始嘗試寫driver但是又不瞭解va pa之間的差別的人,常常產生很多疑問。& w5 I/ ~4 K- @# G  Y& w
6 ~) j( f5 d7 R4 T
現在我們回頭想想OS,既然我們跑到create page table,可以預期的是他想要將MMU打開,因此希望預先建立起一個page table,讓MMU知道目前os想規劃的位址對應和讀取權力是如何被安排的。所以os必須考慮到現在的系統究竟是長怎樣?應該要如何被安排?是不是有那些要被保護?好吧∼因為我們完全對os不了解,也不知道該安排什麼,只能祈禱在trace code完後,可以找到這些問題的答案,或者發現一些沒想到的問題。
) l9 B" |( A' \0 I' x
" q6 K! R1 l; m% q0 }% v  J知道了page table的大致上的功能,下篇就可以專心的研究這個table的長相,和它想規劃出的系統模樣。
$ x( k8 g. _  l2 X% D3 z# y4 C3 W: l' b# V4 ~
p.s. 字數限制好像變短了。   (看來很難寫)
8#
 樓主| 發表於 2008-10-14 13:56:17 | 只看該作者
由於字數限制挑整,用詞儘量精簡,以便可以貼必要source。另外,以後寫到一段落,考慮收集成一篇完整的文章,這樣應就不會因為用回覆的方式,造成閱讀斷斷續續的問題。希望有人對文章呈現方式有想法的話,可以跟我講。
9#
 樓主| 發表於 2008-10-14 13:57:39 | 只看該作者
現在,讓我們跳入create_page_tables吧∼& x( Z! @; \7 O/ c6 I& N0 a# S
line 216,會跳到pgtbl的macro去執行,其實只是載入一個位址。
/ K3 }4 p: y. S7 K& M% Q' x8 p
! R# s9 J" @( U) n$ N6 G9 W# H* r只是這個位址因為你硬體規劃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 */, j4 x$ b4 Z, C9 C% m; t2 p; n
  2.      95 textofs-y       := 0x00008000
    % w+ j; b; _7 `8 _9 S/ x; U
  3.     152 TEXT_OFFSET := $(textofs-y)
複製代碼
  1. /* include/asm-arm/arch-omap/memory.h */
    7 m: i$ `5 V5 ?9 m. M
  2.      40 #define PHYS_OFFSET             UL(0x10000000)
    0 f- Q, q. z. Z; U: _2 i0 J# \7 y- c

  3. ( X- y6 O* N& ]! o8 H
  4.      /* arch/arm/kernel/head.S */
    3 w4 o; b1 ^# ]5 Q
  5.      29 #define KERNEL_RAM_VADDR        (PAGE_OFFSET + TEXT_OFFSET)
    ; M2 {! F; o3 t( z: q
  6.      30 #define KERNEL_RAM_PADDR        (PHYS_OFFSET + TEXT_OFFSET)
    ( f% H, j! Q+ f$ H6 b2 R

  7. 2 E) I3 x+ t; d
  8.      47         .macro  pgtbl, rd
    9 P5 |, i$ v! H% u+ f* k
  9.      48         ldr     \rd, =(KERNEL_RAM_PADDR - 0x4000)+ I# O' X7 a% w& E6 b
  10.      49         .endm8 f1 n$ x8 ~) j, g
  11. - U! D' l/ h. t; Y. d3 H8 O9 b
  12.     216         pgtbl   r4                              @ page table address
複製代碼
10#
 樓主| 發表於 2008-10-14 14:16:53 | 只看該作者
得到pg table的開始位置之後,當然就是初始化囉。
6 d) ~" a9 I, }, |9 Bline 221, 將pg table的base addr放到r0.: \- Y9 j+ a* s! _( O* b1 U# D: e
line 223, 將pg table的end addr放到r6.
# H/ F, f- D! }: Yline 224~228, 反覆地將0x0寫到pg table的區段裡頭,每個loop寫16bytes. (4x4),直到碰到end,結果就是把它全部clear成0x0.
  1.     221         mov     r0, r4
    % z9 l& V' U; c9 m( m
  2.     222         mov     r3, #0. z) ]6 S( f# o' j/ t
  3.     223         add     r6, r0, #0x40002 F) w4 F# O: Y& V' w
  4.     224 1:      str     r3, [r0], #41 U9 a8 C" V$ b! O( {
  5.     225         str     r3, [r0], #4
    ! Y4 s0 \  S! p: k; A9 u. D, g
  6.     226         str     r3, [r0], #40 @. {- }; d7 D8 ]5 ^1 q1 }7 b) F
  7.     227         str     r3, [r0], #4
    . Z! C7 y: K. o  K/ c
  8.     228         teq     r0, r6
    0 W- a4 V, W- j$ I
  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 | 只看該作者
問題怎麼填值??
4 ?% b7 p: D3 ^0 P拿出ARM的手冊,翻到MMU章節。一看發現page有很多種,還得分first level和second level。
* [% t; J/ }. {0 A; R$ ]% L6 {0 X7 ^1 g1 n7 j$ H' R
念書時的印象,是從1st level在去查2nd level,先看怎麼設定1st level (不知以前老師有沒有亂教)# u  _/ Z6 V3 g% G
1. [31:20]存著section base addr
8 A4 g9 Z# u4 _2 ~) }; o, j) ^1 Z% q3 f2. [19:2]存著mmu flags, A: u& K3 P  f3 y8 u
3. [1:0]用來辨別這是存放哪種page, 有四種:6 x  p# U0 Q4 d# u) z
   a. fault (00) b. coarse page (01) c. section (1st level) (10) d. fine page (11)) E2 F2 ?) G0 I7 k2 Z  e2 G0 k
4. pg tabel資料要存放到 [31:14] = translation base, [13:2] = table index , [1:0] = 0b00 的位址
3 U- Y* R* \3 q4 v$ h1 |
" \" H) f( Z$ j% u& c1 R5 q$ A- u來看code是怎麼設定。: H9 ?: Q" {$ A+ F! h. g

1 A$ U! u" F! D, Eline 239, 將pc的值往右shift 20次放到r6.這是取得前20高位元的資料,有點像是 1.。
. W5 B7 y8 T! \& R# cline 240, 將r6往左shift 20次之後,和r7做or的動作,剛好也是 2.提到的mmu flags和1.處理好的資料做整理。* v- ]. ~. m) ?# z# F
所以前面兩個做完,就完成了bit[31:2]。/ b: f/ j; {- X! b
line 241, 將r3的資料寫到r4+(r6<<0x2)的地方去,剛好是4.提到的位址。(lucky)
  1.     239         mov     r6, pc, lsr #20* ?2 ]# D; P2 O# N) c5 @+ S
  2.     240         orr     r3, r7, r6, lsl #20$ A, B3 G5 P: J, i  ?7 v1 f/ `
  3.     241         str     r3, [r4, r6, lsl #2]
複製代碼
p.s. 用pc值剛好可以算出當前的page。
12#
 樓主| 發表於 2008-10-22 19:47:03 | 只看該作者
最近又被釘上了,開始忙碌,大概沒太多時間貼文∼
0 d- Q: l' O: {5 ^# `  B" l2 k  f" C4 ?$ h: U3 K3 Y2 _
上篇已經將pc所屬的page table entry(pte)設定好,接著繼續看
# k" B. K. F. ^line 247, 248, 將KERNEL_START的位址往右shift 18算出pte的offset,(等於line239&241,239&241是先shift right 20然後shift left 2),並將剛剛r3的值設給pte。『!』的意思是會把address存到r0。5 k  |, D" N) d" U; s, E
' M' x0 @7 T3 y* n
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& a& D) d2 C- T# k- }8 Z6 M
  2.     248         str     r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
    ! u, M* I# e: G
  3.     249         ldr     r6, =(KERNEL_END - 1)
    2 L1 V! m# i+ ~4 M6 [$ T, w
  4.     250         add     r0, r0, #4) P, K' f5 \# R/ o' L
  5.     251         add     r6, r4, r6, lsr #18: r1 W' B0 F0 j9 H1 `' b
  6.     252 1:      cmp     r0, r66 W) j3 \6 A6 R2 I) W& p9 G9 m
  7.     253         add     r3, r3, #1 << 20' a$ H" ]# J7 [1 ~
  8.     254         strls   r3, [r0], #4
    * Z. }) c" m9 D3 E) W
  9.     255         bls     1b
複製代碼
13#
 樓主| 發表於 2008-10-22 20:24:58 | 只看該作者
line279,PAGE_OFFSET是規範RAM mapping完後的virtual address,我們將這個位址所屬的pte算出來放到r0。
; i* J5 }: f5 ]: O# Sline 280~283,將要 map 的physical address的方式算出來放到r6。
% O3 V8 k. _/ }; \$ v: w+ Qline 284,最後將結果存到r0所指到的pte。
2 K, J) z; D  E2 L8 ]
7 y! Y$ n. Y$ o; w/ \' v以上三個動作,就是做好 ram 起頭的位址一開始map,還沒做完整塊map。由於我們目前的設定方式每塊是1MB,所以這邊是將RAM開始的1MB做map。
# z# s: e* Z4 r1 S' R! T2 i; ^! J  O, c0 y
line 327,返回,結束一開始的create page table的動作,我們可以看出其實並沒有做完整的初始化page table,所以之後應該會有其他page table的細節。
  1.     279         add     r0, r4, #PAGE_OFFSET >> 18
    3 z0 l* B: ]& `+ P0 l
  2.     280         orr     r6, r7, #(PHYS_OFFSET & 0xff000000)
    8 x& I- E$ P0 a7 K  F2 Q! K" W
  3.     281         .if     (PHYS_OFFSET & 0x00f00000)+ U& z& Z0 L4 J  N" D' K/ }! Y
  4.     282         orr     r6, r6, #(PHYS_OFFSET & 0x00f00000)
    7 b+ ~& s8 W0 x" B! S( U( y
  5.     283         .endif; D& o- U5 g; t5 x* g" q7 B8 X' U
  6.     284         str     r6, [r0]
    & ?3 _  |; h* S' K3 M* p
  7.     327         mov     pc, lr
複製代碼
附帶一提,我們這邊省略了一些用ifdef包起來的程式碼,像是一開始會印一些output message的程式碼(line286~326)。
14#
 樓主| 發表於 2008-10-22 20:37:08 | 只看該作者
自create page table返回後,我們偷偷看一下接下來的程式碼,6 q: G  J5 Z" n$ S1 l! [
line 99, 將switch_data擺到r13
7 u; K0 C0 ~9 q. j- Y6 Cline 101, 將enable_mmu擺到lr
8 O; b+ W9 {1 B: ^line 102, 將pc改跳到r10+PROCINFO_INITFUNC的地方去
/ u" I5 [( S0 t3 N% I3 _
& n& g( a0 S0 n$ z其實這邊有點玄機,switch_data和enable_mmu都是function,結果位址都只是被存起來,並沒有直接跳過去執行。其實,雖然程式碼是順序switch_data->enable_mmu->proc init function,但其實執行的順序會是 procinfo 的init function-> enable_mmu -> switch_data 。至於,為什麼要這樣寫的原因?就不清楚了,也還沒仔細推敲過。
1 l. H- W' k; L. ^, F# I
% O" b* |4 H2 Aswitch_data最後就會跳到大家都很熟悉的start_kernel(). 詳細要賣個關子,最近會忙一下,得要過一陣子才能貼文∼  
  1.      99         ldr     r13, __switch_data              @ address to jump to after; P7 J+ N" C4 o" i# U; Z: @) k
  2.     100                                                 @ mmu has been enabled
    $ W& I3 P8 F& [4 f" C' h+ B2 t
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    " J& L4 K# }5 C  ~  q2 t
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
15#
 樓主| 發表於 2009-7-4 01:09:36 | 只看該作者
老店重新開張~; E; L( N+ Z9 b8 a4 E9 @
9 B& d6 U. z) v4 S& a$ P) T
花了一些時間把舊的貼文整理到一個blog
* I/ h4 s& u" j+ o& i有把一些敘述修改過; K& H& ~2 f5 N9 S3 M# [& Q/ ?
希望會比較容易集中閱讀
. b+ V) i" c. k& t( l: s! N3 y目前因為某些敘述不容易. O) W% \7 N! t- F* u2 r- v
還是比較偏向筆記式而且用字不夠精確  T2 s, d& _3 I& K" o8 `
希望之後能夠慢慢有系統地整理4 G% B( _! ~2 I; L* i1 X
大家有興趣的話
* B( \8 \+ P. Y可以來看看和討論 * ^7 _' Y3 z' M4 O3 @/ `2 z& F4 e
http://gogojesseco.blogspot.com/9 I# w4 I( r1 S5 F0 s8 E5 B
  ^4 A: j' V. d
以後可能會採取  先在chip123貼新文章7 |, I' y# k* P
慢慢整理到blog上的方式
8 r0 p% U' c. B9 N$ ?/ r因為chip123比較方便討論 =)
. T4 c8 P* F* _( K, D$ F7 |blog編輯修改起來比較方便
" U* }0 x3 J% Z% r閱讀也比較集中   大家可以在這邊看到討論
; C8 Q  M" h) ~; X然後在blog看到完整的文章 (類似BBS精華區的感覺)

評分

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

查看全部評分

16#
 樓主| 發表於 2009-7-15 17:07:03 | 只看該作者
隔了很長一段時間沒update
6 ?$ b7 l& o4 C8 h之前程式碼走到 ./arch/arm/kernel/head.S 的 line 99 附近
  1.      99         ldr     r13, __switch_data              @ address to jump to after
    2 I# \" n4 C; k3 w/ l4 m! v
  2.     100                                                 @ mmu has been enabled
    ( G/ r" F+ u1 j& \2 g
  3.     101         adr     lr, __enable_mmu                @ return (PIC) address
    0 ?7 t4 Z& |8 _
  4.     102         add     pc, r10, #PROCINFO_INITFUNC
複製代碼
line 99, 將__switch_data放到r13。(留作之後用)
3 \) ^# S+ \( ~+ A- Lline 101, 將__enable_mmu的addr放到lr。(留作之後用). {4 Z8 y2 I' m9 a" }
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
    3 L. a5 c' B. J  b  _+ P5 m
  2. 374 __arm926_setup:' c$ k8 s! I! M, ]0 `5 n* I- T
  3. 375         mov     r0, #0% B. s" A/ q4 \$ C7 u, h
  4. 376         mcr     p15, 0, r0, c7, c7              @ invalidate I,D caches on v4( B( g* e0 X! |- \
  5. 377         mcr     p15, 0, r0, c7, c10, 4          @ drain write buffer on v4- f  A* ]* j/ d0 T( S
  6. 378 #ifdef CONFIG_MMU
    8 v/ x' m; |/ S3 }# {
  7. 379         mcr     p15, 0, r0, c8, c7              @ invalidate I,D TLBs on v4
    2 Z- @2 j5 C# [
  8. 380 #endif) O( C. x0 s3 n; O3 Z' ?7 V. c4 Q
  9. 0 y9 r! n: D$ v+ Q- [0 ]6 J
  10. 388         adr     r5, arm926_crval4 T4 G2 x8 H8 _* E7 W: H1 _
  11. 389         ldmia   r5, {r5, r6}
    # ~; K, D5 z# Y2 G
  12. 390         mrc     p15, 0, r0, c1, c0              @ get control register v40 W8 l0 Z( }5 `$ i! K
  13. 391         bic     r0, r0, r54 O" n, p- k; A+ d1 E5 L* s
  14. 392         orr     r0, r0, r6
    8 Z# p; Y( I0 U" o& c) g4 u

  15.   q" i, v) U; }  m' b1 w
  16. 396         mov     pc, lr
    ; G+ w. E$ n) q) v  f
  17. 397         .size   __arm926_setup, . - __arm926_setup
複製代碼
這邊的程式碼就跟CPU有很大的相依性,
& C+ K/ `9 M4 Y, T) j& tline 375~380, 主要就是invalidate CPU的I&D cache和清空write buffer。
- F5 e, G& R8 `+ ^  rline 388~392, 把cp15的設定讀出來,並且將一些預設狀態做好運算放到r0。(預設值從arm926_crval這邊可以取得。)+ ^6 V7 v& V- y3 S2 E9 t% b" U
line 396, 直接把pc跳到lr,因為我們之前已經將lr = enable_mmu,所以直接跳過去。
17#
 樓主| 發表於 2009-7-15 17:29:45 | 只看該作者
  1. 155 __enable_mmu:( |* l2 R- {$ l: I% ^9 d% o' h) k* R. ^
  2. 170         mov     r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
    1 I- L* u% j- {- e' z
  3. 171                       domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
    9 L6 {3 M& f4 U. v
  4. 172                       domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
    ! G0 H- C4 r9 l! A8 A
  5. 173                       domain_val(DOMAIN_IO, DOMAIN_CLIENT))
    0 |) w2 Q* h8 E) Q+ b
  6. 174         mcr     p15, 0, r5, c3, c0, 0           @ load domain access register* R* a& D# {1 P5 p7 ?' B
  7. 175         mcr     p15, 0, r4, c2, c0, 0           @ load page table pointer! h+ y  f/ G# [7 q) t
  8. 176         b       __turn_mmu_on
    1 V8 z1 J& g7 [
  9. 177 ENDPROC(__enable_mmu)
複製代碼
line 170~174,設置好domain access。(domain access可以先當成設置access的權限,或許以後可以寫詳細的文章說明). T( d) W3 k- d0 {9 C5 e
line 175~176,將create好的page table開頭丟給mmu。跳到turn_mmu_on
  1. 191 __turn_mmu_on:; e4 D& J4 x! [8 N$ }
  2. 192         mov     r0, r06 f5 Z9 z4 ?8 O' M. d+ e
  3. 193         mcr     p15, 0, r0, c1, c0, 0           @ write control reg
    5 W2 _) F# `# `# h6 p0 p2 x! {$ p3 g
  4. 194         mrc     p15, 0, r3, c0, c0, 0           @ read id reg
    ! o3 M, n( Z* E+ m) V, p/ O- ^, s
  5. 195         mov     r3, r3" f  |! q% \8 t/ j  f! u
  6. 196         mov     r3, r3
    " t3 Q6 h, e9 m2 M( j
  7. 197         mov     pc, r13
    , e$ C" ?- n/ t% X7 s: i
  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:' m4 u+ [) @! Z7 [% ?6 }: z
  2. 19         .long   __mmap_switched$ C) ?- l" K  B. y! W
  3. 20         .long   __data_loc                      @ r44 _2 g: p3 o0 M( x( O4 D. Q
  4. 21         .long   _data                           @ r5. z! Q$ l. Z$ F$ i
  5. 22         .long   __bss_start                     @ r6
    / j( s6 D. l" d
  6. 23         .long   _end                            @ r7; O4 \) W, I7 B9 Y) D6 ]
  7. 24         .long   processor_id                    @ r4
    : H% U, w9 G$ j. i1 @+ M
  8. 25         .long   __machine_arch_type             @ r5
    $ T0 y/ X2 t( L& K' }
  9. 26         .long   __atags_pointer                 @ r65 Y; ?( i& e0 x- C5 l9 G& }* M8 ~
  10. 27         .long   cr_alignment                    @ r7
    , c. ]# e5 H) p9 Q
  11. 28         .long   init_thread_union + THREAD_START_SP @ sp( v4 g- [6 Q2 a& H+ i
  12. 299 P  C/ ^/ X9 H" f! R# H" \
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。
18#
 樓主| 發表於 2009-7-15 17:30:00 | 只看該作者
  1. 39 __mmap_switched:
    7 n& }2 f* Q/ T- R  I. Q; D/ W' S
  2. 40         adr     r3, __switch_data + 4/ s: e! K6 Z( W
  3. 41
    * v% `& B$ C  R+ f4 V
  4. 42         ldmia   r3!, {r4, r5, r6, r7}
    ' @& j2 T* v. b# h4 N, y% e
  5. 43         cmp     r4, r5                          @ Copy data segment if needed
    9 t* w: n! c' y$ Z# [# ^1 v
  6. 44 1:      cmpne   r5, r6
    & s- ?6 B, E  c+ F4 c, m
  7. 45         ldrne   fp, [r4], #4
    * K  W" \) d1 u
  8. 46         strne   fp, [r5], #4( e- i/ p" j; x' R! L
  9. 47         bne     1b
      }! J( r* L3 Z, Q. ~8 {/ e
  10. 48, p- K" v) w' u
  11. 49         mov     fp, #0                          @ Clear BSS (and zero fp)
    " p% v* O: I5 P* K
  12. 50 1:      cmp     r6, r7
    2 @$ V6 P& X, s) a: J
  13. 51         strcc   fp, [r6],#4
    7 ]9 L. \! ]* u& y+ B
  14. 52         bcc     1b& r( z1 c  O. j: T
  15. 53
    % l" ?' d/ f7 S% O" k0 V% r& {
  16. 54         ldmia   r3, {r4, r5, r6, r7, sp}% Z, Y: `: t) e) d, a2 a" ]
  17. 55         str     r9, [r4]                        @ Save processor ID6 a$ Y' v6 s! o8 f3 K
  18. 56         str     r1, [r5]                        @ Save machine type* K5 M. Q+ q1 N# P# b8 E
  19. 57         str     r2, [r6]                        @ Save atags pointer
    0 x& K& b* C* w6 l- j0 _0 [" Y2 ~
  20. 58         bic     r4, r0, #CR_A                   @ Clear 'A' bit
    $ C( g/ Q( L7 {# E2 \# ]
  21. 59         stmia   r7, {r0, r4}                    @ Save control register values% e; o' X+ m0 v
  22. 60         b       start_kernel/ |8 w$ c7 t. B1 X: e
  23. 61 ENDPROC(__mmap_switched)
複製代碼
switch_data的第一行就是 __mmap_switched,所以我們直接看line 39。& G9 V3 c& s1 g. B' Q" H
line 39,將__data_loc的addr放到r3
! p" u* h$ R4 b5 F# M6 a8 E9 iline 42,從r3的位址,連續讀取四筆資料到r4, r5, r6, r7- O1 q3 i  }' O6 ?
line 43~47,看看data segment是不是需要搬動。
% \( P: a7 p7 [2 U. y( @line 49~52, clear BSS。+ ^" D! X3 Y' h; a& b( c9 y( M
) l3 k0 c, n( M5 E" k6 i
由於linux kernel在進入start_kernel前有一些前提必須要滿足:8 k2 J1 b3 O; W) K
r0  = cp#15 control register% k9 B$ {  R0 X; `" H' h
r1  = machine ID
- Z2 V& B1 ~. U, h+ t* U1 Cr2  = atags pointer
  H2 O6 d" v6 e! U' O/ qr9  = processor ID% k  v% h7 Y' X6 f* X

% c" L* J; @+ U6 N0 N- M7 w所以line 54~59就是在做這些準備。5 h0 C' D) x' P8 E( R( W* Y
最後呢? 我們就跳到start_kernel了。(而且還是用b start_kernel,表示我們不會再回來了)
) V' }. O7 I% x% [8 c3 Q' H
' k6 c& l9 j$ @" [4 I' z" I" ~看一下start_kernel()在./init/main.c,終於跳出architecture specific的目錄,表示. T9 `3 l  X8 [+ E0 {
我們真正的開始linux kernel的初始化。
  \3 n$ j1 o# ]( d; w像是 shedule init, console init, memory init, irq init等等都在start_kernel裡頭。
% R  i6 O. L! j, I6 V( z到這邊之後,應該就可以深入linux kernel中,各項比較跟hardware不那麼相關的軟體部分。

評分

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

查看全部評分

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

本版積分規則

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

GMT+8, 2024-5-22 02:50 AM , Processed in 0.134017 second(s), 18 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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