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

时间:2023-02-15 16:56:22

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記憶體範圍的單核心程式碼依據其對應的記憶體位址,進行修正動作.

運作概念如下圖所示


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

從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)=>要看總共編譯多少核心模組與程式碼範圍.

如下圖所示


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

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如下示意圖


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

以配置在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的流程,簡要描述如下圖所示


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


結束了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.


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

如果我們所編譯的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格式如下所示

31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Implementor Variant Architecture Primary Part Number Revision
欄位 說明
Implementor ARM處理器為0x41
Variant

可用以表示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 執行的流程示意圖,如下所示


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

start_kernel的流程

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

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

整理後的內容如下所示

start_kernel
初始化函式的流程
說明
smp_setup_processor_id Do 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..etc
These 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 allocators
Set 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-writeback
page_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.htmhttp://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對於多核心的初步支援,我們可以預期,未來考量到省電與效能的平衡,多核心肯定會是消費性產品的主流,對有至於從事消費性產品開發的人而言, 這部份的知識建立,也是當務之急.