vmlinuz,Initrd和system.map

在網絡中,不少服務器採用的是Linux系統。爲了進一步提高服務器的性能,可能需要根據特定的硬件及需求重新編譯Linux內核。編譯Linux內核, 需要根據規定的步驟進行,編譯內核過程中涉及到幾個重要的文件。比如對於RedHat Linux,在/boot目錄下有一些與Linux內核有關的文件,進入/boot執行。

 

編譯過RedHat Linux內核的人對其中的System.map、vmlinuz、initrd-2.4.7-10.img印象可能比較深刻,因爲編譯內核過程中涉及到這些文件的建立等操作。那麼這幾個文件是怎麼產生的?又有什麼作用呢?本文對此做些介紹。

 

一、vmlinuz

 

vmlinuz是可引導的、壓縮的內核。“vm”代表“Virtual Memory”。Linux 支持虛擬內存,不像老的操作系統比如DOS有640KB內存的限制。Linux能夠使用硬盤空間作爲虛擬內存,因此得名“vm”。vmlinuz是可執行 的Linux內核,它位於/boot/vmlinuz,它一般是一個軟鏈接,比如圖中是vmlinuz-2.4.7-10的軟鏈接。

 

vmlinuz的建立有兩種方式。一是編譯內核時通過“make zImage”創建,然後通過:“cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage/boot/vmlinuz”產生。zImage適用於 小內核的情況,它的存在是爲了向後的兼容性。

 

二是內核編譯時通過命令make bzImage創建,然後通過:“cp/usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz”產生。bzImage是壓縮的內核映像,需要注意,bzImage不是用bzip2壓縮的,bzImage中的bz容易引起 誤解,bz表示“big zImage”。 bzImage中的b是“big”意思。 zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip壓縮的。它們不僅是一個壓縮文件,而且在這兩個文件的開頭部分內嵌有 gzip解壓縮代碼。所以你不能用gunzip 或 gzip –dc解包vmlinuz。

 

內核文件中包含一個微型的gzip用於解壓縮內核並引導它。兩者的不同之處在於,老的zImage解壓縮內核到低端內存(第一個640K), bzImage解壓縮內核到高端內存(1M以上)。如果內核比較小,那麼可以採用zImage或bzImage之一,兩種方式引導的系統運行時是相同的。 大的內核採用bzImage,不能採用zImage。vmlinux是未壓縮的內核,vmlinuz是vmlinux的壓縮文件。

 

二、initrd-x.x.x.img

 

initrd是“initial ramdisk”的簡寫。initrd一般被用來臨時的引導硬件到實際內核vmlinuz能夠接管並繼續引導的狀態。圖中的initrd-2.4.7-10.img主要是用於加載ext3等文件系統及scsi設備的驅動。

 

比如,使用的是scsi硬盤,而內核vmlinuz中並沒有這個scsi硬件的驅動,那麼在裝入scsi模塊之前,內核不能加載根文件系統,但scsi模 塊存儲在根文件系統的/lib/modules下。爲了解決這個問題,可以引導一個能夠讀實際內核的initrd內核並用initrd修正scsi引導問 題。initrd-2.4.7-10.img是用gzip壓縮的文件,initrd實現加載一些模塊和安裝文件系統等功能。

 

initrd映象文件是使用mkinitrd創建的。mkinitrd實用程序能夠創建initrd映象文件。這個命令是RedHat專有的。其它 Linux發行版或許有相應的命令。這是個很方便的實用程序。具體情況請看幫助:man mkinitrd下面的命令創建initrd映象文件。

 

三、System.map

 

System.map是一個特定內核的內核符號表。它是你當前運行的內核的System.map的鏈接。

 

內核符號表是怎麼創建的呢? System.map是由“nm vmlinux”產生並且不相關的符號被濾出。

 

對於本文中的例子,編譯內核時,System.map創建在/usr/src/linux-2.4/System.map。像下面這樣:

 

nm /boot/vmlinux-2.4.7-10 > System.map

 

下面幾行來自/usr/src/linux-2.4/Makefile:

 

nm vmlinux | grep -v '(compiled)|(.o$$)|( [aUw] )|(..ng$$)|(LASH[RL]DI)' | sort > System.map

 

然後複製到/boot:

 

cp /usr/src/linux/System.map /boot/System.map-2.4.7-10

 

下圖是System.map文件的一部分:

 

在進行程序設計時,會命名一些變量名或函數名之類的符號。Linux內核是一個很複雜的代碼塊,有許許多多的全局符號。

 

Linux內核不使用符號名,而是通過變量或函數的地址來識別變量或函數名。比如不是使用size_t BytesRead這樣的符號,而是像c0343f20這樣引用這個變量。

 

對於使用計算機的人來說,更喜歡使用那些像size_t BytesRead這樣的名字,而不喜歡像c0343f20這樣的名字。內核主要是用c寫的,所以編譯器/連接器允許我們編碼時使用符號名,當內核運行時使用地址。

 

然而,在有的情況下,我們需要知道符號的地址,或者需要知道地址對應的符號。這由符號表來完成,符號表是所有符號連同它們的地址的列表。上圖就是一個內核符號表,由上圖可知變量名checkCPUtype在內核地址c01000a5。

 

Linux 符號表使用到2個文件:

 

/proc/ksyms

 

System.map

 

/proc/ksyms是一個“proc file”,在內核引導時創建。實際上,它並不真正的是一個文件,它只不過是內核數據的表示,卻給人們是一個磁盤文件的假象,這從它的文件大小是0可以看出來。然而,System.map是存在於你的文件系統上的實際文件。

 

當你編譯一個新內核時,各個符號名的地址要發生變化,你的老的System.map具有的是錯誤的符號信息。每次內核編譯時產生一個新的System.map,你應當用新的System.map來取代老的System.map。

 

雖然內核本身並不真正使用System.map,但其它程序比如klogd,lsof和ps等軟件需要一個正確的System.map。如果你使用錯誤的 或沒有System.map,klogd的輸出將是不可靠的,這對於排除程序故障會帶來困難。沒有System.map,你可能會面臨一些令人煩惱的提示 信息。

 

另外少數驅動需要System.map來解析符號,沒有爲你當前運行的特定內核創建的System.map它們就不能正常工作。

 

Linux的內核日誌守護進程klogd爲了執行名稱-地址解析,klogd需要使用System.map。System.map應當放在使用它的軟件能 夠找到它的地方。執行:man klogd可知,如果沒有將System.map作爲一個變量的位置給klogd,那麼它將按照下面的順序,在三個地方查找System.map:

 

/boot/System.map

 

/System.map

 

/usr/src/linux/System.map

 

System.map也有版本信息,klogd能夠智能地查找正確的映象(map)文件。

 

作者:IHH

 

vmlinuz,Initrd和system.map簡介:

Vmlinuz:就不說了,這是編譯出來的壓縮了的內核文件。

Initrd簡介:

initrdlinux在系統引導過程中使用的一個臨時的根文件系統,用來支持兩階段的引導過程。

直白一點,initrd就是一個帶有根文件系統的虛擬RAM盤,裏面包含了根目錄‘/’,以及其他的目錄,比如:bindevprocsbinsyslinux啓動時必須的目錄,以及在bin目錄下加入了一下必須的可執行命令。

PC或者服務器linux內核使用這個initrd來掛載真正的根文件系統,然後將此initrd從內存中卸掉,這種情況下initrd其實就是一個過渡使用的東西。 在現在的許多簡單嵌入式linux中一般是不卸載這個initrd的,而是直接將其作爲根文件系統使用,在這之前就需要把所需要的程序,命令還有其它文件都安裝到這個文件系統中。其實現在的大多數嵌入式系統也是有自己的磁盤的,所以,initrd在現在大多數的嵌入式系統中也和一般的linux中的作用一樣只是起過渡使用。

Initrd的引導過程:第二階段引導程序,常用的是grub將內核解壓縮並拷貝到內存中,然後內核接管了CPU開始執行,然後內核調用init()函數,注意,此init函數並不是後來的init進程!!!然後內核調用函數initrd_load()來在內存中加載initrd根文件系統。Initrd_load()函數又調用了一些其他的函數來爲RAM磁盤分配空間,並計算CRC等操作。然後對RAM磁盤進行解壓,並將其加載到內存中。現在,內存中就有了initrd的映象。

然後內核會調用mount_root()函數來創建真正的根分區文件系統,然後調用sys_mount()函數來加載真正的根文件系統,然後chdir到這個真正的根文件系統中。

最後,init函數調用run_init_process函數,利用execve來啓動init進程,從而進入init的運行過程。

system.map簡介:

內核符號映射表,顧名思義就是將內核中的符號(也就是內核中的函數)和它的地址能聯繫起來的一個列表。是所有符號及其對應地址的一個列表。之所以這樣就使爲了用戶編程方便,直接使用函數符號就可以了,而不用去記要使用函數的地址。

當你編譯一個新內核時,原來的System.map中的符號信息就不正確了。隨着每次內核的編譯,就會產生一個新的 System.map文件,並且需要用該文件取代原來的文件。

 

System.map文件的作用

 

有關System.map文件的信息好象很缺乏。其實它一點也不神祕,並且在整個事情當中它並不象看上去那麼得重要。但是由於缺乏必要的文檔說明,使其顯得比較神祕。它就象耳垂,我們每個人都有,但卻不知道是幹什麼用的。本網頁就是用來說明這個問題的。

注意,我並不會是百分之一百正確的。例如,一個系統很可能沒有/proc文件系統支持,但是大多數系統肯定有。這裏我假定你是“隨大流的”,並有一個典型配置的系統。

某些有關內核出錯(oops)的闡述來自於Alessandro Rubini的“Linux設備驅動程序” 一書,我是從其中學到大部分內核編程知識的。
什麼是符號(Symbols)?

在編程中,一個符號(symbol)是一個程序的創建塊:它是一個變量名或一個函數名。 正如你自己編制的程序一樣,內核具有各種符號也是不應該感到驚奇的。當然,區別在 於內核是一非常複雜的代碼塊,並且含有許多、許多的全局符號。


內核符號表(Kernel Symbol Table)是什麼東西?

內核並不使用符號名。它是通過變量或函數的地址(指針)來使用變量或函數的,而 不是使用size_t BytesRead,內核更喜歡使用(例如)c0343f20來引用 這個變量。

而另一方面,人們並不喜歡象c0343f20這樣的名字。我們跟喜歡使用象 size_t BytesRead這樣的表示。通常,這並不會帶來什麼問題。內核主要 是用C語言寫成的,所以在我們編程時編譯器/連接程序允許我們使用符號名,並且使 內核在運行時使用地址表示。這樣大家都滿意了。

然而,存在一種情況,此時我們需要知道一個符號的地址(或者一個地址對應的 符號)。這是通過符號表來做到的,與gdb能夠從一個地址給出函數名(或者給出一個 函數名的地址)的情況很相似。符號表是所有符號及其對應地址的一個列表。這裏是 一個符號表例子:

c03441a0 B dmi_broken
c03441a4 B is_sony_vaio_laptop
c03441c0 b dmi_ident
c0344200 b pci_bios_present
c0344204 b pirq_table
c0344208 b pirq_router
c034420c b pirq_router_dev
c0344220 b ascii_buffer
c0344224 b ascii_buf_bytes


你可以看出名稱爲dmi_broken的變量位於內核地址c03441a0處。


什麼是System.map文件?

有兩個文件是用作符號表的:

1. /proc/ksyms
2. System.map

這裏,你現在可以知道System.map文件是幹什麼用的了。

每當你編譯一個新內核時,各種符號名的地址定會變化。

/proc/ksyms 是一個 "proc文件" 並且是在內核啓動時創建的。實際上 它不是一個真實的文件;它只是內核數據的簡單表示形式,呈現出象一個磁盤文件似 的。如果你不相信我,那麼就試試找出/proc/ksyms的文件大小來。因此, 對於當前運行的內核來說,它總是正確的..

然而,System.map卻是文件系統上的一個真實文件。當你編譯一個新內核時,你原 來的System.map中的符號信息就不正確了。隨着每次內核的編譯,就會產生一個新的 System.map文件,並且需要用該文件取代原來的文件。


什麼是一個Oops?

在自己編制的程序中最常見的出錯情況是什麼?是段出錯(segfault),信號11。

Linux內核中最常見的bug是什麼?也是段出錯。除此,正如你想象的那樣,段出 錯的問題是非常複雜的,而且也是非常嚴重的。當內核引用了一個無效指針時,並不 稱其爲段出錯 -- 而被稱爲"oops"。一個oops表明內核存在一個bug,應該總是提出 報告並修正該bug。

請注意,一個oops與一個段出錯並不是一回事。你的程序並不能從段出錯中恢復 過來,當出現一個oops時,並不意味着內核肯定處於不穩定的狀態。Linux內核是非常 健壯的;一個oops可能僅殺死了當前進程,並使餘下的內核處於一個良好的、穩定的 狀態。

一個oops並非是內核死循環(panic)。在內核調用了panic()函數後,內核就不能 繼續運行了;此時系統就處於停頓狀態並且必須重啓。如果系統中關鍵部分遭到破壞 那麼一個oops也可能會導致內核進入死循環(panic)。例如,設備驅動程序中 出現的oops就幾乎不會導致系統進行死循環。

當出現一個oops時,系統就會顯示出用於調試問題的相關信息,比如所有CPU寄存器 中的內容以及頁描述符表的位置等,尤其會象下面那樣打印出EIP(指令指針)的內容:

EIP: 0010:[<00000000>]
Call Trace: []



一個Oops與System.map文件有什麼關係呢?

我想你也會認爲EIP和Call Trace所給出的信息並不多,但是重要 的是,對於內核開發人員來說這些信息也是不夠的。由於一個符號並沒有固定的地址, c010b860可以指向任何地方。

爲了幫助我們使用oops含糊的輸出,Linux使用了一個稱爲klogd(內核日誌後臺程序)的 後臺程序,klogd會截取內核oops並且使用syslogd將其記錄下來,並將某些象c010b860 的信息轉換成我們可以識別和使用的信息。換句話說,klogd是一個內核消息記錄器(logger), 它可以進行名字-地址之間的解析。一旦klogd開始轉換內核消息,它就使用手頭的記錄器, 將整個系統的消息記錄下來,通常是使用syslogd記錄器。

爲了進行名字-地址解析,klogd就要用到System.map文件。我想你現在 知道一個oops與System.map的關係了。

深入說明: 其實klogd會執行兩類地址解析活動。

* 靜態轉換,將使用System.map文件。
* 動態轉換,該方式用於可加載模塊,不使用System.map,因此與本討論沒有關係,但我仍然對其加以簡單說明。

Klogd動態轉換

假設你加載了一個產生oops的內核模塊。於是就會產生一個oops消息,klogd就會截獲它,並發現該oops發生在d00cf810處。由於該地址 屬於動態加載模塊,因此在System.map文件中沒有對應條目。klogd將會在其中尋找並會毫無所獲,於是斷定是一個可加載模塊產生了oops。此 時klogd就會向內核查詢該可加載模塊輸出的符號。即使該模塊的編制者沒有輸出其符號,klogd也起碼會知道是哪個模塊產生了oops,這總比對一個 oops一無所知要好。

還有其它的軟件會使用System.map,我將在後面作一說明。


System.map應該位於什麼地方?

System.map應該位於使用它的軟件能夠尋找到的地方,也就是說,klogd會在什麼地方尋找它。在系統啓動時,如果沒有以一個參數的形式爲klogd給出System.map的位置,則klogd將會在三個地方搜尋System.map。依次爲:

1. /boot/System.map
2. /System.map
3. /usr/src/linux/System.map

System.map 同樣也含有版本信息,並且klogd能夠智能化地搜索正確的map文件。例如,假設你正在運行內核2.4.18並且相應的map文件位於 /boot/System.map。現在你在目錄/usr/src/linux中編譯一個新內核2.5.1。在編譯期間,文件 /usr/src/linux/System.map就會被創建。當你啓動該新內核時,klogd將首先查詢 /boot/System.map,確認它不是啓動內核正確的map文件,就會查詢 /usr/src/linux/System.map, 確定該文件是啓動內核正確的map文件並開始讀取其中的符號信息。

幾個注意點:

* 在2.5.x系列內核的某個版本,Linux內核會開始untar成linux-version,而非只是linux (請舉手表決 -- 有多少人一直等待着這樣做?)。我不知道klogd是否已經修改爲在/usr/src/linux-version/System.map中搜索。 TODO:查看klogd源代碼。
* 在線手冊上對此也沒有完整描述,請看:

# strace -f /sbin/klogd | grep 'System.map'
31208 open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2


顯然,不僅klogd在三個搜索目錄中尋找正確版本的map文件,klogd也同樣知道尋找名字爲 "System.map" 後加"-內核版本",象 System.map-2.4.18. 這是klogd未公開的特性。

有一些驅動程序將使用System.map來解析符號(因爲它們與內核頭連接而非glibc庫等),如果沒有System.map文件,它們將不能正確地 工作。這與一個模塊由於內核版本不匹配而沒有得到加載是兩碼事。模塊加載是與內核版本有關,而與即使是同一版本內核其符號表也會變化的編譯後內核無關。

還有誰使用了System.map?

不要認爲System.map文件僅對內核oops有用。儘管內核本身實際上不使用System.map,其它程序,象klogd,lsof,

satan# strace lsof 2>&1 1> /dev/null | grep System
readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23


ps,

satan# strace ps 2>&1 1> /dev/null | grep System
open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6


以及其它許多軟件,象dosemu,需要有一個正確的System.map文件。

如果我沒有一個好的System.map,會發生什麼問題?

假設你在同一臺機器上有多個內核。則每個內核都需要一個獨立的 System.map文件!如果所啓動的內核沒有對應的System.map文件,那麼你將定期地看到這樣一條信息:

System.map does not match actual kernel (System.map與實際內核不匹配)

不是一個致命錯誤,但是每當你執行ps ax時都會惱人地出現。有些軟件,比如dosemu,可能不會正常工作。最後,當出現一個內核oops時,klogd或ksymoops的輸出可能會不可靠。

我如何對上述情況進行補救?

方法是將你所有的System.map文件放在目錄/boot下,並使用內核版本號重新對它們進行命名。假設你有以下多個內核:

* /boot/vmlinuz-2.2.14
* /boot/vmlinuz-2.2.13

那麼,只需對應各內核版本對map文件進行改名,並放在/boot下,如:



/boot/System.map-2.2.14
/boot/System.map-2.2.13

如果你有同一個內核的兩個拷貝怎麼辦?例如:

* /boot/vmlinuz-2.2.14
* /boot/vmlinuz-2.2.14.nosound

最佳解決方案將是所有軟件能夠查找下列文件:

/boot/System.map-2.2.14
/boot/System.map-2.2.14.nosound

但是說實在的,我並不知道這是否是最佳情況。我曾經見到搜尋"System.map-kernelversion",但是對於搜索"System.map -kernelversion.othertext"的情況呢? 我不太清楚。此時我所能做的就是利用這樣一個事實:/usr/src/linux是標準map文件的搜索路徑,所以你的map文件將放在:

* /boot/System.map-2.2.14
* /usr/src/linux/System.map (對於nosound版本)

你也可以使用符號連接:

System.map-2.2.14
System.map-2.2.14.sound
System.map -> System.map-2.2.14.sound

 

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