本文將介紹如何構建一個最簡單的根文件系統,並且初步分析內核如何執行第一個內核程序。
掛接根文件系統
在掛接根文件系統之前,需要制作根文件系統。根文件系統里面需要一些基本的命令,目錄和設備文件,下面來介紹如何使用busybox來制作根文件系統。
我們都知道,init進程是系統啟動后執行的第一個應用程序,根據一般的Linux應用程序配置結構,一個可執行文件通常搭配一個對於的配置文件,例如samba功能對於/etc/samba/smb.conf配置文件,nfs功能對於/dev/exports配置文件,那么init初始化功能肯定也是對於一個配置文件,這個配置文件叫做/etc/inittab.配置文件根據不同的級別,指定在不同的情況下,執行不同的應用程序。
通過Busybox自帶的配置文件格式介紹,我們了解到inittab的文件格式:
Format for each entry: <id>:<runlevels>:<action>:<process>
id項 : 每一個id會變成 /dev/id ,用作程序的輸入輸出文件所使用的終端設備,例如id=s3c2410_serial0,則對於終端為s3c2410_serial0
不需要終端輸出的可以設置為空
runlevels項:用於啟動級別的控制,在arm-linux中可省略不寫。
action項: 執行應用程序執行時機,只能設置為幾個有效的時機,busybox支持如下幾個時間點。
Valid actions include: sysinit[系統引導時啟動], respawn[只要進程一停止,該進程就重新啟動], askfirst, wait[進程只阻塞運行一次], once[進程只運行一次], restart, ctrlaltdel, and shutdown.
askfirst :從名字上可以知道,該觸發點是需要先向用戶發出提問,看是否需要執行。
ctraltdel:當用戶按下ctrl+alt+del時,內核會向init進程發起SIGINT信號,當init進程接收到此信號,會執行指定的ctrlaltdel類型的應用程序,默認執行reboot命令。
process:指定應用程序全路徑
如果用戶沒有在根文件系統中設置/etc/inittab文件,busybox會提供一個默認的配置選項,其等效於如下配置。
busybox會按照sysinit--->wait---->once---->[respawn--->askfirst----> respawn----> askfirst—>… ]
除了busybox提供的基本linux文件框架之外,根文件系統還需要程序運行庫。
有了上面的知識鋪墊,下面來構建最小根文件系統,那么最小根文件系統里面應該有哪些呢?
1. /dev/console . /dev/null
2. /sbin/init ----> busybox
3. /etc/inittab 以及其中定制的應用程序
4. 運行時的C庫
制作根文件系統的操作步驟:
1. 配置編譯busybox
我這里選用的是busybox-1.7.0,編譯步驟和編譯kernel差不多,先make menuconfig,再make ,最后make install。這里面,需要注意如下幾點:
如果在menuconfig中無法配置交叉編譯工具,那么需要在 Makefile中修改,指定特定的工具。
添加命令行代碼補全功能: Busybox Settings--->Busybox Library Tuning---> Tab completion
是否選用靜態連接: Busybox Settings ----> Build Options ----> Build Busybox as a static binary 一般都設置成動態庫
是否支持熱插拔:Linux System Utilities -----> mdev
然后make,執行 make CONFIG_PREFIX=../test_busybox_compile install ,千萬不要直接make install,這樣會覆蓋PC機上面的原有的代碼。安裝完成后結果如下:
busybox編譯的結果有bin目錄,sbin目錄和usr目錄,根目錄下面還有一個linuxrc鏈接文件,指向bin/busybox。查看一下所有生成的文件,他們都是鏈接文件,均指向/bin/busybox。這樣的目錄構造對於啟動一個正常的linux來說還是不夠的,下面繼續制作。
2. 添加其他必須的文件
添加基本的設備節點,仿照當前系統的console設備節點和null設備節點,在busybox的目錄下也建立對於的節點。
3. 構造自己的啟動配置文件 /etc/inittab
為了簡單起見,這里面只設置啟動時開啟shell輸入。首先創建etc目錄,然后執行如下操作。
4. 安裝C庫
將交叉編譯工具鏈中的所有動態鏈接庫都拷貝到busybox的安裝目錄中,先創建lib目錄,然后執行如下操作:
本機交叉編譯工具安裝在/opt目錄下,在拷貝C庫時要注意,-d選項,這個選項使得拷貝過程中,鏈接文件保持文件屬性不變化,否則,鏈接文件的內容將是所指向的全部內容,而不在是鏈接文件本身。
5. 制作鏡像文件
根文件系統的格式一般有兩種,一種是yaffs格式,這種有兩類,一類是針對小頁512字節的nand flash文件系統,稱為yaffs1,另一類是針對大頁2K的nand flash文件系統。這里根據硬件的情況,采用的是yaffs2格式的文件系統。
生成最小根文件系統鏡像,然后燒寫到板子上去,燒寫命令如下:
tftp 0x30000000 test_busybox_compile_fs.yaffs2
nand erase root ; nand write.yaffs 0x30000000 0x00260000 $(filesize); reset
啟動后顯示如下:
經過上述幾個步驟的操作,一個最簡單的根文件系統已經建立好了,此后,可以以此為基礎,逐漸添加功能。
附加功能
上面制作的最小根文件系統有很多不完善的地方,在開機之后,需要自動執行一些掛載命令。
1。 創建proc目錄
在/etc/inittab里面添加這句話, ::sysinit:/etc/init.d/rcS,使得開機后自動執行rcS腳本文件。
在rcS腳本文件中,添加 mount –t proc none /proc 就可以開啟自動掛載proc虛擬文件系統
2. 如果需要掛載多個文件系統,推薦使用mount -a選項,這個命令會讀取etc/fstab配置文件內的掛載命令。
此時,修改rcS中的文件,改成mount -a,在etc/fstab目錄下創建上述文件內容。
3. 使能熱插拔功能 mdev
根據Busybox中的mdev.txt中的說明,我們知道,使能mdev功能需要做如下操作:下述操作均在根文件系統跟目錄
mkdir sys & mount –t sysfs sysfs /sys # mdev通過sysfs文件系統獲得設備信息
mount –t tmpfs mdev /dev # 使用內存文件系統,減少對flash的讀寫
mkdir /dev/pts # devpts支持外部網絡連接(telnet)的虛擬終端
mount –t devpts devpts /dev/pts
echo /bin/mdev > /proc/sys/kernel/hotplug # 設置內核熱拔插事件回調程序
mdev –s #在/dev目錄下,生成內核目前支持的所有設備節點
上述掛載相關的命令可以寫在fstab中,其他執行命令可以寫在rcS啟動腳本中,最后結果如下:
fstab:
rcS:
按照上述操作進行制作燒寫后,一個基本架構比較完整的根文件系統就全部建立好了。
知識介紹:mdev工作原理
執行mdev -s時,mdev掃描/sys/class 和 /sys/block中兩個目錄下的dev屬性文件,從該dev屬性文件中,獲取設備編號,並以包含該dev屬性文件的目錄名稱作為設備名,device_name。而/sys/class 和 device_name 之間的那部分目錄成為subsystem,
[subsystem] [device_name] dev
例如:cat /sys/class/tty/tty0/dev 里面的內容為4:0 【major:minor】,那么subsystem為tty,device_name為tty0
mdev會根據此信息,在dev目錄下創建相應的設備文件
當mdev因uevent事件(以前叫hotplug事件)被調用時,mdev通過由uevent事件傳遞給它的環境變量獲得到兩個變量,
一個是 引發該uevent事件的action
另一個是 該設備所在的device path。
mdev判斷action,若是add,則表示有新設備(虛擬設備或者是物理設備)加入系統,mdev會通過設備路徑下的dev屬性文件獲得設備編號,然后根據路徑相關信息在/dev目錄下建立設備節點。若動作是remove,即表示設備已從系統中移除,則刪除/dev/目錄下的設備節點。
由上可知,要想發揮mdev自動創建設備節點的功能,必須有三個條件。
1. 在/sys/class/的某一個subsystem下
2. 在subsystem下創建一個以設備名device_name作為名稱的目錄
3. 在此目錄里面,包含一個dev屬性文件,內容以”major:minor\n”的形式輸出設備編號。
一個類class可以看成是一個容器,這個大容器里面包含並管理很多class_device,每一個class_device都對於着一個具體的設備。
優化操作:
按照上面制作出來的根文件系統,還有很多可以優化的地方,比如說,可以按照實際需要裁剪文件系統中的C庫,可以使用arm-linux-strip優化lib下面的動態鏈接庫和bin/busybox
如何執行第一個程序
1: static int noinline init_post(void)
2: {
3: free_initmem();
4: unlock_kernel();
5: mark_rodata_ro();
6: system_state = SYSTEM_RUNNING;
7: numa_default_policy();
8:
9: /*hao: open console device */
10: if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
11: printk(KERN_WARNING "Warning: unable to open an initial console.\n");
12:
13: (void) sys_dup(0); /*hao: copy an opened file num,make two file num pointed to the same file. */
14: (void) sys_dup(0); /*make 1(standard output) and 2(error output) point to 0(standard input)*/
15:
16: if (ramdisk_execute_command) {
17: run_init_process(ramdisk_execute_command);
18: printk(KERN_WARNING "Failed to execute %s\n",
19: ramdisk_execute_command);
20: }
21:
22: /*
23: * We try each of these until one succeeds.
24: *
25: * The Bourne shell can be used instead of init if we are
26: * trying to recover a really broken machine.
27: */
28: /*
29: hao: execute_command equal the bootargs named init. e.g : init=/linuxrc
30: then execute_command= = "/linuxrc
31: "*/
32: if (execute_command) {
33: run_init_process(execute_command);
34: printk(KERN_WARNING "Failed to execute %s. Attempting "
35: "defaults...\n", execute_command);
36: }
37:
38: /*hao: if we don't define init cmd, then we call /sbin/init to start system. */
39: run_init_process("/sbin/init");
40: run_init_process("/etc/init");
41: run_init_process("/bin/init");
42: run_init_process("/bin/sh");
43:
44: panic("No init found. Try passing init= option to kernel.");
45: }
在內核啟動的后面,執行init_post,里面會打開一個終端設備(這里指的是/dev/console),然后將標准輸出文件和錯誤輸出文件指向標准輸入文件所指向的console設備中去。
ramdisk_execute_command為uboot傳給kernel的rdinit參數,這里為空,所以ramdisk_execute_command也為空。
execute_command為uboot傳給kernel的init參數,這里為/linuxrc,所以execute_command=linuxrc。
如果uboot沒有定義rdinit和init參數,那么kernel會搜索並依此執行/sbin/init,/etc/init…直到某一個程序執行成功,如果都沒有找到,則會顯示kernel panic,打印對應的提示信息。
參考鏈接: