讀Kernel感悟-Linux內核啓動-鏈接腳本

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章