文章來源: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在使用一次後就可以釋放,從而節省內存。