1. 內核啟動地址
1.1. 名詞解釋
ZTEXTADDR
解壓代碼運行的開始地址。沒有物理地址和虛擬地址之分,因為此時MMU處於關閉狀態。這個地址不一定時RAM的地址,可以是支持讀寫尋址的flash等存儲中介。
Start address of decompressor. here's no point in talking about virtual or physical addresses here, since the MMU will be off at the time when you call the decompressor code. You normally call the kernel at this address to start it booting. This doesn't have to be located in RAM, it can be in flash or other read-only or read-write addressable medium.
ZRELADDR
內核啟動在RAM中的地址。壓縮的內核映像被解壓到這個地址,然后執行。
This is the address where the decompressed kernel will be written, and eventually executed. The following constraint must be valid:
__virt_to_phys(TEXTADDR) == ZRELADDR
The initial part of the kernel is carefully coded to be position independent.
TEXTADDR
內核啟動的虛擬地址,與ZRELADDR相對應。一般內核啟動的虛擬地址為RAM的第一個bank地址加上0x8000。
TEXTADDR = PAGE_OFFSET + TEXTOFFST
Virtual start address of kernel, normally PAGE_OFFSET + 0x8000.This is where the kernel image ends up. With the latest kernels, it must be located at 32768 bytes into a 128MB region. Previous kernels placed a restriction of 256MB here.
TEXTOFFSET
內核偏移地址。在arch/arm/makefile中設定。
PHYS_OFFSET
RAM第一個bank的物理起始地址。
Physical start address of the first bank of RAM.
PAGE_OFFSET
RAM第一個bank的虛擬起始地址。
Virtual start address of the first bank of RAM. During the kernel
boot phase, virtual address PAGE_OFFSET will be mapped to physical
address PHYS_OFFSET, along with any other mappings you supply.
This should be the same value as TASK_SIZE.
1.2. 內核啟動地址確定
內核啟動引導地址由bootp.lds決定。 Bootp.lds : arch/arm/bootp
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0;
.text : {
_stext = .;
*(.start)
*(.text)
initrd_size = initrd_end - initrd_start;
_etext = .;
}
}
由上 .= 0可以確定解壓代碼運行的開始地址在0x0的位置。ZTEXTADDR的值決定了這個值得選取。
Makefile : arch/arm/boot/compressed
如果設定內核從ROM中啟動的話,可以在make menuconfig 的配置界面中設置解壓代碼的起始地址,否則解壓代碼的起始地址為0x0。實際上,默認從ROM啟動時,解壓代碼的起始地址也是0x0。
feq ($(CONFIG_ZBOOT_ROM),y)
ZTEXTADDR := $(CONFIG_ZBOOT_ROM_TEXT)
ZBSSADDR := $(CONFIG_ZBOOT_ROM_BSS)
else
ZTEXTADDR :=0 ZBSSADDR := ALIGN(4)
endif
SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/BSS_START/$(ZBSSADDR)/
……
$(obj)/vmlinux.lds: $(obj)/vmlinux.lds.in arch/arm/mach-s3c2410/Makefile .config
@sed "$(SEDFLAGS)" < $< > $@
@sed "$(SEDFLAGS)" < $< > $@ 規則將TEXT_START設定為ZTEXTADDR。TEXT_START在arch/arm/boot/compressed/vmlinux.lds.in 中被用來設定解壓代碼的起始地址。
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = TEXT_START;
_text = .;
.text : {
_start = .;
*(.start)
*(.text)
*(.text.*)
……
}
}
內核的編譯依靠vmlinux.lds,vmlinux.lds由vmlinux.lds.s 生成。從下面代碼可以看出內核啟動的虛擬地址被設置為PAGE_OFFSET + TEXT_OFFSET,而內核啟動的物理地址ZRELADDR在arch/arm/boot/Makefile中設定。
OUTPUT_ARCH(arm)
ENTRY(stext)
SECTIONS
{
#ifdef CONFIG_XIP_KERNEL
. = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
#else
. = PAGE_OFFSET + TEXT_OFFSET;
#endif
.init : { /* Init code and data */
_stext = .;
_sinittext = .;
*(.init.text)
_einittext = .;
……
}
}
# arch/arm/boot/Makefile
# Note: the following conditions must always be true:
# ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)
# PARAMS_PHYS must be within 4MB of ZRELADDR
# INITRD_PHYS must be in RAM
ZRELADDR := $(zreladdr-y)
#---> zrealaddr-y is specified with 0x30008000 in arch/arm/boot/makefile.boot
PARAMS_PHYS := $(params_phys-y)
INITRD_PHYS := $(initrd_phys-y)
export ZRELADDR INITRD_PHYS PARAMS_PHYS
通過下面的命令編譯內核映像,由參數-a, -e設置其入口地址為ZRELADDR,此值在上面ZRELADDR := $(zreladdr-y)指定。
quiet_cmd_uimage= UIMAGE $@
cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A arm -O linux -T kernel \
-C none -a $(ZRELADDR) -e $(ZRELADDR) \
-n 'Linux-$(KERNELRELEASE)' -d $< $@
1.3. 小結
從上面分析可知道,linux內核被bootloader拷貝到RAM后,解壓代碼從ZTEXTADDR開始運行(這段代碼是與位置無關的PIC)。內核被解壓縮到ZREALADDR處,也就是內核啟動的物理地址處。相應地,內核啟動的虛擬地址被設定為TEXTADDR,滿足如下條件:
TEXTADDR = PAGE_OFFSET + TEXT_OFFSET
內核啟動的物理地址和虛擬地址滿足入下條件:
ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)= virt_to_phys(TEXTADDR)
假定開發板為smdk2410,則有:
內核啟動的虛擬地址
TEXTADDR = 0xC0008000
內核啟動的物理地址
ZRELADDR = 0x30008000
如果直接從flash中啟動還需要設置ZTEXTADDR地址。
2. 內核啟動過程分析
內核啟動過程經過大體可以分為兩個階段:內核映像的自引導;linux內核子模塊的初始化。
start
Decompress_kernel()
Call_kernel
Stext:
Prepare_namespace
Do_basic_setup
init
Rest_init
Setup_arch ……
Start_kernel
_enable_mmu
Execve(“/sbin/init”))
內核啟動流程圖
2.1. 內核映像的自引導
這階段的主要工作是實現壓縮內核的解壓和進入內核代碼的入口。
Bootloader完成系統引導后,內核映像被調入內存指定的物理地址ZTEXTADDR。典型的內核映像由自引導程序和壓縮的VMlinux組成。因此在啟動內核之前需要先把內核解壓縮。內核映像的入口的第一條代碼就是自引導程序。它在arch/arm/boot/compressed/head.S文件中。
Head.S文件主要功能是實現壓縮內核的解壓和跳轉到內核vmlinux內核的入口。Decompress_kernel(): arch/arm/boot/compressed/misc.c 和call_kernel這兩個函數實現了上述功能。在調用decompress_kernel()解壓內核之前,需要確保解壓后的內核代碼不會覆蓋掉原來的內核映像。以及設定內核代碼的入口地址ZREALADDR。
.text
adr r0, LC0
ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
.type LC0, #object
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word zreladdr @ r4
.word _start @ r5
.word _got_start @ r6
.word _got_end @ ip
.word user_stack+4096 @ sp
上面這段代碼得到內核代碼的入口地址,保存在r4中。
/*
* Check to see if we will overwrite ourselves.
* r4 = final kernel address
* r5 = start of this image
* r2 = end of malloc space (and therefore this image)
* We basically want:
* r4 >= r2 -> OK
* r4 + image length <= r5 -> OK
*/
cmp r4, r2
bhs wont_overwrite
add r0, r4, #4096*1024 @ 4MB largest kernel size
cmp r0, r5
bls wont_overwrite
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
b call_kernel
上面代碼判斷解壓后的內核代碼會不會覆蓋原來的內核映像,然后調用內核解壓縮函數decompress_kernel()。
ulg
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p,
int arch_id)
{
output_data = (uch *)output_start; /* 指定內核執行地址,保存在r4中*/
free_mem_ptr = free_mem_ptr_p;
free_mem_ptr_end = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup(); /*解壓縮前的初始化和設置,包括串口波特率設置等*/
makecrc(); /*CRC校驗*/
putstr("Uncompressing Linux...");
gunzip(); /*調用解壓縮函數*/
putstr(" done, booting the kernel.\n");
return output_ptr;
}