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

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

hlchou@mail2000.com.tw

by loda.


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_d

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值