文章來源:http://www.top-e.org/jiaoshi/class/
一般來說,使用者是不需要關心section的具體位置的。在使用者態,核心會解析elf可執行檔的各個section,然後把它映射到虛擬位址空間。然而,在核心啟動時,一切得從零開始。很多在使用者態下應用程式不需要操心的東西,例如映射section的任務不得不由核心自己來完成。上一篇感悟揭示了核心如何建立頁表,並且把自身的一部分映射到虛擬位址。核心還要負責對BSS段(所有在代碼中未定義的全域變數)的初始化(設定為0),這就要求核心知道section的具體位置(否則如何知道該映射哪一部分呢?)
此外,在開啟頁面映射的過程中,我最為疑惑的是幾個常量(頁目錄swapper_pg_dir,頁表pg0等等)是如何確定的。擴充一下。gcc連結可執行檔時,是如何確定變數的地址的?按理說應該有某種途徑(命令列參數或者檔案)告訴連結器ld如何定位這些變數。最普通如hello world。為什麼_start的地址是0x80482e0?於是想到,我們需要一個檔案來指定各個section的虛擬位址。在核心原始碼裡,還看到這個檔案arch/i386/kernel/vmlinux.lds.S。不像是普通的彙編檔案。原來這就是linker scripts連結器指令碼。
在連結器指令碼中,.表示當前location counter地址計數器的值。預設為0。
017 . = __KERNEL_START;
表示地址計數器從__KERNEL_START(0xc00100000)開始。
.text:{...}
表示.text section包含了哪幾個section
031 . = ALIGN(16);
則表示對齊。
具體格式可以調用info ld查看Linker Scripts一節。
連結器指令碼指定了各個section的起始位置和結束位置。它還允許程式員在指令碼中對變數進行賦值。這使核心可以通過__initcall_start和__initcall_end之類的變數獲得段的起始地址和結束位址,從而對某些段進行操作。
根據連結器指令碼,以及nm vmlinux的結果,核心中各個section的虛擬位址就很清楚了。以我的機子為例(粗略):
地址分配
text section:
從_text:c0100000 A _text
到_etext:c0436573 A _etext
Exception table
從__start___ex_table:c0436580 A __start___ex_table
到__stop___ex_table:c04370b8 A __stop___ex_table
RODATA read only section
.data writable section
.data_nosave section
從__nosave_begin:c050f000 A __nosave_begin
到__nosave_end:c050f000 A __nosave_end
.data.page_aligned section
.data.cacheline_aligned section
.data.read_mostly section
.data.init_task section
init section
從__init_begin c0514000 A __init_begin
到__init_end c0540000 A __init_end
其中.initcall.init section:
從__initcall_start:c053b570 A __initcall_start
到__initcall_end:c053b8c0 A __initcall_end
BSS section
從__bss_start c0540000 A __bss_start
到__bss_end c0594c78 A __bss_stop
其中 swapper進程的頁表
從c0540000 B swapper_pg_dir
到c0541000
共一頁
empty_zero_page
從c0541000 B empty_zero_page
到c0542000
共一頁
pg0 頁目錄0
從c0595000 A pg0
到init_pg_tables_end
.exitcall.exit
section
stab section
幾個比較重要的section:
bss section,存放在代碼裡未初始化的全域變數,最後初始化為0。
init sections,所有只在初始化時調用的函數和變數,包括所有在核心啟動時調用的函數,以及核心模組初始化時調用的函數。其中最特別的是.initcall.init section。通過__initcall_start和__initcall_end,核心可以調用裡面所有的函數。這些section在使用一次後就可以釋放,從而節省記憶體。