轉自:https://blog.csdn.net/michaelwubo/article/details/47418639
bootleader---》kernel----》initrd(是xz、cpio、是ramfs的一種,主要是驅動和為了加載rootfs准備一些環境、設備之類的)--------》rootfs(是img鏡像文件,也是ramfs的一種,是為安裝系統准備的環境,以后再安裝系統就是在rootfs中進行安裝和處理最后得到一個真是的文件系統,安裝程序在這里)
題記
很久之前就分析過這部分內容,但是那個時候不夠深入,姑且知道這么個東西存在,到底怎么用,來龍去脈咋回事就不知道了。前段時間工作上遇到了一個initrd的問題,沒辦法只能再去研究研究,還好,有點眉目,索性整理了一下。
網絡上流傳着很多關於ramdisk、initrd的各種版本的分析,我的這篇源於對他們的理解,非常感謝那些前輩的無私奉獻,要不然我們這些晚輩學起東西來該是多么艱難呀。在這里需要特別聲明的是如果文中有引用了您的思想而沒有給出參考文獻,請您原諒我的疏忽。晚輩就是需要站在像您這種巨人的肩上,技術才會發展,社會才會進步。
另外,由於本人水平有限,文中難免有誤解及不全面之處,煩請指正,謝謝!
歡迎轉載,但請保留原文該有的信息。
聯系方式:李枝果/lizgo lizhiguo0532@163.com 2010/10/16
目錄
第一部分:ramfs、tmpfs、rootfs、ramdisk
一、 什么是ramfs
二、 什么是tmpfs
三、 什么是rootfs
四、 什么是ramdisk
第二部分:initrd、initramfs
一、 initrd出現的背景
二、 initrd的種類和制作
第三部分:kernel初始化initrd代碼分析
一、 bootloader傳遞給內核的關於initrd的參數
二、 動態內存分配器slab介紹
三、 rootfs初始化
四、 內核初始化階段對initrd的處理
五、 老式塊設備的initrd的處理函數prepare_namespace()分析
附文
參考網址
第一部分:ramfs、tmpfs、rootfs、ramdisk
一、 什么是ramfs?
1. linux緩存機制
VFS(虛擬文件系統)層屏蔽了各種真實文件系統的特性,提供給linux上層統一的接口。
通常,linux對所有文件的讀寫都會在內存在做高速緩存,當系統再次使用這些文件時,可以直接從內存中讀取,以提高系統的I/O性能。當高速緩存中的文件被修改或者有數據寫入高速緩存時,系統會在適當的時候將這些高速緩存中的數據回寫到對應的文件系統設備(如磁盤、flash等)中去,那么這之后這些高速緩存的狀態就被標識為clean(可用),這樣就相當於告訴了系統:這些高速緩存中的數據文件系統設備上有備份,你可以拿去另作他用。但其中的數據會保持到VMS(Virtual Memory System)將這些高速緩存回收重新分配。
類似於頁緩存機制,目錄緩存機制也極大地加快了對目錄的訪問。
2. ramfs
ramfs是一種非常簡單的文件系統,它直接利用linux內核已有的高速緩存機制(所以其實現代碼很小,也由於這個原因,ramfs特性不能通過內核配置參數屏蔽,它是內核的天然屬性),使用系統的物理內存,做成一個大小可以動態變化的的基於內存的文件系統。
ramfs工作於虛擬文件系統層(VFS)層,不能被格式化,可以創建多個,默認情況下,ramfs最多能用到內存的一半,必要時也可以使用-o maxsize = 10000(單位是KB)來更改使用的最大內存量。
ramfs沒有對應的文件系統設備,文件被寫入ramfs和其他文件系統一樣都正常分配頁緩存和目錄緩存,但是卻不可能想其他有存儲設備的文件系統一樣將高速緩存中的文件回寫到存儲設備。這就意味着這些為ramfs中的文件和目錄分配的高速頁或者目錄緩存都不可能會被標記為clean(可用)狀態,所以系統就永遠不會釋放ramfs所占用的內存。正因為可以在ramfs下面可以一直往里寫數據,直到寫滿為止,所以這種操作只有root(or trusted user)用戶才可以進行ramfs寫操作。
為了解決ramfs的缺點(沒有回寫設備)導致的種種問題,所以衍生出了tmpfs文件系統。
二、 什么是tmpfs?
tmpfs是ramfs的衍生物,在ramfs的基礎上增加了容量大小的限制和允許向交換
空間(swap) 寫入數據。由於增加了這兩個特性,所以普通用戶也可以使用tmpfs。
tmpfs是一種虛擬內存文件系統,它不同於傳統的用塊設備形式來實現的ramdisk,也不同於針對物理內存的ramfs。tmpfs既可以使用物理內存,也可以使用交換分區。在linux內核中,虛擬內存資源由物理內存和交換分區組成,這些資源由內核中的虛擬內存子系統來負責管理。tmpfs就是和虛擬內存子系統打交道的,它向虛擬內存子系統請求頁來存儲文件,同linux的其他請求頁的部分一樣,不知道分配給自己的頁是在內存中還是在交換分區中。也就是說tmpfs使用的是虛擬內存,而ramfs使用物理內存。另外tmpfs和ramfs一樣,不可以被格式化,同時大小也是不固定的,可以使用-o size =32m或者(1g)來修改。
另外,tmpfs可以將當前不需要使用的頁寫入到交換空間。同時由於其使用的虛擬內存,所以tmpfs一旦被卸載,其中的數據都會丟失。
如果需要使用tmpfs,在內核編譯的時候得選擇上:
Virtual memory filesystem support
ramfs只會在物理內存中被創建,而tmpfs可能在物理內存中創建,也可能在交換 分區中創建。對於想利用內存的高速IO來提高效能的應用,最好是使用ramfs。對於只是想存放臨時緩存的應用,最好使用tmpfs,以提高內存的使用率。
三、 什么是rootfs?
rootfs是一個特定的ramfs(或tmpfs,如果tmpfs被啟用)的實例,它始終存在於linux2.6的系統中。rootfs不能被卸載(與其添加特殊代碼用來維護空的鏈表,不如把rootfs節點始終加入,因此便於kernel維護。rootfs是ramfs的一個空實例,占用空間極小)。大部分其他的文件系統安裝於rootfs之上,然后忽略它。它是內核啟動初始化根文件系統。
四、 什么是ramdisk?
linux2.6版本之后都不在使用ramdisk了,2.4中還在使用。ramdisk是一種將內存
中的的一塊區域作為物理磁盤來使用的一種技術,也可以說,ramdisk是在一塊內存區 域中創建的塊設備,用於存放文件系統。對於用戶來說,可以把ramdisk與通常的硬盤分區同等對待來使用。ramdisk不適合作為長期保存文件的介質,掉電后ramdisk的內容會消失。
為了能夠使用ramdisk 你的內核必須要支持ramdisk,即:在編譯內核時,要選中RAM disk support這一選項,會在配置文件中定義CONFIG_BLK_DEV_RAM。同時為了讓內核有能力在內核加載階段就能裝入ramdisk,並運行其中的內容,要選中initial RAM disk(initrd) support 選項,會在配置文件中定義CONFIG_BLK_DEV_INITRD。
ramdisk的大小是固定的,安裝在其上的文件系統大小也是固定的。ramdisk在使用的時候,這個假的塊設備和高速緩存(頁緩存和目錄緩存)之間有數據的拷貝,而且它還需要文件系統的驅動來格式化和解釋這些數據。所以,使用ramdisk不僅浪費了內存,還加重了cpu的負擔,同時也無污染了cache,而且其所有的文件和目錄都要通過頁和目錄緩存進行訪問,這些工作ramfs都要執行的,那么ramdisk就可以完全不需要。這是廢棄ramdisk的理由之一。
另一個廢棄它的理由就是,回環設備的引進,而回環設備提供了一個更靈活和方便的方式(從文件而不是從大塊的內存)來創建一個合成塊設備。
第二部分:initrd、initramfs
一、 initrd出現的背景
在早期的linux系統中,一般只有硬盤或者軟盤被用來作為linux根文件系統的存儲設備,因此也就很容易把這些設備的驅動程序集成到內核中。但是現在的嵌入式系統中可能將根文件系統保存到各種存儲設備上,包括scsi、sata,u-disk等等。因此把這些設備的驅動代碼全部編譯到內核中顯然就不是很方便。
在內核模塊自動加載機制udev中,我們看到利用udevd可以實現內核模塊的自動加載,因此我們希望如果存儲根文件系統的存儲設備的驅動程序也能夠實現自動加載,那就好了。但是這里有一個矛盾,udevd是一個可執行文件,在根文件系統被掛載前,是不可能執行udevd的,但是如果udevd沒有啟動,那就無法自動加載存儲根文件系統設備的驅動程序,同時也無法在/dev目錄下建立相應的設備節點。
為了解決這一矛盾,於是出現了基於ramdisk的initrd( bootloader initialized RAM disk )。Initrd是一個被壓縮過的小型根目錄,這個目錄中包含了啟動階段中必須的驅動模塊,可執行文件和啟動腳本,也包括上面提到的udevd(實現udev機制的demon)。當系統啟動的時候,bootloader會把initrd文件讀到內存中,然后把initrd文件在內存中的起始地址和大小傳遞給內核。內核在啟動初始化過程中會解壓縮initrd文件,然后將解壓后的initrd掛載為根目錄,然后執行根目錄中的/init腳本(cpio格式的initrd為/init,而image格式的initrd<也稱老式塊設備的initrd或傳統的文件鏡像格式的initrd>為/initrc),您就可以在這個腳本中運行initrd文件系統中的udevd,讓它來自動加載realfs(真實文件系統)存放設備的驅動程序以及在/dev目錄下建立必要的設備節點。在udevd自動加載磁盤驅動程序之后,就可以mount真正的根目錄,並切換到這個根目錄中來。
這里只是個簡單的描述,后面慢慢分析吧。
二、 initrd的種類和制作
initrd總的來說目前有兩種格式:image格式和cpio格式。image格式也叫文件系統鏡像文件(老式塊設備的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個字節。
4. count=blocks:僅拷貝blocks個塊,塊大小等於ibs指定的字節數。
還有其他更詳細的參數用法參考:dd命令-詳解
http://blog.csdn.net/liumang_D/archive/2009/02/17/3899462.aspx
mkfs.ext2:等同於mke2fs,建立ext2文件系統。
mke2fs [-cFMqrSvV][-b <區塊大小>][-f <不連續區段大小>][-i <字節>][-N ][-l <文件>][-L <標簽>][-m <百分比值>][-R=<區塊數>][ 設備名稱][區塊數]
1. -F 不管指定的設備為何,強制執行mke2fs。
2. -m<百分比值> 指定給管理員保留區塊的比例,預設為5%。
3. 其余參數默認即可,參考: mkfs.ext2 命令-詳解(網絡搜索)
mount:掛在命令.
mount [-t vfstype] [-o options] device dir
1.-t vfstype 指定文件系統的類型,通常不必指定。mount 會自動選擇正確的類型。
2.-o options 主要用來描述設備或檔案的掛接方式。常用的參數有:
loop:用來把一個文件當成硬盤分區掛接上系統
ro:采用只讀方式掛接設備
rw:采用讀寫方式掛接設備
iocharset:指定訪問文件系統所用字符集
3.詳細參數參考: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文件系統的掛載,然而image-initrd存在以下缺點:
1.image-initrd大小是固定的,例如上面的壓縮之前的initrd大小是4M(4k*1024),假設您的根目錄(上例中的/opt/filesystem)總大小僅僅是1M,它仍然要占用4M的空間。如果您在dd階段指定大小為1M,后來發現不夠用的時候,必須按照上面的步驟重新來一次。
2.image-initrd是一個虛擬的塊設備,您可是使用fdisk對這個虛擬塊設備進行分區。在內核中,對塊設備的讀寫還要經過緩沖區管理模塊,也就是說,當內核讀取initrd中的文件內容時,緩沖區管理層會認為下層的塊設備速度比較慢,因此會啟用預讀和緩存功能。這樣initrd本身就在內存中,同時塊設備緩沖區管理層還會保存一部分內容。
2)、initramfs
為了避免上述缺點,在linux2.5中出現了initramfs,它的作用和initrd類似,只是和內核編譯成一個文件(該initramfs是經過gzip壓縮后的cpio格式的數據文件),該cpio格式的文件被鏈接進了內核中特殊的數據段.init.ramfs上,其中全局變量__initramfs_start和__initramfs_end分別指向這個數據段的起始地址和結束地址。內核啟動時會對.init.ramfs段中的數據進行解壓,然后使用它作為臨時的根文件系統。
要制作這樣的內核,我們只需要在make menuconfig中配置以下選項就可以了:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/opt/filesystem) Initramfs source file(s)
其中/opt/filesystem就是我們的小型根目錄,這里可以使一個現成的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
如果指定的是一個目錄而不是一個像這樣的配置文件,內核在編譯的時候會從指定的目錄創建一個配置文件(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命令來實現,如下:
# sudo find /opt/filesystem/ -depth | cpio -c -o > initrd.img
# gzip -9 initrd.img
這樣得到的initrd就是cpio格式的,而且這個文件的大小是可變的,意思就是根據
你的filesystem的大小而變化,不會像前面的image格式的initrd那樣大小固定了。當然我覺得前面的initramfs的大小也是可變的了。
關鍵詞:
ramfs、tmpfs、rootfs、ramdisk
image-initrd、initramfs、cpio-initrd
第三部分:kernel初始化initrd代碼分析
arm平台、linux2.6.29
一、 bootloader傳遞給內核的關於initrd的參數
root=
該參數告訴內核以那個設備作為根文件系統,通常由如下形式:
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因為使用的是系統內一直存在的rootfs,所以該參數可以省略。
第三、四種基本通用,只是,第四種使用的設備號和分區號來指定,比如root=/dev/mtdblock4 等價於 root=31:04,意思就是使用nand設備上的第四分區來作為根文件系統, 同時用rootfstype指定文件系統類型。
最后一種就是掛在nfs文件系統了,除了要指定root類型之外,還需要指定服務器上的某個目錄來作為掛載的根目錄。
initrd=
應遵循這種格式:initrd = addr, size ,其中addr表示initrd在內存中的位置,size表示initrd的大小。如initrd = 0xa060000,0x2c6b
另外一種傳遞該參數的方法就是:使用tag參數來傳遞,uboot中使用的是函數setup_initrd_tag()函數來生成對應的tag參數。
需要注意的有兩點:
1. 內核初始化先解析tag中的initrd地址和大小參數,然后再去會去執行cmdline解析initrd=參數,如果這兩個地方都指定了話,那么最終取決於cmdline中initrd=所指定的參數,因為這兩種方法傳遞的結果都是被內核初始化階段的全局變量phys_initrd_start和phys_initrd_size中(這是物理地址),然后再轉換成虛擬地址存儲在全局變量initrd_start和initrd_size中,供后續使用。
2. bootloader傳遞的大小應該是initrd的實際大小,不能多傳更不能少傳,否則,gunzip解壓縮的時候會出錯。
init=
init指定的是內核啟起來后,進入系統中運行的第一個腳本,一般init=/linuxrc(image-initrd使用)和init=/init(initramfs和cpio-initrd使用)。
ramdisk使用rdinit=/xxx來指定。
小結:( 以下結論均在initrd的地址和大小均通過tag參數傳遞)
通過后面的代碼分析,可以做如下總結:
1. 使用initramfs和cpio-initrd
不需要root=xxx參數,而init=/init是必須的
2. 使用image-initrd(記得打開內核對ramdisk和initrd的支持)
init=/initrc,下面是傳遞不同root=xxx的情況
a. root=/dev/ramx,將image-initrd作為真實的文件系統在使用
b. root=/dev/mtdblockx rw rootfstype = jffs2,這樣image-initrd只是作為一個中間過渡的文件系統,最終還是需要依靠內核1號線程將真實的文件系統掛上來。
(b中的root= 只是舉個例子,真實系統放在什么設備上沒關系,主要的是需要在image-initrd文件系統中將真是文件系統存在的設備的驅動程序加載上就好了)
二、 動態內存分配器slab介紹
linux的每種動態內存管理都使用一種基於堆的分配策略。
原始的分配策略是這樣的,在這種方法中,大塊內存(稱為 堆)用來為用戶定義的目的提 供內存。當用戶需要一塊內存時,就請求給自己分配一定大小的內存。堆管理器會查看可用內存的情況(使用特定算法)並返回一塊內存。搜索過程中使用的一些算 法有 first-fit(在堆中搜索到的第一個滿足請求的內存塊 )和 best-fit(使用堆中滿足請求的最合適的內存塊)。當用戶使用完內存后,就將內存返回給堆。這種基於堆的分配策略的根本問題是碎片(fragmentation)。當內存塊被分配后,它們會以不同的順序在不同的時間返回。這樣會在堆中留下一些洞,需要花一些時間才能有效地管理空閑內存。這種算法通常具有較高的內存使用效率(分配需要的內存),但是卻需要花費更多時間來對堆進行管理。
大約在2.0內核之前,出現了另外一種管理機制稱為 buddy memory allocation(伙伴內存分配系統),是一種更快的內存分配技術,它將內存 划分為 2 的冪次方個分區,並使用 best-fit 方法來分配內存請求。當用戶釋放內存時,就會檢查 buddy 塊,查看其相鄰的內存塊是否也已經被釋放。如果是的話,將合並內存塊以最小化內存碎片。這個算法的時間效率更高,但是由於使用 best-fit 方法的緣故,會產生內存浪費。
后來在2.2內核之后,引入了一種全新的動態內存分配器slab分配器,slab分配器的概念首先在Sun Microsystem 的SunOs 5.4操作系統中得以實現,圍繞對象緩存進行的。slab層會把不同的對象划分為所謂的高速緩存(cache)組,其中每個高速緩存都存放不同類型的對象。每種對象類型對應一個高速緩存。例如,一個高速緩存用於存放進程描述符(task_struct),而另一個高速緩存存放索引節點對象(struct inond)。值得一提的是,kmalloc接口建立在slab層之上,使用了一組通用高速緩存。
下圖給出了slab 結構的高層組織結構。在最高層是cache_chain,這是一 個 slab 緩存的鏈接列表。這對於 best-fit 算法非常有用,可以用來查找最適合所需要的分配大小的緩存(遍歷列表)。cache_chain 的每個元素都是一個 kmem_cache 結構的引用(稱為一個 cache)。它定義了一個要管理的給定大小的對象池。
圖貼不上,《linux內核設計與實現》書上有
這些高速緩存又划分為slab,slab又由一個或多個物理上連續的頁組成,每一個物理頁被划分為特定的對象。一般情況下,slab也僅僅由一頁組成。
slab分配器的接口:
創建一個新的高速緩存,它通常在內核初始化時或者首次加載內核模塊時執行。
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);
創建了高速緩存之后,就可以通過下面的函數從中獲取對象
void *kmem_cache_alloc(kmem_cache_t *cachep,int flags);
如果高速緩存中所有的slab中都沒有空閑的對象,那么slab層必須通過kmem_getpages()獲取新的頁,flags值傳遞給__get_free_pages()。
下面的函數是釋放對象,將其返還給原先的slab
void kmem_cache_free(kmem_cache_t *cachep,void *objp);
更加詳細的參看:《linux內核設計與實現》 robrt love著
另外值得提到的是在linux2.6.22之后,引進了slub分配器,該分配器保留了slab的基本思想:每個緩沖區由多個小的slab組成,每個slab包含固定數目的對象。slub分配器簡化了kmem_cache,slab等相關的管理數據結構,摒棄了slab分配器中眾多的隊列概念,並針對多處理器、NUMA系統進行優化,從而提高了性能和可擴展性並降低了內存的浪費。為了保證內核其它模塊能夠無縫遷移到slub分配器,slub還保留了原有slab分配器所有的接口 API 函數。
http://qgjie456.blog.163.com/blog/static/3545136720090622056716/
三、 rootfs初始化
該部分代碼主要在init/main.c中的start_kernel()函數中的vfs_caches_init_early()和vfs_caches_init(num_physpages)來實現,其中num_physpages全局變量在mem_init()函數中初始化,代表物理內存的總頁數,
詳細的代碼分析見:rootfs_initialize.c
vfs_caches_init_early():
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
總結:
inode_hashtable就是一數組,每個數組元素都是一個struct hlist_head結構體,該結構體實際上就只有一個指向struct hlist_node對象的指針first,所有指針在arm體系上都是4字節,所以這個函數實際上只是為數組inode_hashtable分配了一段內存空間,該數組是實際上就是一個指針數組,每個元素都是指向struct hlist_node對象的指針。然后最后將這些指針全部置成NULL。至於后續怎么使用還沒涉及到。
同樣類似地,對於Dentry cache哈希表(dentry_hashtable)也是一樣的,只是創建了一個指針數組而已。
http://qgjie456.blog.163.com/blog/static/3545136720081126102615685/
四、 內核初始化階段對initrd的處理
start_kernel()函數最后會調用rest_init(),在該函數中會創建兩個內核線程kernel_init和kthreadd(2.6.14內核中只創建了一個內核線程init,但是所做工作基本一樣),內核線程創建后,原來的系統0號進程進入idle狀態。
對initrd的處理函數主要有兩個:populate_rootfs()和prepare_namespace(),針對不同的格式,處理情況各不相同。
這里注意一點的是:populate_rootfs()函數在2.6.14和2.6.29中執行的位置不一樣。2.6.14中在do_basic_setup()函數執行之前,而2.6.29版本中將其移植do_basic_setup()函數中執行,怎么講呢?高版本中將populate_rootfs()移到do_initcalls()中執行,也就是編譯的時候將函數放在了.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,也叫傳統的文件鏡像格式的initrd
initramfs : 和內核編譯到一起的cpio格式的initrd
cpio-initrd : cpio格式的獨立文件的initrd
populate_rootfs函數分析
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分別指向這個數據段的內存起始地址和結束地址(虛擬地址)
所以這里了如果__initramfs_end - __initramfs_start等於0,那么unpack_to_rootfs函數不會做任何事情,直接退出。系統認為該initrd不是initramfs文件,所以需要到后面去處理。
***/
if (err)
panic(err);
// 判斷是否加載了initrd,bootlaoder會將initrd加載到內存地址initrd_start,// 前面已經說到了如何給內核傳遞這些參數
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包, 這是通過最后一個參數來區分的, 0:釋放 1:查看。
***/
if (!err) {
// 如果是cpio-initrd則利用函數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;
}
/***
如果執行到這里,說明這是舊的塊設備格式的initrd。那么首先在前面掛載的根目錄rootfs上創建一個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()執行完后,如果是initramfs和cpio-initrd的話,都已經將他們釋放到了前面初始化完成的rootfs中去了,那么根目錄下肯定會出現init文件。而如果是image-initrd,那么只會在rootfs根目錄下出現一個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就會直接執行init_post()函數,然后在后面執行/init文件
***/
init_post();
return 0;
}
五、老式塊設備的initrd的處理函數prepare_namespace()分析
cmdline傳了里的參數需要包括:
root=/dev/mtdblockx rw 或者{root=/dev/ramx } init=/initrc
謹記root=指定的設備是最終的真實文件系統的。在處理image-initrd的時候指定了它,那么系統就會認為你這個initrd就是我的真實文件系統了,會跳過很多步驟不執行的。
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);
/***
將存儲真實文件系統的設備的設備號保存到ROOT_DEV中,如果cmdline傳遞的是/dev/ramx話,這里的ROOT_DEV = Root_RAM0,如果不是,那么是什么就是什么,后面會根據這個做出不同的事情
***/
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
/***
saved_root_name全局數組保存的是由命令行傳進來的root=的值,比如你傳遞了root=/dev/ramx或者root=/dev/mtdblockx rw,那么上面的if條件成立,那么就通過name_to_dev_t(root_device_name)函數獲得該設備的設備號ROOT_DEV。否則就跳過該段不執行。
***/
if (initrd_load()) /*** 詳見后面注釋 ***/
/***
加載老式塊設備的initrd,這里也分兩種情況,一種是將該initrd作為真實文件系統返回1另外一種就是作為一種過渡的文件系統而已,此時返回0。
***/
goto out;
/***
如果要使用老式塊設備的initrd作為真實的文件系統的話,需要在cmdline傳入一下參數: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()中直接創建一個Root_RAM0類型的/dev/root節點(前面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設備上的真實文件系統mount到rootfs的/root目錄中
out:
// 注意:上面的過程都已經將真實文件系統掛載到了/root(這里的/還是rootfs),
// 並且當前目錄為/root
sys_mount(".", "/", NULL, MS_MOVE, NULL);
// mount當前目錄為根目錄,覆蓋掉原來的rootfs
sys_chroot(".");
// 切換當前目錄為程序執行所參考的根目錄位置,至此,真實文件系統
// 掛載完畢
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
int __init initrd_load(void)
{
if (mount_initrd) {
create_dev("/dev/ram", Root_RAM0);
// 建立一個Root_RAM0類型的/dev/ram設備節點,實際上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()函數負責對initrd進行具體的處理
return 1; /* image-initrd 作為中間過渡的文件系統 */
}
/***
/initrd.image文件保存的就是image-initrd,rd_load_image函數執行具體的加載操作,
將image-nitrd的文件內容釋放到設備類型為Root_RAM0的ramdisk中去(節點名為ram)。判斷ROOT_DEV!=Root_RAM0的含義是(ROOT_DEV不能=0,否則mount的時候會出錯),正如前面所說,如果你在bootloader里配置了root=/dev/ramx,則實際上真正的根設備就是這個initrd了,所以就不把它作為initrd處理 ,而是作為真實文件系統來處理。
***/
}
sys_unlink("/initrd.image");// 刪除文件initrd.image
return 0; /* image-initrd作為真實的文件系統 */
}
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
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設備節點,實際上就是一ramdisk
// 訪問root.old和上層函數中創建的ram節點是一樣的,對應的是同一設備,內容一樣
/* mount initrd on rootfs's /root */
mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
// 將/dev/root.old中的initrd文件系統掛載到了rootfs的/root目錄下
sys_mkdir("/old", 0700); /* 在rootfs的根目錄下創建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為根目錄,然后切換當前目錄為程序執行所
// 參考的根目錄位置
/*
* 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);
// 啟動一進程執行/linuxrc,並等待其執行完。可以看出前面執行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描述的目錄,再將現在的根目錄mount到原來根目錄下的old目錄中
/* switch root and cwd back to / of rootfs */
sys_fchdir(root_fd);
sys_chroot(".");
// 進入真正的根目錄,然后切換當前目錄為程序執行所參考的根目錄位置,也就是根
// 目錄還原, 但是真實的文件系統還沒有掛載
sys_close(old_fd);
sys_close(root_fd);
if (new_decode_dev(real_root_dev) == Root_RAM0) {
sys_chdir("/old");
return;
}
// 取真實文件系統所在設備的設備號
ROOT_DEV = new_decode_dev(real_root_dev);
mount_root();
// 將真實文件系統掛載到/root目錄中
// 注意mount_root()實際調用了函數mount_block_root()函數來實現mount,之后會
// chdir(/root),否則后面返回上上層函數有個地方可能看不明白
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將真實的文件系統掛載起來了
附文:
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