核心編譯連結過程是依靠vmlinux.lds檔案,以
arm為例vmlinux.lds檔案位於kernel/arch/arm/vmlinux.lds,vmlinux-armv.lds的產生過程在kernel/arch/arm/Makefile中
ifeq ($(CONFIG_CPU_32),y)
PROCESSOR = armv
TEXTADDR = 0xC0008000
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
endif
arch/arm/vmlinux.lds: $(LDSCRIPT) dummy
@sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@
查看arch/arm/vmlinux.lds 中
OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
. = 0xC0008000;
.init : { /* Init code and data */
_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 = .;
}
/DISCARD/ : { /* Exit code and data */
*(.text.exit)
*(.data.exit)
*(.exitcall.exit)
}
.text : { /* Real text segment */
_text = .; /* Text and read-only data */
*(.text)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
*(.got) /* Global offset table */
_etext = .; /* End of text section */
}
.kstrtab : { *(.kstrtab) }
. = ALIGN(16);
__ex_table : { /* Exception table */
__start___ex_table = .;
*(__ex_table)
__stop___ex_table = .;
}
__ksymtab : { /* Kernel symbol table */
__start___ksymtab = .;
*(__ksymtab)
__stop___ksymtab = .;
}
. = ALIGN(8192);
.data : {
/*
* first, the init task union, aligned
* to an 8192 byte boundary.
*/
*(.init.task)
/*
* then the cacheline aligned data
*/
. = ALIGN(32);
*(.data.cacheline_aligned)
/*
* and the usual data section
*/
*(.data)
CONSTRUCTORS
_edata = .;
}
.bss : {
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
_end = . ;
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}
arch/arm/Makefile中:
vmlinux: arch/arm/vmlinux.lds
arch/arm/vmlinux.lds: $(LDSCRIPT) dummy
@sed 's/TEXTADDR/$(TEXTADDR)/;s/DATAADDR/$(DATAADDR)/' $(LDSCRIPT) >$@
MAKEBOOT = $(MAKE) -C arch/$(ARCH)/boot
bzImage zImage zinstall Image bootpImage install: vmlinux
@$(MAKEBOOT) $@
但在kernel/arch/arm/boot/Makefile
ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif
zImage: $(CONFIGURE) compressed/vmlinux
$(OBJCOPY) -O binary -R .note -R .comment -S compressed/vmlinux $@
compressed/vmlinux: $(TOPDIR)/vmlinux dep
@$(MAKE) -C compressed vmlinux
在compressed目錄下的Makefile中
ZLDFLAGS = -p -X -T vmlinux.lds
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/;s/BSS_START/$(ZBSSADDR)/
all: vmlinux
vmlinux: $(HEAD) $(OBJS) piggy.o vmlinux.lds
$(LD) $(ZLDFLAGS) $(HEAD) $(OBJS) piggy.o $(LIBGCC) -o vmlinux
vmlinux.lds: vmlinux.lds.in Makefile $(TOPDIR)/arch/$(ARCH)/boot/Makefile $(TOPDIR)/.config
@sed "$(SEDFLAGS)" < vmlinux.lds.in > $@
vmlinux-armv.lds.in檔案的內容:OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = LOAD_ADDR;
_load_addr = .;
. = TEXT_START;
_text = .;
.text : {
_start = .;
*(.start)
*(.text)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
input_data = .;
piggy.o
input_data_end = .;
. = ALIGN(4);
}
_etext = .;
_got_start = .;
.got : { *(.got) }
_got_end = .;
.got.plt : { *(.got.plt) }
.data : { *(.data) }
_edata = .;
. = BSS_START;
__bss_start = .;
.bss : { *(.bss) }
_end = .;
.stack (NOLOAD) : { *(.stack) }
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}
vmlinux.lds內容為
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x30008000;
_load_addr = .;
. = 0;
_text = .;
.text : {
_start = .;
*(.start)
*(.text)
*(.fixup)
*(.gnu.warning)
*(.rodata)
*(.rodata.*)
*(.glue_7)
*(.glue_7t)
input_data = .;
piggy.o
input_data_end = .;
. = ALIGN(4);
}
_etext = .;
_got_start = .;
.got : { *(.got) }
_got_end = .;
.got.plt : { *(.got.plt) }
.data : { *(.data) }
_edata = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
.stack (NOLOAD) : { *(.stack) }
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}
一般情況下都在產生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檔案產生的
在kernel/arch/arm/boot/Makefile檔案中定義了:
ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif
ZTEXTADDR就是解壓縮代碼的ram位移地址,ZRELADDR是核心ram啟動的位移地址,這裡看到指定ZTEXTADDR的地址為30008000,
以上就是我對核心啟動地址的分析,總結一下核心啟動地址的設定:
設定kernel/arch/arm/boot/Makefile檔案中的
ifeq ($(CONFIG_ARCH_S3C2410),y)
ZTEXTADDR = 0x30008000
ZRELADDR = 0x30008000
endif
# 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)
#
查看2410的datasheet ,發現記憶體映射的基址是0x3000 0000 ,那麼 0x30008000又是如何來的呢?
在核心文檔kernel/Document/arm/Booting 檔案中有:
Calling the kernel image
Existing boot loaders: MANDATORY
New boot loaders: MANDATORY
There are two options for calling the kernel zImage. If the zImage is stored in flash, and is linked correctly to be run from flash, then it is legal for the boot loader to call the zImage in flash directly.
The zImage may also be placed in system RAM (at any location) and called there. Note that the kernel uses 16K of RAM below the image to store page tables. The recommended placement is 32KiB into RAM.
看來在image下面用了32K(0x8000)的空間存放核心頁表,
0x30008000就是2410的核心在RAM中的啟動地址,這個地址就是這麼來的。
關於核心解壓縮的過程分析核心壓縮和解壓縮代碼都在目錄kernel/arch/arm/boot/compressed,
編譯完成後將產生vmlinux、head.o、misc.o、head-s3c2410.o、piggy.o這幾個檔案,
head.o是核心的頭部檔案,負責初始設定;
misc.o將主要負責核心的解壓工作,它在head.o之後;
head-s3c2410.o檔案主要針對的初始化,將在連結時與head.o合并;
piggy.o是一個中間檔案,其實是一個壓縮的核心(kernel/vmlinux),只不過沒有和初始設定檔案及解壓檔案連結而已;
vmlinux是沒有(zImage是壓縮過的核心)壓縮過的核心,就是由piggy.o、head.o、misc.o、head-s3c2410.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)=0x30008000,而對於TEXT_START(_text、_start)的位置只設為0,BSS_START(__bss_start)=ALIGN(4)。
對於這樣的結果依賴於,對核心解壓的運行方式,也就是說,核心解壓前是在記憶體(RAM)中還是在FLASH上,
因為這裡,我們的BOOTLOADER將壓縮核心(zImage)移到了RAM的0x30008000位置,我們的壓縮核心是在記憶體(RAM)從0x30008000地址開始順序排列, 因此我們的r0獲得的位移量是載入地址(0x30008000)。
接下來的工作是要把核心鏡像的相對位址轉化為記憶體的物理地址,即重載核心地址。
(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-s3c2410/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
下面就開始真正的核心了。
linux2.6 啟動傳遞命令列分析
核心在啟動時可以傳遞一個字串命令列,來控制核心啟動的過程,例如:
"console=ttyS2,115200mem=64M@0xA0000000"
這裡指定了控制台是串口2,傳輸速率是115200,記憶體大小是64M,物理基地址是0xA0000000。
另外我們可以在核心中定義一些全域變數,使用這些全域變數控制核心的配置,例如
usb
驅動中定義了
static int nousb; /* Disable USB when built into kernel image */
這個變數為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函數對其進行付值。