核心在啟動時可以傳遞一個字元串指令行,來控制核心啟動的過程,例如:
"console=ttyS2,115200 [email protected]"
這裡指定了控制台是序列槽2,波特率是115200,記憶體大小是64M,實體基位址是0xA0000000。
另外我們可以在核心中定義一些全局變量,使用這些全局變量控制核心的配置,例如usb驅動中定義了
static int nousb;
這個變量為1,則整個usb驅動不初始化,如果想将其置1,可在字元串指令行中添加nousb=1。
在操作該變量之前,還要讓系統知道該變量,方法是:
__module_param_call("",nousb,param_set_bool,param_get_bool,&nousb,0444);
__module_param_call這個宏定義在kernel/include/linux/moduleparam.h
原型如下:
#define __module_param_call(prefix, name, set, get, arg, perm) /
static char __param_str_##name[] = prefix #name; /
static struct kernel_param const __param_##name /
__attribute_used__ /
__attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) /
= { __param_str_##name, perm, set, get, arg }
它定義了一個kernel_param類型的變量,這個變量被放到了段__param,
kernel_param結構體的定義是:
struct kernel_param {
const char *name;
unsigned int perm;
param_set_fn set;
param_get_fn get;
void *arg;
};
__param這個段的聲明有些平台是在arch/../../vmlinux.lds.S,而大多數平台是放到
kernel/include/asm-generic/vmlinux.lds.h中,定義如下:
__param : AT(ADDR(__param) - LOAD_OFFSET) { /
VMLINUX_SYMBOL(__start___param) = .; /
*(__param) /
VMLINUX_SYMBOL(__stop___param) = .; /
}
核心啟動時就會對字元串指令進行解析,在kernel/init/main.c中,核心啟動函數start_kernel中
對外部數組進行了聲明:
extern struct kernel_param __start___param[], __stop___param[];
然後調用函數parse_args對數組進行解析:
parse_args("Booting kernel", command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
其中command_line就是要解析的字元串指令行,unknown_bootoption是函數指針,它用來擷取指定參數的=右邊的值。
parse_args就會在數組中找到和nousb名稱一樣的kernel_param變量,并調用它的set函數對其進行付值。
核心啟動位址的确定
核心編譯連結過程是依靠vmlinux.lds檔案,以arm為例vmlinux.lds檔案位于kernel/arch/arm/vmlinux.lds,
但是該檔案是由vmlinux-armv.lds.in生成的,根據編譯選項的不同源檔案還可以是vmlinux-armo.lds.in,
vmlinux-armv-xip.lds.in。
vmlinux-armv.lds的生成過程在kernel/arch/arm/Makefile中
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) /
$(wildcard include/config/cpu/32.h) /
$(wildcard include/config/cpu/26.h) /
$(wildcard include/config/arch
_stext = .;
__init_begin = .;
*(.text.init)
__proc_info_begin = .;
*(.proc.info)
__proc_info_end = .;
__arch_info_begin = .;
*(.arch.info)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist)
__tagtable_end = .;
*(.data.init)
. = ALIGN(16);
__setup_start = .;
*(.setup.init)
__setup_end = .;
__initcall_start = .;
*(.initcall.init)
__initcall_end = .;
. = ALIGN(4096);
__init_end = .;
}
其中TEXTADDR就是核心啟動的虛拟位址,定義在kernel/arch/arm/Makefile中:
ifeq ($(CONFIG_CPU_32),y)
PROCESSOR = armv
TEXTADDR = 0xC0008000
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
endif
需要注意的是這裡是虛拟位址而不是實體位址。
一般情況下都在生成vmlinux後,再對核心進行壓縮成為zImage,壓縮的目錄是kernel/arch/arm/boot。
下載下傳到flash中的是壓縮後的zImage檔案,zImage是由壓縮後的vmlinux和解壓縮程式組成,如下圖所示:
|-----------------|/ |-----------------|
| | / | |
| | / | decompress code |
| vmlinux | / |-----------------| zImage
| | /| |
| | | |
| | | |
| | | |
| | /|-----------------|
| | /
| | /
| | /
|-----------------|/
zImage連結腳本也叫做vmlinux.lds,位于kernel/arch/arm/boot/compressed。
是由同一目錄下的vmlinux.lds.in檔案生成的,内容如下:
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = LOAD_ADDR;
_load_addr = .;
. = TEXT_START;
_text = .;
.text : {
_start = .;
其中LOAD_ADDR就是zImage中解壓縮代碼的ram偏移位址,TEXT_START是核心ram啟動的偏移位址,這個位址是實體位址。
在kernel/arch/arm/boot/Makefile檔案中定義了:
ZTEXTADDR =0
ZRELADDR = 0xa0008000
ZTEXTADDR就是解壓縮代碼的ram偏移位址,ZRELADDR是核心ram啟動的偏移位址,這裡看到指定ZTEXTADDR的位址為0,
明顯是不正确的,因為我的平台上的ram起始位址是0xa0000000,在Makefile檔案中看到了對該位址設定的幾行注釋:
# We now have a PIC decompressor implementation. Decompressors running
# from RAM should not define ZTEXTADDR. Decompressors running directly
# from ROM or Flash must define ZTEXTADDR (preferably via the config)
他的意識是如果是在ram中進行解壓縮時,不用指定它在ram中的運作位址,如果是在flash中就必須指定他的位址。是以
這裡将ZTEXTADDR指定為0,也就是沒有真正指定位址。
在kernel/arch/arm/boot/compressed/Makefile檔案有一行腳本:
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/
使得TEXT_START = ZTEXTADDR,LOAD_ADDR = ZRELADDR。
這樣vmlinux.lds的生成過程如下:
vmlinux.lds: vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
@sed "$(SEDFLAGS)" < vmlinux.lds.in > $@
以上就是我對核心啟動位址的分析,總結一下核心啟動位址的設定:
1、設定kernel/arch/arm/Makefile檔案中的
TEXTADDR = 0xC0008000
核心啟動的虛拟位址
2、設定kernel/arch/arm/boot/Makefile檔案中的
ZRELADDR = 0xa0008000
核心啟動的實體位址
如果需要從flash中啟動還需要設定
ZTEXTADDR位址。
核心解壓縮過程
核心壓縮和解壓縮代碼都在目錄kernel/arch/arm/boot/compressed,
編譯完成後将産生vmlinux、head.o、misc.o、head-xscale.o、piggy.o這幾個檔案,
head.o是核心的頭部檔案,負責初始設定;
misc.o将主要負責核心的解壓工作,它在head.o之後;
head-xscale.o檔案主要針對Xscale的初始化,将在連結時與head.o合并;
piggy.o是一個中間檔案,其實是一個壓縮的核心(kernel/vmlinux),隻不過沒有和初始化檔案及解壓檔案連結而已;
vmlinux是(沒有--lw:zImage是壓縮過的核心)壓縮過的核心,就是由piggy.o、head.o、misc.o、head-xscale.o組成的。
在BootLoader完成系統的引導以後并将Linux核心調入記憶體之後,調用bootLinux(),
這個函數将跳轉到kernel的起始位置。如果kernel沒有壓縮,就可以啟動了。
如果kernel壓縮過,則要進行解壓,在壓縮過的kernel頭部有解壓程式。
壓縮過得kernel入口第一個檔案源碼位置在arch/arm/boot/compressed/head.S。
它将調用函數decompress_kernel(),這個函數在檔案arch/arm/boot/compressed/misc.c中,
decompress_kernel()又調用proc_decomp_setup(),arch_decomp_setup()進行設定,
然後使用在列印出資訊“Uncompressing Linux...”後,調用gunzip()。将核心放于指定的位置。
以下分析head.S檔案:
(1)對于各種Arm CPU的DEBUG輸出設定,通過定義宏來統一操作。
(2)設定kernel開始和結束位址,儲存architecture ID。
(3)如果在ARM2以上的CPU中,用的是普通使用者模式,則升到超級使用者模式,然後關中斷。
(4)分析LC0結構delta offset,判斷是否需要重載核心位址(r0存入偏移量,判斷r0是否為零)。
這裡是否需要重載核心位址,我以為主要分析arch/arm/boot/Makefile、arch/arm/boot/compressed/Makefile
和arch/arm/boot/compressed/vmlinux.lds.in三個檔案,主要看vmlinux.lds.in連結檔案的主要段的位置,
LOAD_ADDR(_load_addr)=0xA0008000,而對于TEXT_START(_text、_start)的位置隻設為0,BSS_START(__bss_start)=ALIGN(4)。
對于這樣的結果依賴于,對核心解壓的運作方式,也就是說,核心解壓前是在記憶體(RAM)中還是在FLASH上,
因為這裡,我們的BOOTLOADER将壓縮核心(zImage)移到了RAM的0xA0008000位置,我們的壓縮核心是在記憶體(RAM)從0xA0008000位址開始順序排列,
是以我們的r0獲得的偏移量是載入位址(0xA0008000)。接下來的工作是要把核心鏡像的相對位址轉化為記憶體的實體位址,即重載核心位址。
(5)需要重載核心位址,将r0的偏移量加到BSS region和GOT table中。
(6)清空bss堆棧空間r2-r3。
(7)建立C程式運作需要的緩存,并賦于64K的棧空間。
(8)這時r2是緩存的結束位址,r4是kernel的最後執行位址,r5是kernel境象檔案的開始位址。檢查是否位址有沖突。
将r5等于r2,使decompress後的kernel位址就在64K的棧之後。
(9)調用檔案misc.c的函數decompress_kernel(),解壓核心于緩存結束的地方(r2位址之後)。此時各寄存器值有如下變化:
r0為解壓後kernel的大小
r4為kernel執行時的位址
r5為解壓後kernel的起始位址
r6為CPU類型值(processor ID)
r7為系統類型值(architecture ID)
(10)将reloc_start代碼拷貝之kernel之後(r5+r0之後),首先清除緩存,而後執行reloc_start。
(11)reloc_start将r5開始的kernel重載于r4位址處。
(12)清除cache内容,關閉cache,将r7中architecture ID賦于r1,執行r4開始的kernel代碼。
下面簡單介紹一下解壓縮過程,也就是函數decompress_kernel實作的功能:
解壓縮代碼位于kernel/lib/inflate.c,inflate.c是從gzip源程式中分離出來的。包含了一些對全局資料的直接引用。
在使用時需要直接嵌入到代碼中。gzip壓縮檔案時總是在前32K位元組的範圍内尋找重複的字元串進行編碼,
在解壓時需要一個至少為32K位元組的解壓緩沖區,它定義為window[WSIZE]。inflate.c使用get_byte()讀取輸入檔案,
它被定義成宏來提高效率。輸入緩沖區指針必須定義為inptr,inflate.c中對之有減量操作。inflate.c調用flush_window()
來輸出window緩沖區中的解壓出的位元組串,每次輸出長度用outcnt變量表示。在flush_window()中,還必
須對輸出位元組串計算CRC并且重新整理crc變量。在調用gunzip()開始解壓之前,調用makecrc()初始化CRC計算表。
最後gunzip()傳回0表示解壓成功。
我們在核心啟動的開始都會看到這樣的輸出:
Uncompressing Linux...done, booting the kernel.
這也是由decompress_kernel函數内部輸出的,它調用了puts()輸出字元串,
puts是在kernel/include/asm-arm/arch-pxa/uncompress.h中實作的。
執行完解壓過程,再傳回到head.S中,啟動核心:
call_kernel: bl cache_clean_flush
bl cache_off
mov r0, #0
mov r1, r7 @ restore architecture number
mov pc, r4 @ call kernel
下面就開始真正的核心了。
彙編部分(1)
在網上參考很多高手的文章,又加入了自己的一點兒内容,整理了一下,裡面還有很多不明白的地方,而且也會有了解錯誤的地方,望高手指點,自己也會不斷進行修改
當進入linux核心後,arch/arm/kernel/head-armv.S是核心最先執行的一個檔案,包括從核心入口ENTRY(stext)到
start_kernel之間的初始化代碼,下面以我所是用的平台intel pxa270為例,說明一下他的彙編代碼:
1 .section ".text.init",#alloc,#execinstr
2 .type stext, #function
3 ENTRY(stext)
4 mov r12, r0
5 mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode
6 msr cpsr_c, r0 @ and all irqs disabled
7 bl __lookup_processor_type
8 teq r10, #0 @ invalid processor?
9 moveq r0, #'p' @ yes, error 'p'
10 beq __error
11 bl __lookup_architecture_type
12 teq r7, #0 @ invalid architecture?
13 moveq r0, #'a' @ yes, error 'a'
14 beq __error
15 bl __create_page_tables
16 adr lr, __ret @ return address
17 add pc, r10, #12 @ initialise processor
@ (return control reg)
第5行,準備進入SVC工作模式,同時關閉中斷(I_BIT)和快速中斷(F_BIT)
第7行,檢視處理器類型,主要是為了得到處理器的ID以及頁表的flags。
第11行,檢視一些體系結構的資訊。
第15行,建立頁表。
第17行,跳轉到處理器的初始化函數,其函數位址是從__lookup_processor_type中得到的,
需要注意的是第16行,當處理器初始化完成後,會直接跳轉到__ret去執行,
這是由于初始化函數最後的語句是mov pc, lr。
http://hi.baidu.com/anorchidwith/blog/item/cac6e30903fca5266a60fb1f.html