Android 筆記-Linux Kernel SMP (Symmetric Multi-Processors) 開機流程解析 Part(2) Linux Kernel SMP zImage到st

来源:互联网 发布:刑侦书籍知乎 编辑:程序博客网 时间:2024/05/09 01:14

Android 筆記-Linux Kernel SMP  (Symmetric Multi-Processors) 開機流程解析 Part(2) Linux Kernel SMP  zImage到start_kernel流程.

hlchou@mail2000.com.tw

by loda.


Loda's Blog

App BizOrz

Android/Linux Source Code Tags
App BizOrz 
BizOrz.COM 
BizOrz Blog




Mmmmmm,必須承認,我把這篇文章寫的有點囉嗦,以前在Linux Kernel上的工作,沒有留下太多的筆記,抽象的概念,容易隨著下一個產品或是技術的開發,成為過往記憶的一部分,這次重新整理,希望以後回來看時,可以很快Pick-up所有的細節,所以在一些枝微末節上,會比較嘮叨.

也因此,如果你原本就對ARM與Linux Kernel原始碼有一定的基礎,可能讀起本文來會比較輕鬆些. 若是有些部分,筆者探究的太過細節,還請各位見諒.

Linux Kernel對於SMP的支援有三種組合,

1,不支援SMP的Linux Kernel

2,支援SMP的Linux Kernel (關閉SMP對於單核心UniProcessor的支援,SMP_ON_UP=n)

3,支援SMP/UP的Linux Kernel (開啟SMP對於單核心UniProcessor的支援,SMP_ON_UP=y),並在啟動時,如果偵測到為UniProcessor時,會自我修正不能在non-SMP運作的指令.

目前SMP_ON_UP選項只在ARM處理器上有.

其實,整個Linux Kernel中包括PageSet,Process ID Map與相關的資料結構,都會參考目前系統中的處理器個數,來做出對應的配置,也就是說Linux Kernel對於支援多核心的架構,已經是相當的內化(骨子裡...就是會考慮到多核心的情況.),並蘊含在許多核心模組的設計上. 也因此,在整理本文的過程中,收獲最大的也是筆者自己對於SMP架構與Linux Kernel模組的藍圖. 並希望對閱讀本文的人也能有所助益.

本文主要從zImage開始到start_kernel完畢(rest_init除外),並以Tegra平台為主要參考,由於並非所有函式都在筆者平台上被參考到,在說明中也會略過,只選擇在這平台上比較重要的部份.

由於筆者時間受限,本系列文章會分次刊登,還請見諒.

Linux Kernel Image

依據開發的需求,Linux Kernel Image可以編譯為 zImage (Compressed kernel image),Image (Uncompressed kernel image),xipImage(XIP(eXecution In Place) kernel image),uImage(U-Boot wrapped zImage)與 bootpImage( Combined zImage and initial RAM disk).

若對Linux Kernel編譯過程有興趣,可在編譯時加上 KBUILD_VERBOSE=1,讓quiet參數為空白,可把編譯過程吐到Console中,便於觀察.

不只是ARMv32,還支援Thumb2的 Linux Kernel Image

如果所選擇的處理器是ARMv7 (也就是Cortex的架構),可以透過勾選Experimental程式碼的選項,就可把Linux Kernel以Thumb2的方式進行編譯.

有關ARMv32與Thumb2效能的比較可以參考這篇在ARM工作的Richard Phelan所寫的文章Improving ARM Code Density and Performance (http://www.cs.uiuc.edu/class/fa05/cs433ug/PROCESSORS/Thumb2.pdf), 以C Code實作同樣的功能來說,編譯為Thumb2最高可以達到98%的ARM指令及效能,程式碼本身所需的記憶體空間只佔原本ARM程式碼的74%.  對記憶體受限的嵌入式裝置,以Thumb2 16/32 bits混合的程式碼可以得到較佳的 Performance/Code Size的C/P值.

在選擇Linux Kernel選單時,只要進行如下勾選即可,

General setup  --->Prompt for development and/or incomplete code/drivers

Kernel Features  --->Compile the kernel in Thumb-2 mode

目前筆者並未驗證過這部份的代碼,僅作為有興趣的開發者參考資訊.

Linux Kernel編譯時所產生的Relocatable Object File.

當一個編譯系統比較龐大時,如果是一次要Link大量的.o或.a檔時,要解決這些Symbol Resolve會需要的記憶體與運算成本,也會對應的提高,Linux Kernel有使用GCC  relocatable output的機制,讓個別模組可以先進行 Symbol Resolve,節省最後Kernel Image產生的運算資源. 簡要說明如下

1, 編譯過程中,會透過arm-eabi-ld (GCC Linker) 搭配 “-r” 產生”relocatable output”,例如:

arm-eabi-ld -EL    -r -o drivers/tty/vt/built-in.o drivers/tty/vt/vt_ioctl.o drivers/tty/vt/vc_screen.o drivers/tty/vt/selection.o drivers/tty/vt/keyboard.o drivers/tty/vt/consolemap.o drivers/tty/vt/consolemap_deftbl.o drivers/tty/vt/vt.o drivers/tty/vt/defkeymap.o

會把 drivers/tty/vt下的.o檔案,產生出一個在內部已經做過Symbol Resolved動作的集合Object檔案 built-in.o,透過objdump我們先檢視在目錄下的vt.o檔案中呼叫外部函式vt_ioctl,

arm-eabi-objdump -t vt.o|grep "vt_ioctl"

00000000         *UND*  00000000 vt_ioctl

由於該函式的實作不在vt.c中,因此在編譯後,.o檔案中的Symbol會被標示為 “Undefined”,再來檢視實作該函式的vt_ioctl.c產生的Object檔案,如下所示

arm-eabi-objdump -t vt_ioctl.o|grep "vt_ioctl"

vt_ioctl.o:     file format elf32-littlearm

00000000 l    df *ABS*  00000000 vt_ioctl.c

00000558 g     F .text  00001ccc vt_ioctl

可以看到該函式在vt_ioctl.c編譯後,是在text節區中,且屬性為 global,可供外部的.o檔案連結.

最後我們檢視drivers/tty/vt目錄下產生的built-in.o,

arm-eabi-objdump -t built-in.o|grep "vt_ioctl"

00000000 l    df *ABS*  00000000 vt_ioctl.c

00000558 g     F .text  00001ccc vt_ioctl

可以看到,最後產生的集合檔案built-in.o,包含了vt.o與vt_ioctl.o,且在其中這些.o之間的Symbol交互參考的問題,都已經在編譯階段被解決.

想像一下,如果一次有5000個Object檔案或是.a檔案(.a檔案,等於是Object檔案的Archive,可以分辨.o檔案的集合性,但其中所包含的.o並沒有彼此先進行Symbol Resolved,因此,所花的時間成本跟.o是一樣的.),要去做Symbol Resolved,這要建立的對應表格複雜度,跟我先把這5000檔案所在的20個目錄,針對這20個目錄先把其中包含的Object檔案做內部的Symbol Resolved,減少要解決的Symbol個數與要建立的查表範圍,就可以顯著的加速最後要連結成Image的運算時間與記憶體成本.

參考平台Tegra2的記憶體配置

筆者以Linux Kernel 2.6.39並選擇ARM Tegra2的平台為例 (NVIDIA Tegra (ARCH_TEGRA)),關於這處理器的基本資訊為

1,兩個Cortex A9處理器

2,一個Audio/Video ARM7處理器

3,實體記憶體SDRAM定址在 0x00000000  ( AP20_BASE_PA_SDRAM)

4,OnChip 256KB SRAM定址在0x40000000 (AP20_BASE_PA_SRAM)

5,NOR Flash的定址在0xD0000000 ( AP20_BASE_PA_NOR_FLASH)

有關NVidia Tegra2的資訊可以參考http://developer.nvidia.com/tegra/taxonomy/term/36/0

有關ALT_UP對SMP到UP程式碼的修正

由於Linux Kernel SMP的實作,在ARM的架構下會有SMP與單核心共用函式實作程式碼的差異,在檔案 arch/arm/include/asm/assembler.h中,有實現ALT_SMP與ALT_UP兩個巨集,例如在程式碼中使用ALT_UP,該指令就會被加入Section .alt.smp.init 如下所示.

#define ALT_UP(instr...)                                        \

.pushsection ".alt.smp.init", "a"                       ;\

.long   9998b                                           ;\

9997:   instr                                                   ;\

.if . - 9997b != 4                                      ;\

.error "ALT_UP() content must assemble to exactly 4 bytes";\

.endif                                                  ;\

.popsection

藉此我們可以在同樣的函式中,根據單核心與SMP實作的差異,透過ALT_SMP與ALT_UP來把兩種版本的程式碼置入,以開啟SMP與SMP_ON_UP的實作來說,屬於SMP的實作,會被編譯在原本執行函式的內容中,而屬於單核心版本的實作,則會被編譯到Section .alt.smp.init下,參考如下程式碼的例子

在檔案arch/arm/mm/proc-v7.S中,

ALT_SMP(orr     r0, r0, #TTB_FLAGS_SMP)

ALT_UP(orr      r0, r0, #TTB_FLAGS_UP)

…..

cpu_resume_l1_flags:

ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_FLAGS_SMP)

ALT_UP(.long  PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_FLAGS_UP)

或檔案arch/arm/mm/tlb-v7.S中,

ALT_SMP(mcr     p15, 0, r0, c8, c3, 1)  @ TLB invalidate U MVA (shareable)

ALT_UP(mcr      p15, 0, r0, c8, c7, 1)  @ TLB invalidate U MVA

我們可以看到依據SMP與單核心版本的差異,實作上會在同一處程式碼中同時實現兩種版本的程式碼,並透過ALT_UP把單核心的版本在編譯階段放到Section .alt.smp.init中,並且會在每4bytes程式碼位址後,記錄對應4bytes單核心版本指令集,以便修正時參考,如下例子

0xc001858c <__smpalt_begin>:

…..

0xc0018634: c0029fd8     .word 0xc0029fd8 //4 bytes 要取代的目標記憶體位址

0xc0018638: ee080f37     mcr    15, 0, r0, cr8, cr7, {1} //4bytes UniProcessor版本指令

0xc001863c: c0029fec     .word 0xc0029fec

0xc0018640: ee07cfd5     mcr    15, 0, ip, cr7, cr5, {6}

0xc0018644: c002a00c    .word 0xc002a00c

0xc0018648: ee080f37     mcr    15, 0, r0, cr8, cr7, {1}

…..

在最後的Link階段,會把Section .alt.smp.init放在Symbol __smpalt_begin與__smpalt_end之中,因此在程式碼執行階段,就可以透過這兩個Symbol取得 Section .alt.smp.init中所包含單核心程式碼的內容與記憶體範圍.

在Linux Kernel啟動後會呼叫函式__fixup_smp,如果判斷目前是在單核心平台上,就會把在__smpalt_begin到__smpalt_end記憶體範圍的單核心程式碼依據其對應的記憶體位址,進行修正動作.

運作概念如下圖所示



從zImage開始,啟動Linux Kernel

接下來,以Linux Kernel zImage為例,簡要說明執行流程,也借此對產生的Linux Kernel Image有一個概念,有關SMP的部份,會在流程走到時,著重說明

編譯完成後,在根目錄下的vmlinux會透過如下的命令產生出來,其中有關記憶體位置與節區的配置參考檔案為 arch/arm/kernel/vmlinux.lds

arm-eabi-ld -EL  -p --no-undefined -X --build-id -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o  init/built-in.o --start-group  usr/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o  arch/arm/common/built-in.o  arch/arm/mach-tegra/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  firmware/built-in.o  net/built-in.o --end-group .tmp_kallsyms2.o

(關於 .tmp_vmlinux1 與 .tmp_vmlinux2的產生,在此先略過)

之後,執行如下命令把ELF格式的vmlinux轉為 Binary 格式的Image

arm-eabi-objcopy -O binary -R .comment -S  vmlinux arch/arm/boot/Image

並執行如下命令把 Linux Kernel Binary Image轉為壓縮檔案

cat arch/arm/boot/compressed/../Image | gzip -f -9 > arch/arm/boot/compressed/piggy.gzip

參考arch/arm/boot/compressed/piggy.gzip.S原始碼

.section .piggydata,#alloc

.globl  input_data

input_data:

.incbin "arch/arm/boot/compressed/piggy.gzip"

.globl  input_data_end

input_data_end:

可以知道在編譯arch/arm/boot/compressed/piggy.gzip.S產生arch/arm/boot/compressed/piggy.gzip.o時,就會把壓縮後的Linux Kernel Image " arch/arm/boot/compressed/piggy.gzip",一併產生在piggy.gzip.o中的Symbol input_data與input_data_end之間.

然後,把壓縮檔跟解壓縮的部份,連結產生 compressed目錄下的vmlinux (記憶體起點為0x00000000,也就是對應到Tegra2外部記憶體的起點),執行的Link指令如下所示

arm-eabi-ld -EL    --defsym _image_size=1602596 --defsym zreladdr=0x00008000 -p --no-undefined -X -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.gzip.o arch/arm/boot/compressed/misc.o arch/arm/boot/compressed/decompress.o arch/arm/boot/compressed/lib1funcs.o -o arch/arm/boot/compressed/vmlinux

然後,執行如下命令把帶有壓縮後的vmlinux與解壓縮程式的ELF格式vmlinux轉為 Binary 格式的zImage

arm-eabi-objcopy -O binary -R .comment -S  arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage

如此,就完成Linux Kernel Image的產生.

其中有關zImage執行的實體記憶體位址可以透過CONFIG_ZBOOT_ROM_TEXT與CONFIG_ZBOOT_ROM_BSS設定.

而Linux Kernel解壓縮的位址會在 CONFIG_ZBOOT_ROM_TEXT + 16kbytes的位址,以這例子來說就是 0x00008000. 這是在最後產生arch/arm/boot/compressd/vmlinux時,透過 “--defsym zreladdr=0x00008000” 產生zreladdr Syombol傳遞給zImage.

可以參考 boot/compressed/Makefile中

LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)

而 ZRELADDR是在arch/arm/boot/Makefile 中設定的

ZRELADDR    := $(zreladdr-y)

其中,zreladdr-y會是在每個Machine對應的目錄下的Makefile.boot被定義,例如Tegra2是在檔案 arch/arm/mach-tegra/Makefile.boot中,以如下方式定義zreladdr-y

zreladdr-$(CONFIG_ARCH_TEGRA_2x_SOC)   := 0x00008000

同時,對解壓縮的Kernel Image執行的虛擬與實體記憶體對應,必須滿足如下條件

ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)

因為如此,解壓縮的Linux Kernel在虛擬記憶體中的位址就必須是0xc0008000 對應到實體記憶體中的位址會是 0x00008000. 如果Kernel Space的虛擬記憶體空間有調整的話(例如從 0xc0000000調整為0x80000000,就會變成 0x80008000 ↔ 0x00008000).

Linux Kernel Image在虛擬記憶體的運作位置可以透過 xx訂定,一般而言都是給User-Space 3GB的範圍,Kernel Space為1GB的範圍.

CONFIG_PAGE_OFFSET=0xC0000000

要進一步探討Linux Kernel啟動流程,我們可以透arch/arm/boot/compressed/vmlinux.lds了解zImage的啟動流程,

1,節區.text產生的Binary Symbol有

a,<lext.1135> ("static const unsigned short lext" in lib/zlib_inflate/inftrees.c)

b,<lbase.1134> ("static const unsigned short lbase"  in lib/zlib_inflate/inftrees.c)

c,<dext.1137>  ("static const unsigned short dext" in lib/zlib_inflate/inftrees.c)

d,<dbase.1136> ("static const unsigned short dbase" in lib/zlib_inflate/inftrees.c)

e,<lenfix.1621> ("static const code lenfix" in lib/zlib_inflate/inffixed.h)

f,<distfix.1622> ("static const code distfix" in lib/zlib_inflate/inffixed.h)

....etc

2,會把壓縮後的Linux Kernel piggy.gzip 放在.text節區中Symbol <input_data> 到 <input_data_end>之間,以筆者驗證的環境來說,大約介於實體記憶體 0x00003fde-0x000bdcbe之間. (約761kbytes)=>要看總共編譯多少核心模組與程式碼範圍.

如下圖所示



3,編譯後的zImage,在會檔頭  offset: 0x00000024 bytes位置存放32-bits 的 “0x016f2818” 作為 BootLoader載入zImage時,確認zImage檔案正確與否的Magic Number. 緊接著offset: 0x00000028位置存放32-bits  zImage在實體記憶體的起始位址 (以筆者編譯Tegra2 Seaboard環境而言該值為0x00000000.) 再來的offset: 0x0000002c位置存放32-bits zImage在實體記憶體的結束位置 (以筆者編譯Tegra2 Seaboard環境而言該值為0x000bdcf4). 透過上述的三個值,就可以讓BootLoader驗證Linux Kernel Magic Number,並且知道要把該Kernel Image擺放到哪一段記憶體位址執行與大小.

4,啟動後,會執行arch/arm/boot/compressed/head.S 中的函式start ,可以參考UBoot中呼叫Linux Kernel Entry 的函式原型為

void    (*kernel_entry)(int zero, int arch, uint params);

也就是說, UBoot/BootLoader 呼叫zImage時,第一個參數固定為0(r0),第二個參數為處理器平台ID(r1),第三個參數為Linux Kernel啟動的Boot Argument(r2)

00000000 <start>:

0x00000000: nop                      (mov r0,r0)

0x00000004: nop                       (mov r0,r0)

0x00000008: nop                       (mov r0,r0)

0x0000000c: nop                       (mov r0,r0)

0x00000010: nop                       (mov r0,r0)

0x00000014: nop                       (mov r0,r0)

0x00000018: nop                       (mov r0,r0)

0x0000001c: nop                       (mov r0,r0)

0x00000020: b       30 <_text+0x30>

0x00000024: .word          0x016f2818

0x00000028: .word          0x00000000

0x0000002c: .word          0x000bdcf4

0x00000030: mov r7, r1

0x00000034: mov r8, r2

0x00000038: mrs   r2, CPSR

0x0000003c: tst    r2, #3 ; 0x3 (確認是否為Supervisor Mode,User Mode=b10000(0x0010) and Supervisor Mode=b10011(0x0013)).

0x00000040: bne  4c <not_angel>

0x00000044: mov r0, #23          ; 0x17 (如果在SVC Mode就執行這部份的程式碼).

0x00000048: svc   0x00123456 (ARM semihosting 會傳遞參數0x17給JTAG除錯器,並讓ARM Core停止執行,可用於ICE除錯啟動流程)

如果zImage在編譯時是載入到記憶體0x00000000的位址,則zImage的進入點為 0x00000030.  (從0x00000000開始執行,也會執行連續的 mov r0,r0 直到0x00000020再Branch到正式的函式入口.).

5,之後呼叫函式__armv7_mmu_cache_on,並透過函式setup_mmu以1MB  Section方式配置TLB (MMU虛擬記憶體機制在這並沒有作用,所設定的TLB虛擬記憶體是直接對應到4GB的範圍,所以TLB虛擬位址0xc0000000就會直接對應到實體記憶體的0xc0000000.)

000002bc <__armv7_mmu_cache_on>:

0x000002bc: mov  ip, lr

0x000002c0: mrc    15, 0, fp, cr0, cr1, {4}   //read ID_MMFR0

0x000002c4: tst      fp, #15         ; 0xf    //VMSA

0x000002c8: blne   21c <__setup_mmu>

0x000002cc: mov   r0, #0 ; 0x0

0x000002d0: mcr    15, 0, r0, cr7, cr10, {4}  //drain write buffer

0x000002d4: tst     fp, #15         ; 0xf    //VMSA

0x000002d8: mcrne          15, 0, r0, cr8, cr7, {0}    //flush I,D TLBs

0x000002dc: mrc    15, 0, r0, cr1, cr0, {0}    //read control reg

0x000002e0: orr     r0, r0, #20480         ; 0x5000    //I-cache enable, RR cache replacement

0x000002e4: orr     r0, r0, #60    ; 0x3c     //write buffer

0x000002e8: orrne r0, r0, #1      ; 0x1      //MMU enabled

0x000002ec: mvnne         r1, #0 ; 0x0

0x000002f0: mcrne           15, 0, r3, cr2, cr0, {0}    //load page table pointer //r3 from setup_mmu = page table entry.

0x000002f4: mcrne           15, 0, r1, cr3, cr0, {0}    //load domain access control

0x000002f8: mcr     15, 0, r0, cr1, cr0, {0}    //load control register

0x000002fc: mrc     15, 0, r0, cr1, cr0, {0}    //and read it back

0x00000300: mov   r0, #0 ; 0x0

0x00000304: mcr    15, 0, r0, cr7, cr5, {4}    //ISB

0x00000308: mov   pc, ip

0000021c <__setup_mmu>:

0x0000021c: sub    r3, r4, #16384         ; 0x4000    //Page directory size //(r4=Linux Kernel 解壓縮後記憶體位址 =0x00008000, r3=0x00004000)

0x00000220: bic     r3, r3, #255  ; 0xff    //Align the pointer

0x00000224: bic     r3, r3, #16128         ; 0x3f00

/* Initialise the page tables, turning on the cacheable and bufferable

* bits for the RAM area only. */

0x00000228: mov   r0, r3

0x0000022c: lsr      r9, r0, #18

0x00000230: lsl      r9, r9, #18    //start of RAM

0x00000234: add    sl, r9, #268435456 ; 0x10000000   // a reasonable RAM size

0x00000238: mov   r1, #18          ; 0x12

0x0000023c: orr     r1, r1, #3072           ; 0xc00

0x00000240: add    r2, r3, #16384         ; 0x4000

0x00000244: cmp   r1, r9    //if virt > start of RAM

0x00000248: orrcs r1, r1, #12    ; 0xc    //set cacheable, bufferable

0x0000024c: cmp   r1, sl    //if virt > end of RAM

0x00000250: biccs r1, r1, #12    ; 0xc    //clear cacheable, bufferable

0x00000254: str      r1, [r0], #4    //1:1 mapping

0x00000258: add    r1, r1, #1048576     ; 0x100000 //Use Section Size=1MB//Page Table從0x4000-0x8000,每個Entry值為4 bytes,所以會有0x1000筆Entry,也就是說0x1000 * 1MB=4GB Range=32bits處理器訂址的上限

0x0000025c: teq     r0, r2

0x00000260: bne    244 <__setup_mmu+0x28>

/* If ever we are running from Flash, then we surely want the cache

* to be enabled also for our execution instance...  We map 2MB of it

* so there is no map overlap problem for up to 1 MB compressed kernel.

* If the execution is in RAM then we would only be duplicating the above.  */

0x00000264: mov   r1, #30          ; 0x1e

0x00000268: orr     r1, r1, #3072           ; 0xc00

0x0000026c: mov   r2, pc

0x00000270: lsr      r2, r2, #20

0x00000274: orr     r1, r1, r2, lsl #20

0x00000278: add    r0, r3, r2, lsl #2

0x0000027c: str      r1, [r0], #4

0x00000280: add    r1, r1, #1048576     ; 0x100000

0x00000284: str      r1, [r0]

0x00000288: mov   pc, lr

有關TLB 1MB Section Settings如下示意圖



以配置在0x00004000-0x00008000之間TLB 1MB Section的虛擬記憶體設定來說,Linux Kernel Base Address 0xc0008000,透過TLB查尋0xc0000000對應到的實體記憶體位址,首先取虛擬記憶體bits 31-20 值0xc00 作為Table Index,得到的Translation Table Base Address為 0x00004000 + 0xc00*4 = 0x00007000,該記憶體位址的1st Level Descriptor內容為0xc0000c12,屬性欄位為  1100 0001 0010 也就是AP[2:0]=011(Privileged/User permissions 都是可讀可寫的Full Access Right.),XN=1,

用虛擬記憶體0xc0008000轉換為實體記憶體時,1st Level Descriptor的bit 31-20 為0xc0000000+Offset 0x00008000=實體記憶體0xc0008000.(這是只有經過zImage前面setup_mmu設定的結果,並非正式Linux Kernel MMU TLB的配置結果喔!!!)

雖然setup_mmu已經對MMU TLB進行配置,但還不是最後的運作狀態,會在後續執行中設定完成.

6, 之後透過ARM r4暫存器 (r4  = kernel execution address )儲存Linux Kernel所要解壓縮的目標記憶體位置,如下程式碼

#ifdef CONFIG_AUTO_ZRELADDR

@ determine final kernel image address

mov     r4, pc

and     r4, r4, #0xf8000000

add     r4, r4, #TEXT_OFFSET

#else

ldr     r4, =zreladdr

#endif

bl      cache_on

呼叫函式decompress_kernel,解壓縮Linux到目標位址

/*

* The C runtime environment should now be setup sufficiently.

* Set up some pointers, and start decompressing.

*   r4  = kernel execution address

*   r7  = architecture ID

*   r8  = atags pointer

*/

mov     r0, r4

mov     r1, sp                  @ malloc space above stack

add     r2, sp, #0x10000        @ 64k max

mov     r3, r7

bl      decompress_kernel

bl      cache_clean_flush

bl      cache_off

mov     r0, #0                  @ must be zero

mov     r1, r7                  @ restore architecture number

mov     r2, r8                  @ restore atags pointer

mov     pc, r4                  @ call kernel

函式decompress_kernel  第一個參數為Linux Kernel要解壓縮的目標記憶體位置(from r4),第二個參數為目前的Stack Point,第三個參數為目前Stack加上64kbytes,第四個參數為目前的處理器平台識別碼,之後Flush Cache並關閉Cache.

最後,從Linux Kernel解壓縮後的記憶體位置的起點(也就是r4的記憶體位置)開始執行Linux Kernel (第一個參數為0, 第二個參數為目前的處理器平台識別碼,第三個參數為BootLoader要傳遞給Linux Kernel的Boot Argument.)

整個ARM從Boot Rom到UBoot BootLoader ,到 zImage解壓縮,到啟動解壓縮後Linux Kernel的流程,簡要描述如下圖所示




結束了zImage到解壓縮後的vmlinux Kernel Image流程,接下來我們以開啟SMP選項的Linux Kernel Image為例,進一步說明啟動的流程.

繼續vmlinux Linux Kernel Image執行流程

最原始的ELF格式Linux Kernel在編譯後所在的位置為/vmlinux,透過objcopy產生的Binary檔案為 arch/arm/boot/Image.

根目錄的vmlinux執行時,會從arch/arm/kernel/head.S中的函式stext開始,stext函式會帶入三個函式參數,第一個參數固定為0,第二個參數為architecture number,第三個參數為要帶給Kernel參數在記憶體中的位址.

如下為反組譯vmlinux執行時最前端的程式碼,

0x00008000 <stext>:

0x00008000:           msr    CPSR_c, #211       ; 0xd3 //ensure svc mode and irqs disabled

0x00008004:           mrc    15, 0, r9, cr0, cr0, {0} //get processor id

0x00008008:           bl       0x00156878 <__lookup_processor_type> //r5=procinfo r9=cpuid

0x0000800c:           movs sl, r5 //invalid processor (r5=0)?

0x00008010:           beq    c01568bc <__error> //yes, error 'p'

0x00008014:           add    r3, pc, #40   ; 0x28

0x00008018:           ldm    r3, {r4, r8}

0x0000801c:           sub    r4, r3, r4   //(PHYS_OFFSET - PAGE_OFFSET)

0x00008020:           add    r8, r8, r4 //PHYS_OFFSET

/*r1 = machine no, r2 = atags,

* r8 = phys_offset, r9 = cpuid, r10 = procinfo     */

0x00008024:           bl       0x0000810c <__vet_atags>

0x00008028:           bl       0x00008144 <__fixup_smp>//用以修正SMP Linux Kernel在單核心處理器運作的機制

0x0000802c:           bl       0x0000804c <__create_page_tables> //設定BootStrap處理器的MMU TLB分頁表

0x00008030:           ldr      sp, [pc, #8]  ; 0x00008040 <stext+0x40> //address to jump to after mmu has been enabled

0x00008034:           add    lr, pc, #0       ; 0x0  //return (PIC) address

0x00008038:           add    pc, sl, #16    ; 0x10 //Go to function __v7_proc_info (0x0019abf8+0x10 = 0x0019ac08)

0x0000803c:           b        0x0015684c <__enable_mmu>

0x00008040:           .word 0xc00081a0

0x00008044:           .word 0xc0008044

0x00008048:           .word 0xc0000000

在前面的內容有提到,透過setup_mmu所配置的MMU TLB並不是一個真正適應於Linux Kernel最終在高位址執行設定的虛擬記憶體配置,而在這階段透過函式__create_page_tables,就會把MMU TLB從下圖左側原本的配置方式,改為如右側適應於Linux Kernel配置在高位址,並且針對不存在的記憶體分頁,設定為0.



如果我們所編譯的Linux Kernel為支援SMP的版本,並且有開啟編譯選項SMP_ON_UP,在函式 __fixup_smp中 (fixup = Fix UniProcessor),會進行確認是否為SMP的核心運作在單核心處理器上,並執行必要的修正.

在ARMv7 Cortex處理器下,會進入函式__v7_proc_info + 0x10 也就是函式__v7_setup中

0x0019abf8 <__v7_proc_info>:  (in arch/arm/mm/proc-v7.S)

0x0019abf8:          .word 0x000f0000

0x0019abfc:          .word 0x000f0000

0x0019ac00:          .word 0x00011c0e

0x0019ac04:          .word 0x00000c12

0x0019ac08:          b        0x001570bc <__v7_setup>

在函式__v7_setup中會初始化TLB,Caches與MMU. 並透過CP15取得Main ID Register, 簡要說明如下

透過"MRC p15, 0, <Rd>, c0, c0, 0" 讀取處理器Main ID Register

Cortex A8 單核心 Main

Cortex A9 單核心 Main

Cortex A9 Dual-Core Main ID=  0x412FC090 (for r2p0) ,0x412FC091 (for r2p1) or  0x412FC092 (for r2p2)

Main ID Register格式如下所示

313029282726252423222120191817161514131211109876543210ImplementorVariantArchitecturePrimary Part NumberRevision欄位說明ImplementorARM處理器為0x41Variant

可用以表示Major Revision

Indicates the variant number, or major revision, of the processor:

0x3.

Architecture

Indicates that the architecture is given in the feature registers:

0xF.

Primary Part Number

Indicates the part number, Cortex-A8:

0xC08.

Revision

Indicates the revision number, or minor revision, of the processor:

0x2.

函式__v7_setup運作如下所示

0x001570bc <__v7_setup>: (in arch/arm/mm/proc-v7.S)

0x001570bc:                    add    ip, pc, #140 ; 0x8c //the local stack

0x001570c0:          stm    ip, {r0, r1, r2, r3, r4, r5, r7, r9, fp, lr}

0x001570c4:          bl       0x000297ec <v7_flush_dcache_all>

0x001570c8:          ldm    ip, {r0, r1, r2, r3, r4, r5, r7, r9, fp, lr}

0x001570cc:          mrc    15, 0, r0, cr0, cr0, {0} //read main ID register

0x001570d0:                    and    sl, r0, #-16777216  ; 0xff000000 //ARM?

0x001570d4:                    teq     sl, #1090519040     ; 0x41000000

0x001570d8:                    bne    0x00157108 <__v7_setup+0x4c>

0x001570dc:                    and    r5, r0, #15728640   ; 0xf00000 //variant

0x001570e0:          and    r6, r0, #15    ; 0xf //revision

0x001570e4:          orr      r6, r6, r5, lsr #16 //combine variant and revision

0x001570e8:          ubfx   r0, r0, #4, #12 //primary part number

0x001570ec:          ldr      sl, [pc, #136]          ; 0x0015717c <__v7_setup_stack+0x2c> //Cortex-A8 primary part number

0x001570f0:          teq     r0, sl

0x001570f4:          bne    0x001570fc <__v7_setup+0x40>

0x001570f8:          b        0x00157108 <__v7_setup+0x4c>

0x001570fc:          ldr      sl, [pc, #124]          ; 0x00157180 <__v7_setup_stack+0x30>//Cortex-A9 primary part number

0x00157100:          teq     r0, sl

0x00157104:          bne    0x00157108 <__v7_setup+0x4c>

0x00157108:          mov   sl, #0 ; 0x0

0x0015710c:          dsb    sy

0x00157110:          mcr    15, 0, sl, cr8, cr7, {0} //invalidate I + D TLBs

0x00157114:          mcr    15, 0, sl, cr2, cr0, {2}  //TTB control register

0x00157118:          orr      r4, r4, #106  ; 0x6a

0x0015711c:          mcr    15, 0, r4, cr2, cr0, {1}  //load TTB1

0x00157120:          ldr      r5, [pc, #92] ; 0x00157184 <__v7_setup_stack+0x34> //PRRR

0x00157124:          ldr      r6, [pc, #92] ; 0x00157188 <__v7_setup_stack+0x38> //NMRR

0x00157128:          mcr    15, 0, r5, cr10, cr2, {0} //write PRRR

0x0015712c:          mcr    15, 0, r6, cr10, cr2, {1} //write NMRR

0x00157130:          add    r5, pc, #16   ; 0x10

0x00157134:          ldm    r5, {r5, r6}

0x00157138:          mrc    15, 0, r0, cr1, cr0, {0} //read control register

0x0015713c:          bic     r0, r0, r5 //clear bits them

0x00157140:          orr      r0, r0, r6 //set them

0x00157144:          mov   pc, lr //pc=0x0000803c,之後進入 函式 __enable_mmu

回到stext函式

0x0000803c:          b        0x0015684c <__enable_mmu>

進入函式 __enable_mmu

0x0015684c <__enable_mmu>:

0x0015684c:          orr      r0, r0, #2      ; 0x2

0x00156850:          mov   r5, #21          ; 0x15

0x00156854:          mcr    15, 0, r5, cr3, cr0, {0}

0x00156858:          mcr    15, 0, r4, cr2, cr0, {0}

0x0015685c:          b        0x00156860 <__turn_mmu_on>

進入函式 __turn_mmu_on

0x00156860 <__turn_mmu_on>:

0x00156860:          nop                         (mov r0,r0)

0x00156864:          mcr    15, 0, r0, cr1, cr0, {0}

0x00156868:          mrc    15, 0, r3, cr0, cr0, {0}

0x0015686c:          mov   r3, r3

0x00156870:          mov   r3, sp

0x00156874:          mov   pc, r3 //會呼叫0xc00081a0 //到這就是第一次執行到虛擬記憶體的位址 0xc00081a0

進入函式__mmap_switched

0xc00081a0 <__mmap_switched>:

0xc00081a0:           add    r3, pc, #64   ; 0x40

0xc00081a4:           ldm    r3!, {r4, r5, r6, r7}

0xc00081a8:           cmp   r4, r5  //Copy data segment if needed

0xc00081ac:           cmpne          r5, r6

0xc00081b0:                      ldrne  fp, [r4], #4

0xc00081b4:                      strne  fp, [r5], #4

0xc00081b8:                      bne    c00081ac <__mmap_switched+0xc>

0xc00081bc:                      mov   fp, #0 ; 0x0 //Clear BSS (and zero fp)

0xc00081c0:           cmp   r6, r7

0xc00081c4:           strcc  fp, [r6], #4

0xc00081c8:           bcc    c00081c0 <__mmap_switched+0x20>

0xc00081cc:           ldm    r3, {r4, r5, r6, r7, sp}

0xc00081d0:                      str      r9, [r4] //Save processor ID

0xc00081d4:                      str      r1, [r5]  //Save machine type

0xc00081d8:                      str      r2, [r6]  //Save atags pointer

0xc00081dc:                      bic     r4, r0, #2      ; 0x2 //Clear 'A' bit

0xc00081e0:           stm    r7, {r0, r4} //Save control register values 
0xc00081e4:           b        0xc00088ac <start_kernel> //正式進入Linux Kernel Starting.

函式start_kernel實作在 init/main.c中,並且start_kernel啟動主要Linux核心的初始化與包括Kernel SMP機制的初始化, 有關從Linux Kernel stext到start_kernel 執行的流程示意圖,如下所示



start_kernel的流程

在init/main.c中的start_kernel函式為整個Linux核心啟動的主體,接下來我們針對流程簡要說明,在有關SMP部分,會特別強調它的運作行為.

其實探究每一個步驟的細節,是做技術的人最快樂的事情,但如果每個步驟都挖到最深入,就真的像是老太婆的裹腳布一樣又臭又長(雖然本文已經快要又臭又長了...),所以會選擇筆者自己覺得值得深入的動作加以說明,如果我覺得那不必要成為文章中的一部分,就會主動跳過,不另外提及. 有興趣的讀者,也可自行參閱Linux Kernel原碼.

整理後的內容如下所示

start_kernel
初始化函式的流程說明smp_setup_processor_idDo nothing in ARM.此時中斷是關閉的,直到下述必要的程序走完,才會重新開啟中斷tick_init

In kernel/time/tick-common.c

呼叫clockevents_register_notifier註冊 Clock Event.

boot_cpu_init

會初始化第一個處理器,

include/linux/smp.h:# define smp_processor_id() raw_smp_processor_id()

define smp_processor_id() raw_smp_processor_id()

在沒有設定SMP的Kernel中raw_smp_processor_id定義為0,所以處理器ID會固定為0.

而在有設定SMP的Kernel中,smp_processor_id (=raw_smp_processor_id)會傳回目前正在執行該程序的處理器ID.

arch/arm/include/asm/smp.h:#define raw_smp_processor_id() (current_thread_info()->cpu)

呼叫函式set_cpu_online,set_cpu_active,set_cpu_present與set_cpu_possible,

讓目前使用的處理器設定為Online,Active,Present與Prosible,在SMP的架構下,通常會用第一個處理器(CPU)作為初始化的處理器.

顯示字串 "Linux version 2.6.39 (root@localhost.localdomain) (gcc version 4.4.0(GCC) ) #11 SMP Thu Jul xx aa:bb:cc EDT 2011"setup_arch

函式實作在arch/arm/kernel/setup.c,

1,呼叫unwind_init,初始化基於ARM EABI的Backtrace Unwind機制 (in arch/arm/kernel/unwind.c). 處理BootLoader傳遞給Linux Kernel(in __atags_pointer)的參數.  設定code(_text,_etext),data(_sdata,_end),System Ram,Video Ram,I/O記憶體資源

2,呼叫is_smp (in arch/arm/include/asm/smp_plat.h)判斷目前是否運作在SMP Kernel下,總共可以有三種組合

a,沒有設定CONFIG_SMP,會直接返回 false.

b,有設定CONFIG_SMP_ON_UP,表示可以支援SMP Kernel運作在UniProcessor(單核心)的處理器上,這時會以smp_on_up的值判斷SMP Kernel是否有運作在單核心處理器上.若是則會返回false.

c,反之則返回 true,表明目前是在SMP Kernel運作下

3,承上,如果確認是在SMP下,會呼叫smp_init_cpus (in mach-tegra/platsmp.c), 並透過函式scu_get_core_count(in arch/arm/kernel/smp_scu.c)取得處理器個數, 與透過cpu_set(in include/linux/cpumask.h)設定Cpumasks (為一個bitmap,每個bit代表系統中對應存在的一個處理器), 會透過NR_CPUS決定Cpumasks bitmap的大小,以ARM平台為例,代表SMP支援處理器個數的NR_CPUS範圍可以為2到32.

4,預留記憶體給Linux Kernel崩潰時使用,配置IRQ/Abort/Undefined Instruction Mode的Stack,並回到SVC Mode.(此時IRQ/FIQ中斷都還是維持關閉的狀態)

5,呼叫tcm_init,根據arch/arm/kernel/vmlinux.lds.S中Linker Section的配置,  從外部記憶體__dtcm_start,把DTCM資料搬到__sdtcm_data到__edtcm_data中,  從外部記憶體__itcm_start,把ITCM程式碼搬到__sitcm_text到__eitcm_text中.

6,呼叫early_trap_init (in arch/arm/kernel/traps.c),

根據CONFIG_VECTORS_BASE (在ARM上通常為0xffff0000,可以避免設定Vector在0x00000000時,變成無法對0x00000000設定不可讀/不可寫與不可執行的動作,用以抓出系統可能對0位址的讀寫或是Function Null Pointer的錯誤偵測=>這招在產品開發上很好用!!).

7,參考arch/arm/kernel/entry-armv.S.會在0xffff0000配置Vector Table(複製來源為__vectors_start到__vectors_end).在0xffff0200之後配置Vector Stubs(複製來源為__stubs_start到__stubs_end),在中斷觸發後Branch這,之後Branch到對應CPU Mode的對應Vector處理Handlers. 在(0xffff0000+0x1000-(__kuser_helper_end - __kuser_helper_start) )之後配置User Helpers(複製來源為__kuser_helper_start到__kuser_helper_end),每個User Helpers的Segment為32bytes alignment,主要用以由Kernel這提供給User Mode運作的操作,以提高處理的效率,支援的函式包括__kernel_cmpxchg,__kernel_get_tls,__kernel_helper_version. 因此,觸發中斷的流程為,先到0xffff0000對應的中斷,透過Branch到Vector Stubs,再由Vector Strubs到對應的User Mode/SVC Mode的中斷處理函式,例如IRQ中斷觸發後,透過0x00000018到vector_irq再到__irq_usr或__irq_svc中斷處理函式.

8,執行Machine Descriptor 的init_early

setup_nr_cpu_ids

實作在檔案kernel/smp.c中,

會呼叫find_last_bit去尋找Cpumaks中最後設定的bits,用以決定在SMP中支援的處理器個數. 結果會儲存在變數nr_cpu_ids.

setup_per_cpu_areas

實作在檔案mm/percpu.c中,

針對單核心與SMP有不同的實作,用以配置每個處理器在記憶體中的變數區域.

smp_prepare_boot_cpu

//arch-specific boot-cpu hooks

實作在檔案arch/arm/kernel/smp.c中,

設定目前處理器的idle為目前的Process ID.

呼叫函式build_all_zonelists,page_alloc_init,parse_early_param..etcThese use large bootmem allocations and must precede  kmem_cache_init()pidhash_init

實作在檔案kernel/pid.c中,

產生搭配O(1) Scheduling的PID Hash Table,

vfs_caches_init_early

實作在檔案fs/dcache.c中,

會再呼叫dcache_init_early與inode_init_early

sort_main_extable

實作在檔案kernel/extable.c中,

呼叫sort_extable(in sort_extable)對Linux Kernel中的built-in exception table進行排序

mm_init配置Linux Kernel memory allocatorsSet up the scheduler prior starting any interrupts (such as the timer interrupt). Full topology setup happens at smp_init() time - but meanwhile we still have a functioning scheduler.sched_init

實作在檔案kernel/sched.c中.

初始化支援多核心的排程機制.

後續還會在kernel_init中呼叫函式sched_init_smp

Disable preemption - early bootup scheduling is extremely  fragile until we cpu_idle() for the first time.呼叫函式preempt_disable與irqs_disabled關閉Preemptive Scheduling與IRQ.idr_init_cache

實作在檔案lib/idr.c中,

for “Small id to pointer translation service.”

rcu_init

實作在檔案kernel/rcutree.c中

用以初始化Read Copy Update機制.這是Linux Kernel在2.5版本後所支援的同步(synchronization)機制,可用以確保當有多個Reader讀取同一個資料時,在同一個Grace週期中,所有Reader讀取到的內容都是一致的.參考Linux Kernel Documentation的內容,RCU讀取的範例如下

rcu_read_lock();

p = rcu_dereference(head.next);

y = p->data;

rcu_read_unlock();

尤其是在有多核心(SMP)的架構下,當透過RCU機制保護的資料結構被更動時,有RCU保護的內容,在rcu_read_lock/rcu_read_unlock之間,可以確保每個處理器上在這狀態下每個Process所讀取的同一個資料結構內容都是一致的.

RCU是一個在多核心下,很值得善用的資料同步機制,筆者會在後續有專文討論.

radix_tree_init

實作在檔案lib/radix-tree.c中,

用以初始化Radix Tree,作為快速查找的搜尋樹.在Linux Kernel中包括Cache機制,也有採用這資料結構加速效能.

init some links before init_ISA_irqs()early_irq_init

實作在檔案kernel/irq/irqdesc.c中,

會初始化IRQ Descriptor,這機制主要用以抽象化Device Driver處理的Interrupt Handler,讓Device Driver不需要因為平台的差異,而去因應對於中斷的處理行為. (可以透過一致的函式去request, enable, disable 與 free interrupts),可以參考文件http://www.kernel.org/doc/htmldocs/genericirq.html .

init_IRQ

實作在檔案arch/arm/kernel/irq.c中,

用以呼叫每個平台差異下,各自實作的函式 “init_irq”,以Tegra而言會進入函式tegra_init_irq中.(in arch/arm/mach-tegra/irq.c)

prio_tree_init

實作在檔案lib/prio_tree.c中,

初始化priority search tree.

init_timers

實作在檔案kernel/timer.c中,

初始化支援SMP架構的 per-CPU timer.

hrtimers_init

實作在檔案kernel/hrtimer.c中,

初始化High-resolution kernel timers (相對於kernel/timers中支援的milliseconds sleep,在這機制下可以支援nanoseconds sleep)

softirq_init

實作在檔案kernel/softirq.c中,

初始化Soft IRQ,這是在Linux Kernel中支援Soft IRQ Thread的機制,用以執行基於Kernel Thread的 Interrupt Bottom Halves (也就是一般RTOS下的HISR機制.)(參考網頁http://elinux.org/Soft_IRQ_Threads )

timekeeping_init

實作在檔案kernel/time/timekeeping.c中,

初始化Generic Timekeeping Subsystem,用以更新系統的流逝時間,會更新xtime,wall_to_monotonic與total_sleep_time,並負責較整誤差(包括從系統Suspend到Resume的時間差),支援getnstimeofday,do_gettimeofday,do_settimeofday...etc函式.

(參考網頁http://book.opensourceproject.org.cn/kernel/kernel3rd/opensource/0596005652/understandlk-chp-6-sect-2.html )

time_init

實作在檔案arch/arm/kernel/time.c中,

會初始化平台上的System Timer,以Tegra為例,會依據每個平台的實作,透過machine_desc->timer進入函式tegra_init_timer (in arch/arm/mach-tegra/timer.c)

local_irq_enable

在這會透過CPSR確認IRQ是否有被提前Enable,與指令"cpsie i” 開啟Local IRQ. (可以參考arch/arm/include/asm/irqflags.h中arch_local_irq_enable的實作.)

CPSIE i       (Enable irq)

CPSID i      (Disable irq)

CPSIE f       (Enable fiq)

CPSID f (Disable fiq)

console_init

實作在檔案drivers/tty/tty_io.c中,

Initialize the console device early.

setup_per_cpu_pageset

實作在檔案mm/page_alloc.c中,

在這會幫每個處理器配置Page Table. 原本只有啟動時,在Bootstrap處理器TLB針對Linux Kernel的Page 1MB Section配置.  (Page Table的配置會抽空進一步說明)

sched_clock_init

實作在檔案kernel/sched_clock.c中,

初始化每個處理器的Schedule Clock Data,並支援sched_clock傳回系統開機到目前經過的nanoseconds時間值.(透過 jiffies (每一次Timer-Interrupr加一),與 HZ(HZ為每一秒的Timer Interrupt次數 (以Tegra而言定義為CONFIG_HZ=100))計算.)

calibrate_delay

實作在檔案init/calibrate.c中,

這函式就是著名的BogoMIPS(Bogo Millions Instructions Per Second)計算函式,會產生loops_per_jiffy值,並用以算出最後的BogoMIPS.

開機過程會顯示 ”Calibrating delay loop (skipped), value calculated using timer frequency.. AAA.

BB BogoMIPS (lpj=CCCCC)”.  可以透過這網頁http://www.clifton.nl/bogo-list.html 知道不同處理器所對應的BogoMIPS.

pidmap_init

實作在檔案kernel/pid.c中,

Generic pidhash and scalable, time-bounded PID allocator.

會參考處理器的數量,訂定PID數量範圍,並配置PID Map記憶體.

anon_vma_init

實作在檔案mm/rmap.c中,

初始化Anonymous VMA(Virtual Memory Area)機制.

簡要來說記憶體的Page Mapping可以分為

1,Anonymous Page: 只要不是屬於以檔案內容進行的記憶體Mapping,就屬此類.例如: “mmap(NULL, Size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);”

2,File-Mapped Page: 以檔案為記憶體Mapping的分頁. 例如: “mmap(NULL, Size, PROT_READ|PROT_WRITE, MAP_PRIVATE, vFile, 0);”

也可以參考這網頁的說明

http://linuxkernelpanic.blogspot.com/2010/05/while-getting-in-touch-recently-with-ex.html

cred_init

實作在檔案kernel/cred.c中,

初始化Credentials,這是在Linux Kernel中用來管控權限的機制,包括在User Mode的Task要去執行系統呼叫(System Call)時,Task Capability的安全稽核機制,會呼叫current_cred取得目前Task的Credentials,並確認該Task是否具備可以執行該系統呼叫的權限,確保系統的安全性. 防止沒有經過授權的使用者Task,去進行屬於特權等級使用者的動作.

fork_init

實作在檔案kernel/fork.c中,

初始化fork機制,會參考totalram_pages決定可以fork的行程數量max_threads.(最低不小於20個),

proc_caches_init

實作在檔案kernel/fork.c中,

配置行程Process所需的Slab Cache,包括sighand/signal/files/fs/mm_struct/vm_area_struct cache,並執行函式mmap_init

buffer_init

實作在檔案fs/buffer.c中,

初始化FileSystem Buffer,會呼叫nr_free_buffer_pages取得目前系統Free的空間值,並定義最大不超過該值的10%,作為FileSystem Buffer的空間.

vfs_caches_init

實作在檔案fs/dcache.c中,

初始化VFS的 dcache跟inode Cache.

signals_init

實作在檔案kernel/signal.c中,

初始化Signal Queue

rootfs populating might need page-writebackpage_writeback_init

實作在檔案mm/page-writeback.c中,

用來初始化檔案系統中用來作為Cache的記憶體Writeback回儲存媒體的機制,會產生pdflush Kernel Thread執行Dirty Page Writeback的流程.

一般來說,有兩種情況會觸發pdflush流程

1,當系統Free Memory過低,進行Shrink Page時,可透過pdflush回寫到磁碟釋放出記憶體.

2,當Dirty Page數量達到一個值或是相對比例時.

參考Linux Kernel 2.6.39文件Documentation/sysctl/vm.txt,透過檔案/proc/sys/vm/nr_pdflush_threads可知道目前啟動的pdflush daemon數量(這要視系統Runtime Dirty Pages的情況而定). 此外,透過設定/proc/sys/vm/dirty_background_ratio (以相對System Memory的百分比)或/proc/sys/vm/dirty_background_bytes (以Dirty Memory數量)二則一,可設定觸發pdflush daemon進行Page Writeback的條件.

(可以參考網頁http://www.westnet.com/~gsmith/content/linux-pdflush.htm或http://www.debian.org.tw/index.php/PageCache_writeback)

proc_root_init

實作在檔案fs/proc/root.c中,

初始化 proc 檔案系統.

check_bugs

實作在檔案arch/arm/mm/fault-armv.c中,

check_bugs 會被define為 “check_writebuffer_bugs”,用以Check處理器平台Bugs的狀況.

而在筆者所編譯的ARM環境中, check_writebuffer_bugs會透過vmap(實作在mm/vmalloc.c中),把同一個屬性為Bufferable的Page對應到兩個Linux Kernel虛擬記憶體中,再透過設計好 check_writebuffer函式,對剛才得到的兩個虛擬記憶體位置寫入資料,並在每個動作後,加入ARM DSB(Data Synchronization Barrier)指令,透過 “dsb sy” 同步Cache, Branch Predictor 與TLB內容.

如果說,兩個對應到同一個實體記憶體的Bufferable虛擬記憶體寫入動作,最後內容不是一致的,這個ARM平台就可能有Write Buffer Coherency的Bug存在.

rest_init

實作在檔案init/main.c中,

這是start_kernel最後的函式. 有關這函式與後續kernel_init的介紹,會放在下一次的文章中.

結語

Linux Kernel是一個經過長期累積的知識庫,並且由於眾人的幫助,Linux Kernel也支援最新的ARMv7指令集,並支援處理器Bugs確認的機制,對於一般開發者而言,只要善用Linux Kernel所提供的成果,便可以在最快的時間內,進行核心的移植與系統的調教.

隨著多核心SMP架構在Intel與ARM平台的普及,我們可以看到Linux對於SMP 多核心原生架構的支援也非常的充分,相關的核心模塊都會考慮到多核心並取得處理器的數量,做出對應資料結構與行為的因應,對於開發者在新架構的適應來說,可以很幸福的基於這些成果來發展. 尤其,隨著Android的普及,以及在Android 3.0 Framework對於多核心的初步支援,我們可以預期,未來考量到省電與效能的平衡,多核心肯定會是消費性產品的主流,對有至於從事消費性產品開發的人而言, 這部份的知識建立,也是當務之急.

原创粉丝点击