【转载】linux2.6内核initrd机制解析
題記
很久之前就分析過這部分內容,但是那個時候不夠深入,姑且知道這么個東西存在,到底怎么用,來龍去脈咋回事就不知道了。前段時間工作上遇到了一個initrd的問題,沒辦法只能再去研究研究,還好,有點眉目,索性整理了一下。
網絡上流傳著很多關于ramdisk、initrd的各種版本的分析,我的這篇源于對他們的理解,非常感謝那些前輩的無私奉獻,要不然我們這些晚輩學起東 西來該是多么艱難呀。在這里需要特別聲明的是如果文中有引用了您的思想而沒有給出參考文獻,請您原諒我的疏忽。晚輩就是需要站在像您這種巨人的肩上,技術 才會發(fā)展,社會才會進步。
另外,由于本人水平有限,文中難免有誤解及不全面之處,煩請指正,謝謝!
歡迎轉載,但請保留原文該有的信息。
聯(lián)系方式:李枝果/lizgo lizhiguo0532@163.com 2010/10/16
目錄
第一部分:ramfs、tmpfs、rootfs、ramdisk
一、 什么是ramfs
二、 什么是tmpfs
三、 什么是rootfs
四、 什么是ramdisk
第二部分:initrd、initramfs
一、 initrd出現(xiàn)的背景
二、 initrd的種類和制作
第三部分:kernel初始化initrd代碼分析
一、 bootloader傳遞給內核的關于initrd的參數(shù)
二、 動態(tài)內存分配器slab介紹
三、 rootfs初始化
四、 內核初始化階段對initrd的處理
五、 老式塊設備的initrd的處理函數(shù)prepare_namespace()分析
附文
參考網址
第一部分:ramfs、tmpfs、rootfs、ramdisk
一、 什么是ramfs?
1. linux緩存機制
VFS(虛擬文件系統(tǒng))層屏蔽了各種真實文件系統(tǒng)的特性,提供給linux上層統(tǒng)一的接口。
通常,linux對所有文件的讀寫都會在內存在做高速緩存,當系統(tǒng)再次使用這些文件時,可以直接從內存中讀取,以提高系統(tǒng)的I/O性能。當高速緩存中的文 件被修改或者有數(shù)據(jù)寫入高速緩存時,系統(tǒng)會在適當?shù)臅r候將這些高速緩存中的數(shù)據(jù)回寫到對應的文件系統(tǒng)設備(如磁盤、flash等)中去,那么這之后這些高 速緩存的狀態(tài)就被標識為clean(可用),這樣就相當于告訴了系統(tǒng):這些高速緩存中的數(shù)據(jù)文件系統(tǒng)設備上有備份,你可以拿去另作他用。但其中的數(shù)據(jù)會保 持到VMS(Virtual Memory System)將這些高速緩存回收重新分配。
類似于頁緩存機制,目錄緩存機制也極大地加快了對目錄的訪問。
2. ramfs
ramfs是一種非常簡單的文件系統(tǒng),它直接利用linux內核已有的高速緩存機制(所以其實現(xiàn)代碼很小,也由于這個原因,ramfs特性不能通過內核配置參數(shù)屏蔽,它是內核的天然屬性),使用系統(tǒng)的物理內存,做成一個大小可以動態(tài)變化的的基于內存的文件系統(tǒng)。
ramfs工作于虛擬文件系統(tǒng)層(VFS)層,不能被格式化,可以創(chuàng)建多個,默認情況下,ramfs最多能用到內存的一半,必要時也可以使用-o maxsize = 10000(單位是KB)來更改使用的最大內存量。
ramfs沒有對應的文件系統(tǒng)設備,文件被寫入ramfs和其他文件系統(tǒng)一樣都正常分配頁緩存和目錄緩存,但是卻不可能想其他有存儲設備的文件系統(tǒng)一樣將 高速緩存中的文件回寫到存儲設備。這就意味著這些為ramfs中的文件和目錄分配的高速頁或者目錄緩存都不可能會被標記為clean(可用)狀態(tài),所以系 統(tǒng)就永遠不會釋放ramfs所占用的內存。正因為可以在ramfs下面可以一直往里寫數(shù)據(jù),直到寫滿為止,所以這種操作只有root(or trusted user)用戶才可以進行ramfs寫操作。
為了解決ramfs的缺點(沒有回寫設備)導致的種種問題,所以衍生出了tmpfs文件系統(tǒng)。
二、 什么是tmpfs?
tmpfs是ramfs的衍生物,在ramfs的基礎上增加了容量大小的限制和允許向交換
空間(swap) 寫入數(shù)據(jù)。由于增加了這兩個特性,所以普通用戶也可以使用tmpfs。
tmpfs是一種虛擬內存文件系統(tǒng),它不同于傳統(tǒng)的用塊設備形式來實現(xiàn)的ramdisk,也不同于針對物理內存的ramfs。tmpfs既可以使用物理內 存,也可以使用交換分區(qū)。在linux內核中,虛擬內存資源由物理內存和交換分區(qū)組成,這些資源由內核中的虛擬內存子系統(tǒng)來負責管理。tmpfs就是和虛 擬內存子系統(tǒng)打交道的,它向虛擬內存子系統(tǒng)請求頁來存儲文件,同linux的其他請求頁的部分一樣,不知道分配給自己的頁是在內存中還是在交換分區(qū)中。也 就是說tmpfs使用的是虛擬內存,而ramfs使用物理內存。另外tmpfs和ramfs一樣,不可以被格式化,同時大小也是不固定的,可以使用-o size =32m或者(1g)來修改。
另外,tmpfs可以將當前不需要使用的頁寫入到交換空間。同時由于其使用的虛擬內存,所以tmpfs一旦被卸載,其中的數(shù)據(jù)都會丟失。
如果需要使用tmpfs,在內核編譯的時候得選擇上:
Virtual memory filesystem support
ramfs只會在物理內存中被創(chuàng)建,而tmpfs可能在物理內存中創(chuàng)建,也可能在交換 分區(qū)中創(chuàng)建。對于想利用內存的高速IO來提高效能的應用,最好是使用ramfs。對于只是想存放臨時緩存的應用,最好使用tmpfs,以提高內存的使用率。
三、 什么是rootfs?
rootfs是一個特定的ramfs(或tmpfs,如果tmpfs被啟用)的實例,它始終存在于linux2.6的系統(tǒng)中。rootfs不能被卸載(與 其添加特殊代碼用來維護空的鏈表,不如把rootfs節(jié)點始終加入,因此便于kernel維護。rootfs是ramfs的一個空實例,占用空間極小)。 大部分其他的文件系統(tǒng)安裝于rootfs之上,然后忽略它。它是內核啟動初始化根文件系統(tǒng)。
四、 什么是ramdisk?
linux2.6版本之后都不在使用ramdisk了,2.4中還在使用。ramdisk是一種將內存
中的的一塊區(qū)域作為物理磁盤來使用的一種技術,也可以說,ramdisk是在一塊內存區(qū) 域中創(chuàng)建的塊設備,用于存放文件系統(tǒng)。對于用戶來說,可以把ramdisk與通常的硬盤分區(qū)同等對待來使用。ramdisk不適合作為長期保存文件的介 質,掉電后ramdisk的內容會消失。
為了能夠使用ramdisk 你的內核必須要支持ramdisk,即:在編譯內核時,要選中RAM disk support這一選項,會在配置文件中定義CONFIG_BLK_DEV_RAM。同時為了讓內核有能力在內核加載階段就能裝入ramdisk,并運行 其中的內容,要選中initial RAM disk(initrd) support 選項,會在配置文件中定義CONFIG_BLK_DEV_INITRD。
ramdisk的大小是固定的,安裝在其上的文件系統(tǒng)大小也是固定的。ramdisk在使用的時候,這個假的塊設備和高速緩存(頁緩存和目錄緩存)之間有 數(shù)據(jù)的拷貝,而且它還需要文件系統(tǒng)的驅動來格式化和解釋這些數(shù)據(jù)。所以,使用ramdisk不僅浪費了內存,還加重了cpu的負擔,同時也無污染了 cache,而且其所有的文件和目錄都要通過頁和目錄緩存進行訪問,這些工作ramfs都要執(zhí)行的,那么ramdisk就可以完全不需要。這是廢棄 ramdisk的理由之一。
另一個廢棄它的理由就是,回環(huán)設備的引進,而回環(huán)設備提供了一個更靈活和方便的方式(從文件而不是從大塊的內存)來創(chuàng)建一個合成塊設備。
第二部分:initrd、initramfs
一、 initrd出現(xiàn)的背景
在早期的linux系統(tǒng)中,一般只有硬盤或者軟盤被用來作為linux根文件系統(tǒng)的存儲設備,因此也就很容易把這些設備的驅動程序集成到內核中。但是現(xiàn)在 的嵌入式系統(tǒng)中可能將根文件系統(tǒng)保存到各種存儲設備上,包括scsi、sata,u-disk等等。因此把這些設備的驅動代碼全部編譯到內核中顯然就不是 很方便。
在內核模塊自動加載機制udev中,我們看到利用udevd可以實現(xiàn)內核模塊的自動加載,因此我們希望如果存儲根文件系統(tǒng)的存儲設備的驅動程序也能夠實現(xiàn) 自動加載,那就好了。但是這里有一個矛盾,udevd是一個可執(zhí)行文件,在根文件系統(tǒng)被掛載前,是不可能執(zhí)行udevd的,但是如果udevd沒有啟動, 那就無法自動加載存儲根文件系統(tǒng)設備的驅動程序,同時也無法在/dev目錄下建立相應的設備節(jié)點。
為了解決這一矛盾,于是出現(xiàn)了基于ramdisk的initrd( bootloader initialized RAM disk )。Initrd是一個被壓縮過的小型根目錄,這個目錄中包含了啟動階段中必須的驅動模塊,可執(zhí)行文件和啟動腳本,也包括上面提到的udevd(實現(xiàn) udev機制的demon)。當系統(tǒng)啟動的時候,bootloader會把initrd文件讀到內存中,然后把initrd文件在內存中的起始地址和大小 傳遞給內核。內核在啟動初始化過程中會解壓縮initrd文件,然后將解壓后的initrd掛載為根目錄,然后執(zhí)行根目錄中的/init腳本(cpio格 式的initrd為/init,而image格式的initrd<也稱老式塊設備的initrd或傳統(tǒng)的文件鏡像格式的initrd>為 /initrc),您就可以在這個腳本中運行initrd文件系統(tǒng)中的udevd,讓它來自動加載realfs(真實文件系統(tǒng))存放設備的驅動程序以及在 /dev目錄下建立必要的設備節(jié)點。在udevd自動加載磁盤驅動程序之后,就可以mount真正的根目錄,并切換到這個根目錄中來。
這里只是個簡單的描述,后面慢慢分析吧。
二、 initrd的種類和制作
initrd總的來說目前有兩種格式:image格式和cpio格式。image格式也叫文件系統(tǒng)鏡像文件(老式塊設備的initrd文件),主要在 linux 2.4內核中使用流行;在linux 2.5內核開始引入initramfs技術,initramfs實際上已經克服了imgae-initrd的缺點,本質上也是cpio格式的 initrd,只不過是和內核編譯到了一個image文件,放在了.init.ramfs段內;到linux2.6的內核支持兩種格式的initrd,即 image-initrd和cpio-initrd,此時的cpio-initrd文件已不再編譯進內核而單獨成一文件,使用cpio工具生成。后邊詳 述。
在介紹initrd的制作之前,先了解下面的幾個命令作用:
dd:用指定大小的塊拷貝一個文件,并在拷貝的同時進行指定的轉換。
1. if=文件名:輸入文件名,缺省為標準輸入。即指定源文件。< if=input file >
注意/dev/zero設備,這個設備是可以源源不斷地提供0的設備,用來初始化
2. of=文件名:輸出文件名,缺省為標準輸出。即指定目的文件。< of=output file >
3.bs=bytes:同時設置讀入/輸出的塊大小為bytes個字節(jié)。
4. count=blocks:僅拷貝blocks個塊,塊大小等于ibs指定的字節(jié)數(shù)。
還有其他更詳細的參數(shù)用法參考:dd命令-詳解
http://blog.csdn.net/liumang_D/archive/2009/02/17/3899462.aspx
mkfs.ext2:等同于mke2fs,建立ext2文件系統(tǒng)。
mke2fs [-cFMqrSvV][-b <區(qū)塊大小>][-f <不連續(xù)區(qū)段大小>][-i <字節(jié)>][-N ][-l <文件>][-L <標簽>][-m <百分比值>][-R=<區(qū)塊數(shù)>][ 設備名稱][區(qū)塊數(shù)]
1. -F 不管指定的設備為何,強制執(zhí)行mke2fs。
2. -m<百分比值> 指定給管理員保留區(qū)塊的比例,預設為5%。
3. 其余參數(shù)默認即可,參考: mkfs.ext2 命令-詳解(網絡搜索)
mount:掛在命令.
mount [-t vfstype] [-o options] device dir
1.-t vfstype 指定文件系統(tǒng)的類型,通常不必指定。mount 會自動選擇正確的類型。
2.-o options 主要用來描述設備或檔案的掛接方式。常用的參數(shù)有:
loop:用來把一個文件當成硬盤分區(qū)掛接上系統(tǒng)
ro:采用只讀方式掛接設備
rw:采用讀寫方式掛接設備
iocharset:指定訪問文件系統(tǒng)所用字符集
3.詳細參數(shù)參考:mount命令詳解(網絡搜索)
1)、image-initrd制作
我們可以通過下面的方法來制作一個老式基于塊設備的image-initrd文件:
# dd if=/dev/zero of=initrd.img bs=4k count=1024
# mkfs.ext2 -F –m 0 initrd.img
# sudo mkdir /mnt/ramdisk
# mount -o loop initrd.img /mnt/ramdisk
# cp -r /opt/filesystem /mnt/ramdisk
# umount /mnt
# gzip -9 initrd.img
通過上面的命令,我們制作了一個4M的initrd,其中/opt/filesystem就是我們用busybox制作的一個根目錄。最后我們得到一個名為initrd.img.gz的壓縮比最大的壓縮文件。更加詳細的過程,參考:ramdisk制作
http://blog.csdn.net/epicyong333/archive/2008/12/24/3590619.aspx
http://blog.csdn.net/vcvbve/archive/2010/02/27/5329384.aspx
利用image-initrd可以使內核在啟動階段可以順利地完成各種存儲介質的驅動的加載和realfs文件系統(tǒng)的掛載,然而image-initrd存在以下缺點:
1.image-initrd大小是固定的,例如上面的壓縮之前的initrd大小是4M(4k*1024),假設您的根目錄(上例中的/opt /filesystem)總大小僅僅是1M,它仍然要占用4M的空間。如果您在dd階段指定大小為1M,后來發(fā)現(xiàn)不夠用的時候,必須按照上面的步驟重新來 一次。
2.image-initrd是一個虛擬的塊設備,您可是使用fdisk對這個虛擬塊設備進行分區(qū)。在內核中,對塊設備的讀寫還要經過緩沖區(qū)管理模塊,也 就是說,當內核讀取initrd中的文件內容時,緩沖區(qū)管理層會認為下層的塊設備速度比較慢,因此會啟用預讀和緩存功能。這樣initrd本身就在內存 中,同時塊設備緩沖區(qū)管理層還會保存一部分內容。
2)、initramfs
為了避免上述缺點,在linux2.5中出現(xiàn)了initramfs,它的作用和initrd類似,只是和內核編譯成一個文件(該initramfs是經過 gzip壓縮后的cpio格式的數(shù)據(jù)文件),該cpio格式的文件被鏈接進了內核中特殊的數(shù)據(jù)段.init.ramfs上,其中全局變量 __initramfs_start和__initramfs_end分別指向這個數(shù)據(jù)段的起始地址和結束地址。內核啟動時會對.init.ramfs段 中的數(shù)據(jù)進行解壓,然后使用它作為臨時的根文件系統(tǒng)。
要制作這樣的內核,我們只需要在make menuconfig中配置以下選項就可以了:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/opt/filesystem) Initramfs source file(s)
其中/opt/filesystem就是我們的小型根目錄,這里可以使一個現(xiàn)成的gzip壓縮的cpio文件,也可以使一個目錄,更可以是txt格式的配置文件,如下面的實例:
dir /dev 755 0 0
nod /dev/console 644 0 0 c 5 1
nod /dev/loop0 644 0 0 b 7 0
dir /bin 755 1000 1000
slink /bin/sh busybox 777 0 0
file /bin/busybox initramfs/busybox 755 0 0
dir /proc 755 0 0
dir /sys 755 0 0
dir /mnt 755 0 0
file /init initramfs/init.sh 755 0 0
如果指定的是一個目錄而不是一個像這樣的配置文件,內核在編譯的時候會從指定的目錄創(chuàng)建一個配置文件(usr/Makefile調用scripts /gen_initramfs_list.sh來生成),作為usr/gen_init_cpio.c文件的輸入,最后生成一個usr /initramfs_data.cpio.gz文件,通過usr/initramfs_data.S包含到.init.ramfs段中去,最后生成 zImage。
3)、cpio-initrd
這里所說的initrd格式和編譯進內核的initramfs格式是一樣的,都是cpio,也
被稱為外部initramfs,它是獨立存在的,需要bootloader將其加載進內存特定地址,然后將地址和大小傳遞給內核,在內核的初始化階段來進行相應的處理。
這種initrd可以使用cpio命令來實現(xiàn),如下:
# sudo find /opt/filesystem/ -depth | cpio -c -o > initrd.img
# gzip -9 initrd.img
這樣得到的initrd就是cpio格式的,而且這個文件的大小是可變的,意思就是根據(jù)
你的filesystem的大小而變化,不會像前面的image格式的initrd那樣大小固定了。當然我覺得前面的initramfs的大小也是可變的了。
關鍵詞:
ramfs、tmpfs、rootfs、ramdisk
image-initrd、initramfs、cpio-initrd
第三部分:kernel初始化initrd代碼分析
arm平臺、linux2.6.29
一、 bootloader傳遞給內核的關于initrd的參數(shù)
root=
該參數(shù)告訴內核以那個設備作為根文件系統(tǒng),通常由如下形式:
root=/dev/ramx
root=/dev/mtdblockx rw rootfstype = jffs2
root=31:0x rootfstype = jffs2
root=/dev/nfs nfsroot=serverip:nfs_dir
第一種在使用ramdisk和image-initrd的時候是必須的,initramfs和cpio-initdrd因為使用的是系統(tǒng)內一直存在的rootfs,所以該參數(shù)可以省略。
第三、四種基本通用,只是,第四種使用的設備號和分區(qū)號來指定,比如root=/dev/mtdblock4 等價于 root=31:04,意思就是使用nand設備上的第四分區(qū)來作為根文件系統(tǒng), 同時用rootfstype指定文件系統(tǒng)類型。
最后一種就是掛在nfs文件系統(tǒng)了,除了要指定root類型之外,還需要指定服務器上的某個目錄來作為掛載的根目錄。
initrd=
應遵循這種格式:initrd = addr, size ,其中addr表示initrd在內存中的位置,size表示initrd的大小。如initrd = 0xa060000,0x2c6b
另外一種傳遞該參數(shù)的方法就是:使用tag參數(shù)來傳遞,uboot中使用的是函數(shù)setup_initrd_tag()函數(shù)來生成對應的tag參數(shù)。
需要注意的有兩點:
1. 內核初始化先解析tag中的initrd地址和大小參數(shù),然后再去會去執(zhí)行cmdline解析initrd=參數(shù),如果這兩個地方都指定了話,那么最終取 決于cmdline中initrd=所指定的參數(shù),因為這兩種方法傳遞的結果都是被內核初始化階段的全局變量phys_initrd_start和 phys_initrd_size中(這是物理地址),然后再轉換成虛擬地址存儲在全局變量initrd_start和initrd_size中,供后續(xù) 使用。
2. bootloader傳遞的大小應該是initrd的實際大小,不能多傳更不能少傳,否則,gunzip解壓縮的時候會出錯。
init=
init指定的是內核啟起來后,進入系統(tǒng)中運行的第一個腳本,一般init=/linuxrc(image-initrd使用)和init=/init(initramfs和cpio-initrd使用)。
ramdisk使用rdinit=/xxx來指定。
小結:( 以下結論均在initrd的地址和大小均通過tag參數(shù)傳遞)
通過后面的代碼分析,可以做如下總結:
1. 使用initramfs和cpio-initrd
不需要root=xxx參數(shù),而init=/init是必須的
2. 使用image-initrd(記得打開內核對ramdisk和initrd的支持)
init=/initrc,下面是傳遞不同root=xxx的情況
a. root=/dev/ramx,將image-initrd作為真實的文件系統(tǒng)在使用
b. root=/dev/mtdblockx rw rootfstype = jffs2,這樣image-initrd只是作為一個中間過渡的文件系統(tǒng),最終還是需要依靠內核1號線程將真實的文件系統(tǒng)掛上來。
(b中的root= 只是舉個例子,真實系統(tǒng)放在什么設備上沒關系,主要的是需要在image-initrd文件系統(tǒng)中將真是文件系統(tǒng)存在的設備的驅動程序加載上就好了)
二、 動態(tài)內存分配器slab介紹
linux的每種動態(tài)內存管理都使用一種基于堆的分配策略。
原始的分配策略是這樣的,在這種方法中,大塊內存(稱為 堆)用來為用戶定義的目的提 供內存。當用戶需要一塊內存時,就請求給自己分配一定大小的內存。堆管理器會查看可用內存的情況(使用特定算法)并返回一塊內存。搜索過程中使用的一些算 法有 first-fit(在堆中搜索到的第一個滿足請求的內存塊 )和 best-fit(使用堆中滿足請求的最合適的內存塊)。當用戶使用完內存后,就將內存返回給堆。這種基于堆的分配策略的根本問題是碎片 (fragmentation)。當內存塊被分配后,它們會以不同的順序在不同的時間返回。這樣會在堆中留下一些洞,需要花一些時間才能有效地管理空閑內 存。這種算法通常具有較高的內存使用效率(分配需要的內存),但是卻需要花費更多時間來對堆進行管理。
大約在2.0內核之前,出現(xiàn)了另外一種管理機制稱為 buddy memory allocation(伙伴內存分配系統(tǒng)),是一種更快的內存分配技術,它將內存 劃分為 2 的冪次方個分區(qū),并使用 best-fit 方法來分配內存請求。當用戶釋放內存時,就會檢查 buddy 塊,查看其相鄰的內存塊是否也已經被釋放。如果是的話,將合并內存塊以最小化內存碎片。這個算法的時間效率更高,但是由于使用 best-fit 方法的緣故,會產生內存浪費。
后來在2.2內核之后,引入了一種全新的動態(tài)內存分配器slab分配器,slab分配器的概念首先在Sun Microsystem 的SunOs 5.4操作系統(tǒng)中得以實現(xiàn),圍繞對象緩存進行的。slab層會把不同的對象劃分為所謂的高速緩存(cache)組,其中每個高速緩存都存放不同類型的對 象。每種對象類型對應一個高速緩存。例如,一個高速緩存用于存放進程描述符(task_struct),而另一個高速緩存存放索引節(jié)點對象(struct inond)。值得一提的是,kmalloc接口建立在slab層之上,使用了一組通用高速緩存。
下圖給出了slab 結構的高層組織結構。在最高層是cache_chain,這是一 個 slab 緩存的鏈接列表。這對于 best-fit 算法非常有用,可以用來查找最適合所需要的分配大小的緩存(遍歷列表)。cache_chain 的每個元素都是一個 kmem_cache 結構的引用(稱為一個 cache)。它定義了一個要管理的給定大小的對象池。
圖貼不上,《linux內核設計與實現(xiàn)》書上有
這些高速緩存又劃分為slab,slab又由一個或多個物理上連續(xù)的頁組成,每一個物理頁被劃分為特定的對象。一般情況下,slab也僅僅由一頁組成。
slab分配器的接口:
創(chuàng)建一個新的高速緩存,它通常在內核初始化時或者首次加載內核模塊時執(zhí)行。
kmem_cache_t *kmem_cache_creat( const char *name, size_t size,
size_t align,unsigned long flags;
void (*ctor)(void*, struct kmem_cache *, unsigned long),
void (*dtor)(void*, struct kmem_cache *, unsigned long));
銷毀一個高速緩存
int kmem_cache_destroy(kmem_cache_t *cachep);
創(chuàng)建了高速緩存之后,就可以通過下面的函數(shù)從中獲取對象
void *kmem_cache_alloc(kmem_cache_t *cachep,int flags);
如果高速緩存中所有的slab中都沒有空閑的對象,那么slab層必須通過kmem_getpages()獲取新的頁,flags值傳遞給__get_free_pages()。
下面的函數(shù)是釋放對象,將其返還給原先的slab
void kmem_cache_free(kmem_cache_t *cachep,void *objp);
更加詳細的參看:《linux內核設計與實現(xiàn)》 robrt love著
另外值得提到的是在linux2.6.22之后,引進了slub分配器,該分配器保留了slab的基本思想:每個緩沖區(qū)由多個小的slab組成,每個 slab包含固定數(shù)目的對象。slub分配器簡化了kmem_cache,slab等相關的管理數(shù)據(jù)結構,摒棄了slab分配器中眾多的隊列概念,并針對 多處理器、NUMA系統(tǒng)進行優(yōu)化,從而提高了性能和可擴展性并降低了內存的浪費。為了保證內核其它模塊能夠無縫遷移到slub分配器,slub還保留了原 有slab分配器所有的接口 API 函數(shù)。
http://qgjie456.blog.163.com/blog/static/3545136720090622056716/
三、 rootfs初始化
該部分代碼主要在init/main.c中的start_kernel()函數(shù)中的vfs_caches_init_early()和 vfs_caches_init(num_physpages)來實現(xiàn),其中num_physpages全局變量在mem_init()函數(shù)中初始化,代 表物理內存的總頁數(shù),
詳細的代碼分析見:rootfs_initialize.c
vfs_caches_init_early():
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
總結:
inode_hashtable就是一數(shù)組,每個數(shù)組元素都是一個struct hlist_head結構體,該結構體實際上就只有一個指向struct hlist_node對象的指針first,所有指針在arm體系上都是4字節(jié),所以這個函數(shù)實際上只是為數(shù)組inode_hashtable分配了一段 內存空間,該數(shù)組是實際上就是一個指針數(shù)組,每個元素都是指向struct hlist_node對象的指針。然后最后將這些指針全部置成NULL。至于后續(xù)怎么使用還沒涉及到。
同樣類似地,對于Dentry cache哈希表(dentry_hashtable)也是一樣的,只是創(chuàng)建了一個指針數(shù)組而已。
http://qgjie456.blog.163.com/blog/static/3545136720081126102615685/
四、 內核初始化階段對initrd的處理
start_kernel()函數(shù)最后會調用rest_init(),在該函數(shù)中會創(chuàng)建兩個內核線程kernel_init和 kthreadd(2.6.14內核中只創(chuàng)建了一個內核線程init,但是所做工作基本一樣),內核線程創(chuàng)建后,原來的系統(tǒng)0號進程進入idle狀態(tài)。
對initrd的處理函數(shù)主要有兩個:populate_rootfs()和prepare_namespace(),針對不同的格式,處理情況各不相同。
這里注意一點的是:populate_rootfs()函數(shù)在2.6.14和2.6.29中執(zhí)行的位置不一樣。2.6.14中在 do_basic_setup()函數(shù)執(zhí)行之前,而2.6.29版本中將其移植do_basic_setup()函數(shù)中執(zhí)行,怎么講呢?高版本中將 populate_rootfs()移到do_initcalls()中執(zhí)行,也就是編譯的時候將函數(shù)放在了.initcallrootfs.init段 中。
CONFIG_BLK_DEV_RAM – 該宏被定義,表明內核支持ramdisk,make menuconfig中需要選中RAM disk support一項。
CONFIG_BLK_DEV_INITRD – 該宏被定義,表明內核有能力在內核加載階段就能裝入RAMDISK,并運行其中的內容,make menuconfig中需要選中initial RAM disk(initrd) support一項。
下面是各種initrd的不同稱呼:
image-initrd : 老式塊設備的initrd,也叫傳統(tǒng)的文件鏡像格式的initrd
initramfs : 和內核編譯到一起的cpio格式的initrd
cpio-initrd : cpio格式的獨立文件的initrd
populate_rootfs函數(shù)分析
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start, 0);
/***
編譯進內核的initramfs位于段.init.ramfs中,用全局變量__initramfs_start和__initramfs_end分別指向這個數(shù)據(jù)段的內存起始地址和結束地址(虛擬地址)
所以這里了如果__initramfs_end - __initramfs_start等于0,那么unpack_to_rootfs函數(shù)不會做任何事情,直接退出。系統(tǒng)認為該initrd不是initramfs文件,所以需要到后面去處理。
***/
if (err)
panic(err);
// 判斷是否加載了initrd,bootlaoder會將initrd加載到內存地址initrd_start,// 前面已經說到了如何給內核傳遞這些參數(shù)
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
// CONFIG_BLK_DEV_RAM和CONFIG_BLK_DEV_RAM經常是同時定義
int fd;
printk(KERN_INFO "checking if image is initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 1);
/***
判斷加載的是不是cpio-initrd。實際上 unpack_to_rootfs有兩個功能一個是釋放cpio包,另一個就是判斷是不是cpio包, 這是通過最后一個參數(shù)來區(qū)分的, 0:釋放 1:查看。
***/
if (!err) {
// 如果是cpio-initrd則利用函數(shù)unpack_to_rootfs將其內容釋放
// 到rootfs中
printk(" it is\n");
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
free_initrd(); // 釋放掉原來存放cpio-initrd的內存空間
return 0;
}
/***
如果執(zhí)行到這里,說明這是舊的塊設備格式的initrd。那么首先在前面掛載的根目錄rootfs上創(chuàng)建一個initrd.image文件,再把 initrd_start到initrd_end的內容寫入到/initrd.image中,最后釋放initrd占用的內存空間(它的副本已經保存到 /initrd.image中了。)。
***/
printk("it isn't (%s); looks like an initrd\n", err);
fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
#else
// 如果沒有定義支持image-initrd的宏,那么直接通過下面的代碼來處理cpio-initrd
// 實際上和上面處理cpio-initrd是一樣的
printk(KERN_INFO "Unpacking initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
if (err)
panic(err);
printk(" done\n");
free_initrd();
#endif
}
return 0;
}
rootfs_initcall(populate_rootfs);
上面的populate_rootfs()執(zhí)行完后,如果是initramfs和cpio-initrd的話,都已經將他們釋放到了前面初始化完成的 rootfs中去了,那么根目錄下肯定會出現(xiàn)init文件。而如果是image-initrd,那么只會在rootfs根目錄下出現(xiàn)一個 initrd.image文件。
所以就下來的處理就更加不一樣了。
static int __init kernel_init(void * unused)
{
…
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
// cmdline中存在rdinit=xxx時,ramdisk_execute_command才不為NULL
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/***
這里嘗試訪問ramdisk_execute_command,默認為/init,如果訪問失敗,說明根目錄上不存在這個文件。于是調用prepare_namespace(),進一步檢查是不是舊的塊設備的initrd。
(在這種情況下,還是一個塊設備鏡像文件/initrd.image,所以訪問/init文件失敗。)。
如果是initramfs或者cpio-initrd就會直接執(zhí)行init_post()函數(shù),然后在后面執(zhí)行/init文件
***/
init_post();
return 0;
}
五、老式塊設備的initrd的處理函數(shù)prepare_namespace()分析
cmdline傳了里的參數(shù)需要包括:
root=/dev/mtdblockx rw 或者{root=/dev/ramx } init=/initrc
謹記root=指定的設備是最終的真實文件系統(tǒng)的。在處理image-initrd的時候指定了它,那么系統(tǒng)就會認為你這個initrd就是我的真實文件系統(tǒng)了,會跳過很多步驟不執(zhí)行的。
void __init prepare_namespace(void)
{
int is_floppy;
if (root_delay) {
printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}
wait_for_device_probe();
md_run_setup();
if (saved_root_name[0]) {
// 如果cmdline有傳遞root=xxx,那么這里就會將其設備號保存下來
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3) ||
!strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
/***
將存儲真實文件系統(tǒng)的設備的設備號保存到ROOT_DEV中,如果cmdline傳遞的是/dev/ramx話,這里的ROOT_DEV = Root_RAM0,如果不是,那么是什么就是什么,后面會根據(jù)這個做出不同的事情
***/
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
/***
saved_root_name全局數(shù)組保存的是由命令行傳進來的root=的值,比如你傳遞了root=/dev/ramx或者root=/dev /mtdblockx rw,那么上面的if條件成立,那么就通過name_to_dev_t(root_device_name)函數(shù)獲得該設備的設備號ROOT_DEV。否 則就跳過該段不執(zhí)行。
***/
if (initrd_load()) /*** 詳見后面注釋 ***/
/***
加載老式塊設備的initrd,這里也分兩種情況,一種是將該initrd作為真實文件系統(tǒng)返回1另外一種就是作為一種過渡的文件系統(tǒng)而已,此時返回0。
***/
goto out;
/***
如果要使用老式塊設備的initrd作為真實的文件系統(tǒng)的話,需要在cmdline傳入一下參數(shù):root=/dev/ramx init=/initrc,同時在內核編譯的時候需要打開支持ramdisk和initrd
***/
/* wait for any asynchronous scanning to complete */
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}/* 如果root=正常傳遞,該if不會成立 */
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
/***
如果cmdline傳遞了正確的root=/dev/ramx的話,mount_root()中直接創(chuàng)建一個Root_RAM0類型的/dev/root 節(jié)點(前面initrd_load()中將image-initrd已經導入到了Root_RAM0ramdisk中了),所以mount_root() 中就可以直接使用。
***/
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
mount_root(); // 將ROOT_DEV設備上的真實文件系統(tǒng)mount到rootfs的/root目錄中
out:
// 注意:上面的過程都已經將真實文件系統(tǒng)掛載到了/root(這里的/還是rootfs),
// 并且當前目錄為/root
sys_mount(".", "/", NULL, MS_MOVE, NULL);
// mount當前目錄為根目錄,覆蓋掉原來的rootfs
sys_chroot(".");
// 切換當前目錄為程序執(zhí)行所參考的根目錄位置,至此,真實文件系統(tǒng)
// 掛載完畢
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
int __init initrd_load(void)
{
if (mount_initrd) {
create_dev("/dev/ram", Root_RAM0);
// 建立一個Root_RAM0類型的/dev/ram設備節(jié)點,實際上Root_RAM0設備就是
// 一ramdisk
if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
// rd_load_image()將文件initrd.image加載進了/dev/ram0中去了
sys_unlink("/initrd.image"); // 刪除文件initrd.image
handle_initrd(); // handle_initrd()函數(shù)負責對initrd進行具體的處理
return 1; /* image-initrd 作為中間過渡的文件系統(tǒng) */
}
/***
/initrd.image文件保存的就是image-initrd,rd_load_image函數(shù)執(zhí)行具體的加載操作,
將image-nitrd的文件內容釋放到設備類型為Root_RAM0的ramdisk中去(節(jié)點名為ram)。判斷 ROOT_DEV!=Root_RAM0的含義是(ROOT_DEV不能=0,否則mount的時候會出錯),正如前面所說,如果你在 bootloader里配置了root=/dev/ramx,則實際上真正的根設備就是這個initrd了,所以就不把它作為initrd處理 ,而是作為真實文件系統(tǒng)來處理。
***/
}
sys_unlink("/initrd.image");// 刪除文件initrd.image
return 0; /* image-initrd作為真實的文件系統(tǒng) */
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
static void __init handle_initrd(void)
{
int error;
int pid;
real_root_dev = new_encode_dev(ROOT_DEV);
// real_root_dev全局變量保存的是存放realfs的設備的設備號
create_dev("/dev/root.old", Root_RAM0);
// 建立一個Root_RAM0類型的/dev/root.old設備節(jié)點,實際上就是一ramdisk
// 訪問root.old和上層函數(shù)中創(chuàng)建的ram節(jié)點是一樣的,對應的是同一設備,內容一樣
/* mount initrd on rootfs's /root */
mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
// 將/dev/root.old中的initrd文件系統(tǒng)掛載到了rootfs的/root目錄下
sys_mkdir("/old", 0700); /* 在rootfs的根目錄下創(chuàng)建old目錄 */
root_fd = sys_open("/", 0, 0);
// 通過這種方式保存原根目錄的描述符
old_fd = sys_open("/old", 0, 0);
// 通過這種方式保存/old的描述符
/* move initrd over / and chdir/chroot in initrd root */
sys_chdir("/root");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
// 進入/root目錄,將當前目錄mount為根目錄,然后切換當前目錄為程序執(zhí)行所
// 參考的根目錄位置
/*
* In case that a resume from disk is carried out by linuxrc or one of
* its children, we need to tell the freezer not to wait for us.
*/
current->flags |= PF_FREEZER_SKIP;
pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
// 啟動一進程執(zhí)行/linuxrc,并等待其執(zhí)行完。可以看出前面執(zhí)行sys_chroot(".")的意義
if (pid > 0)
while (pid != sys_wait4(-1, NULL, 0, NULL))
yield();
current->flags &= ~PF_FREEZER_SKIP;
/* move initrd to rootfs' /old */
sys_fchdir(old_fd);
sys_mount("/", ".", NULL, MS_MOVE, NULL);
// 進入old_fd描述的目錄,再將現(xiàn)在的根目錄mount到原來根目錄下的old目錄中
/* switch root and cwd back to / of rootfs */
sys_fchdir(root_fd);
sys_chroot(".");
// 進入真正的根目錄,然后切換當前目錄為程序執(zhí)行所參考的根目錄位置,也就是根
// 目錄還原, 但是真實的文件系統(tǒng)還沒有掛載
sys_close(old_fd);
sys_close(root_fd);
if (new_decode_dev(real_root_dev) == Root_RAM0) {
sys_chdir("/old");
return;
}
// 取真實文件系統(tǒng)所在設備的設備號
ROOT_DEV = new_decode_dev(real_root_dev);
mount_root();
// 將真實文件系統(tǒng)掛載到/root目錄中
// 注意mount_root()實際調用了函數(shù)mount_block_root()函數(shù)來實現(xiàn)mount,之后會
// chdir(/root),否則后面返回上上層函數(shù)有個地方可能看不明白
printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
// 將/old移植/root/initrd中去
if (!error)
printk("okay\n");
else {
int fd = sys_open("/dev/root.old", O_RDWR, 0);
if (error == -ENOENT)
printk("/initrd does not exist. Ignored.\n");
else
printk("failed\n");
printk(KERN_NOTICE "Unmounting old root\n");
sys_umount("/old", MNT_DETACH);
// 移動不成功就將/old目錄卸載掉
printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
if (fd < 0) {
error = fd;
} else {
error = sys_ioctl(fd, BLKFLSBUF, 0);
sys_close(fd);
}
printk(!error ? "okay\n" : "failed\n");
}
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
至此,通過image-initrd將真實的文件系統(tǒng)掛載起來了
附文:
rootfs_initialize.c -- rootfs初始化部分的代碼分析注解
rootfs初始化調用層次.txt
參考網址:
http://wiki.debian.org/ramfs
http://linux.net527.cn/Linuxwendang/xitongguanliyuan/1926.html
http://blog.csdn.net/knock/archive/2010/02/02/5280255.aspx
http://www.examda.com/linux/fudao/20090506/101701272.html
http://my.donews.com/tangfl/2007/12/17/linux_tmpfs_ramfs/
kouu’s home 頁面回收
http://www.ibm.com/developerworks/cn/linux/l-k26initrd/
文中的網址鏈接也為參考部分
轉載于:https://www.cnblogs.com/cxd2014/p/4470394.html
總結
以上是生活随笔為你收集整理的【转载】linux2.6内核initrd机制解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 各排序算法的复杂度
- 下一篇: 《代码整洁之道》阅读笔记