嵌入式linux基礎教程 使用者空間初始化(2)
使用者空間初始化
Web伺服器啟動指令碼示例
這個示例很簡單但是它可以說明一些機制,指導你設計自己的系統啟動和關機行為。這個例子是基於busybox,他和init的初始化行為稍有不同。
在典型的包含Web伺服器的嵌入式應用中,你也許會期望系統中有多個伺服器,用於系統維護和遠端訪問。在這個例子中,我們啟用了訪問HTTP和Telnet的伺服器。下面程式碼顯示了一個簡單的rc.sysinit指令碼,用於我們假想的Web伺服器裝置。
Web伺服器的rc.sysinit
#!/bin/sh
echo "This is rc.sysinit"
busybox mount -t proc none /proc
# 載入系統日誌
/sbin/syslogd
/sbin/klogd
#開啟老式的PTY,以支援telnet
busybox mkdir /dev/pts
busybox mknod /dev/ptmx c 5 2
busybox mount -t devpts devpts /dev/pts
這個簡單的指令碼首先掛載proc檔案系統,接著啟動系統日誌,藉此我們可以記錄系統的執行資訊。在系統出錯時,這些系統日誌特別有用。指令碼中的最後一些條目用於開啟對UNIX PTY子系統的支援,這個例子中使用的Telnet伺服器在實現功能時需要該子系統
下面的程式碼清單是在執行級別2的啟動指令碼中使用的命令。這個指令碼中包含的命令用於開啟我們需要的服務
執行級別2的啟動指令碼示例
#!/bin/sh
echo "This is runlv2.startup"
echo "Starting Internet Superserver"
inetd
echo "Starting web server"
webs &
這個執行級別2的啟動指令碼很簡單。首先我們開啟了所謂的因特網超級伺服器inetd,它會攔截常見的TCP/IP請求並啟動相應的服務。本例中,啟用Telnet服務的配置檔案是/etc/inted.conf;接著執行Web伺服器程式webs。雖然很簡單但這是一個可以正常工作的指令碼,能夠啟動Telnet和Web服務。為了完成整個配置,還需要提供一個關機指令碼,用於在系統關機之前關閉web伺服器和因特網超級伺服器。在這個簡單的場景中,對於正確的關機,這些操作已經足夠了。
初始RAM磁碟
LINUX核心中包含了兩種掛載早期根檔案系統的機制,用於執行某些和啟動相關的系統初始化及配置。我們先來討論老式方法,即初始RAM磁碟或initrd。
初始RAM磁碟,或簡稱為initrd,是一種用於啟動早期使用者空間處理流程的老式方法。對這個功能的支援必須編譯至核心中。編譯核心時,相關的選項可以在核心配置工具中找到,具體位置是General Setup中的ARM disk support選項。
初始RAM磁碟是一個功能完備的小型根檔案系統,它通常包含一些指令,用於在系統引導完成之前載入一些特定的裝置驅動程式。比如,在linux工作站發行版中,初始RAM磁碟的作用就是在掛載真正的根檔案系統之前載入EXT3檔案系統的裝置驅動程式。Initrd一般用於載入訪問真正的根檔案系統必須的裝置驅動程式。
使用initrd進行引導
為了使用initrd的功能,大多數架構的引導載入程式會將initrd映象傳遞給核心。常見的場景是,引導載入程式先將壓縮過的核心映象載入到記憶體中,接著將initrd映象載入到另一段可用的記憶體中。在這個工程中,引導載入程式負責將控制權轉交給核心之前,將initrd映象的載入地址傳遞給核心。具體的機制取決於架構、引導載入程式和平臺的實現。然而,核心必須知道initrd映象的位置才能夠載入它。
有些架構和映象會構造單個合成的二進位制映象。當引導載入程式所引導的linux不支援載入initrd映象時就會採用這種方式。在這種情況下,核心和initrd映象只是簡單的拼接在一起,形成一個合成的映象。可以在核心的makefile中找到對這種合成映象的引用,名為bootpImage。目前,只有ARM架構使用了這種方式。
那麼,核心怎麼知道哪裡可以找到initrd映象呢?除非引導載入程式裡做了特別處理,通常情況下,使用核心命令列就可以將initrd映象的起始地址和大小傳遞給核心。下面是一個在採用TI OMAP 5912處理器的ARM參考板上使用核心命令列的例子:
console=ttyS0, 115200 root=/dev/nfs\
nfsroot=192.168.1.9:/home/chris/sandbox/omap-target\
initrd=0x10800000,0x14af47
在裝置ttyS0上指定一個控制檯,波特率為115200
通過NFS(網路檔案系統)掛載根檔案系統
在主機192.168.1.9(目錄為/home/chris/sandbox/omap-target)上找到NFS根檔案系統
載入並掛載初始RAM磁碟,實體記憶體地址為0x10800000,大小為0x14af47(一般指壓縮後的大小)
引導載入程式對initrd的支援
下面來看一個簡單的例子,它基於流行的U-Boot引導載入程式並執行在ARM處理器之上。U-Boot能夠直接引導linux核心,並且能夠在引導核心映象時包含initrd映象。
ramdisk支援的核心引導
[uboot]>tftp 0x10000000 kernel-uImage
...
Load address:0x10000000
Loading:#################done
Bytes transferred = 1069092(105024 hex)
[uboot]>tftp 0x10800000 initrd-uboot
...
Loading address:0x10800000
Loading:###################################done
Bytes transferred = 282575(44fcf hex)
[uboot]>bootm 0x10000000 0x10800040
Uncompressing kernel..............done.
...
RAMDISK driver initialized: 16 RAM disks of 16384K size 1024 blocksize
...
RAMDISK:Compressed image found at block 0
VFS:Mounted root(ext2 filesystem),
Greetings:this is linuxrc from Initial RAMDisk
Mounting /proc filesystem
BusyBox v1.00(2015.03.14-16:37+0000)Built-in shell(ash)
Enter 'help' for a list of built-in commands.
#(<<<<Busybox命令提示符)
tftp命令指示U-Boot從TFTP伺服器上下載核心映象。核心映象被下載下來後,他會被放到目標系統記憶體的基地址處,地址值為256MB(十六進位制表示為0x10000000)。接著,另一個映象即初始RAM磁碟映象,也被下載下來並存放到一個較高的記憶體地址處(256MB+8MB)。最後我們執行U-Boot的bootm命令,這個命令的含義是“從記憶體引導”bootm有兩個引數,一個是linux核心映象的地址,另一個引數時可選的,如果有的話,指初始RAM磁碟映象的地址。
U-Boot引導載入程式的一個特性:它完全支援通過乙太網載入核心映象和ramdisk映象,這個特性非常有利於開發。也可以使用別的方法將核心和ramdisk映象載入到目標板上。也可以使用基於硬體的快閃記憶體程式設計工具將這些映象燒寫到快閃記憶體中,或者可以使用一個串列埠,通過RS-232下載核心和檔案系統映象。然而,由於這些映象一般都比較大使用這種基於TFTP下載方式,會節省大量的開發時間。無論採用哪種引導載入程式,確保它支援通過網路下載映象。
initrd的奧祕所在:linuxrc
當核心引導時,它首先會檢測initrd映象是否存在。然後,它會將這個壓縮的二進位制檔案從記憶體中的指定位置複製到一個合適的核心ramdisk中,並掛載它作為根檔案系統。initrd的奧祕來自一個特殊檔案的內容,而這個檔案就儲存在initrd映象中。當核心掛載初始的ramdisk時,它會查詢一個名為linuxrc的特殊檔案。它將這個檔案當作一個指令碼檔案並執行其中包含的命令。這種機制允許設計者控制initrd的行為。下面程式碼顯示了一個Linuxrc檔案的內容
linuxrc檔案示例
#!/bin/sh
echo 'Greetings: this is 'linuxrc' from Initial Ramdisk'
echo 'Mounting /proc filesystem'
mount -t proc /proc/proc
busybox sh
實際上,這個檔案會包含一些命令,而他們需要在掛載真正的根檔案系統之前執行。舉例來說,該檔案可能會包含一條載入CompactFlash驅動程式的命令,以便從CompactFlash裝置上獲取一個真正的根檔案系統。在簡單的inittab那個例子中,僅僅建立了一個busybox shell,並且暫停了引導過程以便檢查。你可以從ramdisk支援的核心載入程式中看到由busybox shell生成的#命令列提示符。如果在此輸入exit命令,核心會繼續其引導過程直至完成。
當核心將ramdisk(initrd映象)從實體記憶體複製到一個核心ramdisk之後,它會將這塊實體記憶體歸還到系統的可用記憶體池中。你可以認為這是將initrd映象轉移了一下,從實體記憶體的一個固定地址轉移到核心自身的虛擬記憶體中(形式是一個核心ramdisk裝置)。
掛載根檔案系統使用了mount命令似乎多了一個/proc,下面這個命令也是有效的
mount -t proc none /proc
mount命令會忽略device欄位,因為沒有任何物理裝置和proc檔案系統相關聯。命令中由-t proc就足夠了,這會指示mount將/proc檔案系統掛載/proc掛載點(目錄)上。使用前一種命令是為了說明我們實際上是將一個核心偽裝置(/proc)掛載到/proc上。mount命令會忽略device引數,你可以選擇自己喜歡的方式。使用前一種命令形式掛載成功後,在命令列中輸入mount時,輸出資訊中的device欄位會顯示為proc,而不是none,這就會提醒你這是一個虛擬檔案系統。
initrd探究
作為linux引導過程的一部分,核心必須找到並掛載一個根檔案系統。在引導過程的後期,核心通過函式prepare_namespace()決定要掛載的檔案系統及掛載點,這個函式位於檔案.../init/do_mount.c中。如果核心支援initrd並且核心命令列也按此進行配置的,核心會解壓實體記憶體中的initrd映象,並最終將這個檔案的內容複製到一個ramdisk裝置中。這時,我們擁有了一個位於核心ramdisk中的合適的檔案系統。當檔案系統被讀入到ramdisk中後,核心實際上會掛載ramdisk裝置作為其根檔案系統。最後,核心生成一個核心執行緒,用以執行initrd映象中的linuxrc檔案。
當linuxrc指令碼執行完畢後,核心會解除安裝initrd,並繼續執行系統引導的最後一些步驟。如果真正的根裝置中有一個名為/initrd的目錄,linux會將initrd檔案系統掛載到這個路徑上(稱為掛載點)。如果最終這個根檔案系統不包含這個目錄,initrd映象就簡單的被丟棄了。
如果核心命令列中包含引數root=引數並指定一個ramdisk(root=/dev/ram0),那麼前面描述的initrd的行為會發生兩個重要的改變。首先,linuxrc檔案將不會得到處理。其此,核心不會嘗試掛載另一個檔案系統作為其根檔案系統。這意味著你可以擁有一個linux系統,其中initrd時它唯一的根檔案系統。這對於小型的系統配置很有用,這類系統中唯一的根檔案系統就是ramdisk。如果在核心命令列中指定/dev/ram0,當整個系統初始化完成後,initrd就會稱為最終的根檔案系統。
構造initrd映象
開發嵌入式系統會遇到一些挑戰,其中之一就是建立合適的根檔案系統映象。建立合適的initrd映象就更具有挑戰性了,因為我們需要限定它的大小並專門製作。這一節將研究initrd的需求以及其檔案系統的內容。
下列程式碼清單是執行tree命令顯示本章示例initrd映象的內容
.
--bin
--busybox
--echo->busybox
--mount->busybox
--sh->busybox
--dev
--console
--ram0
--ttyS0
--etc
--linuxrc
--proc
initrd基於busybox具有很多功能。這個例子中的busybox是靜態連線的,不依賴於任何系統程式庫。
使用initramfs
執行早期的使用者空間程式還有一種更好的機制,就是使用initramfs。從概念上將它類似於initrd。它的作用也是類似的:掛載真正的根檔案系統前載入一些必須的裝置驅動程式。然而,它與initrd的機制有很多不同。
initramfs是在呼叫do_basic_setup()之前載入的,這就提供了一種在載入裝置驅動程式之前載入韌體的機制。可以參考linux核心程式碼關於這個子系統的文件。.../Documentation/filesystem/ramfs-rootfs-initramfs.txt。
initramfs是一個cpio格式的檔案檔案,而initrd是檔案系統映象。無需稱為root使用者就可以建立initramfs。它已經整合到linux核心原始碼樹中了,當構建核心映象時,會自動建立一個預設小型initramfs映象。改動這個小型映象要比構件和載入新的initrd映象容易的多。
下面程式碼顯示了linux核心原始碼樹.../usr目錄的內容,initramfs映象就是在這裡構建的。
total 72
-rw-r--r-- 1 chris chris 1146 2009-12-16 12:36 built-in.o
-rwxr-xr-x 1 chris chris 15567 2009-12-16 12:36 gen_init_cpio
-rwxr-xr-x 1 chris chris 12543 2009-12-16 12:36 gen_init_cpio.c
-rwxr-xr-x 1 chris chris 1024 2009-12-16 12:36 initramfs_data.bz2.s
...
...
linux核心原始碼樹的.../scripts目錄中有一個名為gen_initramfs_list.sh的指令碼檔案,其中定義了哪些檔案會預設包含在initramfs檔案檔案中。對於最新的linux核心,這些預設檔案類似於下列程式碼所列出的檔案
dir /dev 0755 0 0
nod /dev/console 0600 0 0 c 5 1
dir /root 0700 0 0
這會生成一個小型的預設目錄結構,其中包含/root和/dev兩個頂層目錄,還包含了一個單獨的代表控制檯的裝置節點。核心文件.../Documentation/filesystem/ramfs-rootfs-initramfs中詳細講述瞭如何指定initramfs檔案系統的組成部分。總而言之,上面的程式碼清單會生成一個名為/dev的目錄項(dir),檔案許可權為0755,使用者ID和組ID都是0(代表root使用者)。第二行定義了一個名為/dev/console的裝置節點(nod),檔案許可權為0600,使用者ID和組ID都是0,它是一個字元裝置,主裝置號為5此裝置號為1。第三行建立了一個目錄名為/root。規格於/dev目錄類似/
定製initramfs
由兩種針對特定需求釘子initramfs的方法。一種方法是建立一個cpio格式的檔案檔案,其中包含了你所需的所有檔案,另一種方法是指定一系列目錄和檔案,這些檔案會和gen_initramfs_list.sh所建立的預設檔案合併在一起。你可以通過核心配置工具來為initramfs指定一個檔案源。在核心配置中開啟INITRAMFS-SOURCE,並將它指向開發工作站上的一個目錄。核心的構建系統會使用這個目錄中的檔案作為initranfs映象的原始檔。讓我們使用一個最小的檔案系統來研究以下它的機制。
首先我們構造一個檔案集合,其中包含了一個最小化系統所需的檔案。initramfs應當是短小精幹的,由此我們可以基於靜態編譯的busybox來構造它。靜態編譯busybox意味著它不依賴於任何系統程式庫。除了busybox外,我們需要的檔案很少:一個代表控制檯的裝置節點,它位於/dev。還有一個指向busybox的符號連線,名為init。最後我們需要包含一個busybox的啟動指令碼,用於系統啟動後生成一個shell與我們互動。
tree ./usr/myinitramfs_root/
.