Linux深入學(xué)習(xí):如何分析根文件系統(tǒng)
掃描二維碼
隨時(shí)隨地手機(jī)看文章
整體框架:
1)在Linux kernel的源代碼中,對(duì)如何啟動(dòng)應(yīng)用程序有著明確的定義。首先我們需要掛載根文件系統(tǒng),只有正確掛載了根文件系統(tǒng),才能夠從根文件系統(tǒng)中讀出應(yīng)用程序。我們啟動(dòng)的第一個(gè)程序就是init程序。init進(jìn)程完成了對(duì)應(yīng)用程序的各項(xiàng)配置(進(jìn)程ID、執(zhí)行時(shí)機(jī)、命令、終端、下一個(gè)執(zhí)行的進(jìn)程等),并最終依據(jù)配置執(zhí)行了應(yīng)用程序。 2)要執(zhí)行應(yīng)用程序,首先進(jìn)行配置。配置文件inittab里有著對(duì)應(yīng)用程序的詳細(xì)配置,這些都是C文件。init進(jìn)程讀出配置、分析配置并配置應(yīng)用程序、配置C庫(kù)(用到很多C庫(kù)里的函數(shù))。最后執(zhí)行程序。 3)Busybox是一個(gè)遵循GPL v2協(xié)議的開源項(xiàng)目。Busybox將眾多的UNIX命令集合進(jìn)一個(gè)很小的可執(zhí)行程序中,可以用來(lái)替換GNU fileutils、shellutils等工具集。Busybox中各種命令與相應(yīng)的GNU工具相比,所能提供的選項(xiàng)較少,但是能夠滿足一般應(yīng)用。Busybox為各種小型的或者嵌入式系統(tǒng)提供了一個(gè)比較完全的工具集。更多詳細(xì)介紹參考README。
我們執(zhí)行
命令
的時(shí)候?qū)嶋H是執(zhí)行busybox 命令
我們查看軟連接
內(nèi)核檢測(cè)根文件系統(tǒng)并啟動(dòng)init
內(nèi)核啟動(dòng)的最后一步就是啟動(dòng)init進(jìn)程,代碼在init/main.c/init_post函數(shù)
static?int?noinline?init_post(void) { ????free_initmem(); ????unlock_kernel(); ????mark_rodata_ro(); ????system_state?=?SYSTEM_RUNNING; ????numa_default_policy(); ????if?(sys_open((const?char?__user?*)?"/dev/console",?O_RDWR,?0)?<?0) ????????printk(KERN_WARNING?"Warning:?unable?to?open?an?initial?console.n"); ????(void)?sys_dup(0); ????(void)?sys_dup(0); ????if?(ramdisk_execute_command)?{ ????????run_init_process(ramdisk_execute_command); ????????printk(KERN_WARNING?"Failed?to?execute?%sn", ????????????????ramdisk_execute_command); ????} ????/* ?????*?We?try?each?of?these?until?one?succeeds. ?????* ?????*?The?Bourne?shell?can?be?used?instead?of?init?if?we?are ?????*?trying?to?recover?a?really?broken?machine. ?????*/ ????if?(execute_command)?{ ????????run_init_process(execute_command); ????????printk(KERN_WARNING?"Failed?to?execute?%s.??Attempting?" ????????????????????"defaults...n",?execute_command); ????} ????run_init_process("/sbin/init"); ????run_init_process("/etc/init"); ????run_init_process("/bin/init"); ????run_init_process("/bin/sh"); ????panic("No?init?found.??Try?passing?init=?option?to?kernel."); }
內(nèi)核啟動(dòng)init進(jìn)程的過程如下:
(1)打開標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤設(shè)備open("/dev/console")
嘗試打開/dev/console設(shè)備文件,如果成功即為init進(jìn)程標(biāo)準(zhǔn)輸入設(shè)備。(void) sys_dup(0); (void) sys_dup(0);
將文件描述符0復(fù)制給文件描述符1、2,所以標(biāo)準(zhǔn)輸入、輸出、錯(cuò)誤都對(duì)應(yīng)同一個(gè)文件(設(shè)備)
(2)如果execute_command
變量指定了要運(yùn)行的程序,啟動(dòng)它。
if?(execute_command)?{ run_init_process(execute_command); }
其中execute_command為命令行參數(shù),在我們uboot傳給內(nèi)核的參數(shù)中,init設(shè)置了init=/linuxrc,所以這里的execute_command就等于/linuxrc。
?如果傳值成功則執(zhí)行run_init_process,否則打印printk(KERN_WARNING “Failed to execute %s. ?Attempting “”defaults…n”, execute_command);
?并接著往下執(zhí)行,接著檢測(cè)其他位置的init進(jìn)程,若成功則執(zhí)行,失敗則接著往下檢測(cè),直到找到合適的init進(jìn)程或者沒找到則打印panic(“No init found. ?Try passing init= option to kernel.”);
那么這里我們可以先使用nand erase root
擦除root分區(qū),也就是說擦除根文件系統(tǒng),然后啟動(dòng)只有bootloader和kernel的系統(tǒng)。在結(jié)果是否和代碼中說明的一致,結(jié)果Linux kernel在啟動(dòng)過程中,打印出了如下的信息:
VFS:?Mounted?root?(yaffs?filesystem).?? Freeing?init?memory:?140K Warning:?unable?to?open?an?initial?console. Failed?to?execute?/linuxrc.??Attempting?defaults... Kernel?panic?-?not?syncing:?No?init?found.??Try?passing?init=?option?to?kernel.
首先是VFS:掛載了根文件系統(tǒng),可能大家會(huì)問,不是剛剛已經(jīng)擦除了根文件系統(tǒng),為什么說這里掛載了?
?這是因?yàn)楫?dāng)我們擦除了根文件系統(tǒng)的root分區(qū)后,Linux kernel認(rèn)為它是任意格式的根文件系統(tǒng)(其實(shí)分區(qū)里面什么都沒有),而默認(rèn)的又是yaffs格式,所以這里說掛載了yaffs格式的根文件系統(tǒng)。
?這里的warning難道不是和我們init_post函數(shù)中的printk(KERN_WARNING “Warning: unable to open an initial console.n”);相對(duì)應(yīng)嗎?
?同理,F(xiàn)ailed to execute /linuxrc. ?Attempting defaults…和printk(KERN_WARNING “Failed to execute %s. ?Attempting “”defaults…n”, execute_command);相對(duì)應(yīng)。
?Kernel panic - not syncing: No init found. ?Try passing init= option to kernel.和panic(“No init found. ?Try passing init= option to kernel.”);相對(duì)應(yīng)。
?so=>這證明我們的分析是正確的。
Busybox init進(jìn)程的啟動(dòng)過程
其中與構(gòu)建根文件系統(tǒng)關(guān)系密切的是控制臺(tái)的初始化、對(duì)inittab文件的解釋執(zhí)行。
內(nèi)核啟動(dòng)init進(jìn)程時(shí)已經(jīng)打開“/dev/console”設(shè)備作為控制臺(tái),一般情況下Busybox init程序就使用/dev/console。
?但是如果內(nèi)核啟動(dòng)init進(jìn)程的同時(shí)設(shè)置了環(huán)境變量CONSOLE或console,則使用環(huán)境變量所指定的設(shè)備。
?在Busybox init程序中,還會(huì)檢查這個(gè)設(shè)備是否可以打開,如果不能打開則使用”/dev/null”。
/etc/inittab文件的相關(guān)文檔和示例代碼都在Busybox的examples/inittab文件中,我們來(lái)一探究竟
查看inittab文件得知inittab格式: Format?for?each?entry: #:::#id:????????The?id?field?is?used?by?BusyBox?init?to?specify?the?controlling?tty?for?the?specified?process?to?run?on.?? #runlevels:??The?runlevels?field?is?completely?ignored. #action:?????Valid?actions?include:???sysinit,?respawn,?askfirst,?wait,?once, #????????????????????????????????????????????restart,?ctrlaltdel,?and?shutdown. #process:????Specifies?the?process?to?be?executed?and?it's?command?line. /*******************************解析************************************/ 從默認(rèn)的new_init_action反推出默認(rèn)的配置文件: #?inittab格式: #:::#?id?=>?/dev/id,用作終端:stdin,stdout,stderr:printf,?scanf,?err(即標(biāo)準(zhǔn)輸入、輸出、錯(cuò)誤設(shè) #?備),如果省略,則使用與Init進(jìn)程一樣的控制臺(tái)。 #?runlevels?:?忽略? #?action??????:執(zhí)行時(shí)機(jī)?sysinit,?respawn,?askfirst,?wait,?once, #????????????????????????restart,?ctrlaltdel,?and?shutdown. #?process?????:應(yīng)用程序或腳本,如果前有“-”字符,這個(gè)程序被稱為“交互的”。
在init_main函數(shù)中,調(diào)用了parse_inittab函數(shù)來(lái)讀取配置文件inittab。如果根文件系統(tǒng)中沒有/etc/inittab文件,Busybox init程序?qū)⑹褂媚J(rèn)的inittab條目。這里我們可以通過默認(rèn)的配置語(yǔ)句,倒推出默認(rèn)的配置文件內(nèi)容。
DIR:?init.c-parse_inittab函數(shù) /*?Reboot?on?Ctrl-Alt-Del?*/ ????????new_init_action(CTRLALTDEL,?"reboot",?""); ????????/*?Umount?all?filesystems?on?halt/reboot?*/ ????????new_init_action(SHUTDOWN,?"umount?-a?-r",?""); ????????/*?Swapoff?on?halt/reboot?*/ ????????if?(ENABLE_SWAPONOFF)?new_init_action(SHUTDOWN,?"swapoff?-a",?""); ????????/*?Prepare?to?restart?init?when?a?HUP?is?received?*/ ????????new_init_action(RESTART,?"init",?""); ????????/*?Askfirst?shell?on?tty1-4?*/ ????????new_init_action(ASKFIRST,?bb_default_login_shell,?""); ????????new_init_action(ASKFIRST,?bb_default_login_shell,?VC_2); ????????new_init_action(ASKFIRST,?bb_default_login_shell,?VC_3); ????????new_init_action(ASKFIRST,?bb_default_login_shell,?VC_4); ????????/*?sysinit?*/ ????????new_init_action(SYSINIT,?INIT_SCRIPT,?""); /*******************************解析************************************/ ::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount?-a?-r ::shutdown:/sbin/swapoff?-a ::restart:/sbin/init ::askfirst:-/bin/sh tty2::askfirst:-/bin/sh tty3::askfirst:-/bin/sh tty4::askfirst:-/bin/sh? ::sysinit:/etc/init.d/rcS
這里涉及到了一個(gè)函數(shù) new_init_action 。 它實(shí)際上的工作就是把各個(gè)程序的執(zhí)行時(shí)機(jī)、命令行、控制臺(tái)參數(shù)分別賦值給結(jié)構(gòu)體,并把這些結(jié)構(gòu)體組成一個(gè)單鏈表。這也就是我們所說的配置。 它的聲明是:static void new_init_action(int action, const char *command, const char *cons);這三個(gè)參數(shù)不正是inittab配置文件中的配置命令嗎?他們分別對(duì)應(yīng)于來(lái)看看new_init_action函數(shù):
DIR:init.c-new_init_action函數(shù) static?void?new_init_action(int?action,?const?char?*command,?const?char?*cons) { ????struct?init_action?*new_action,?*a,?*last; ????if?(strcmp(cons,?bb_dev_null)?==?0?&&?(action?&?ASKFIRST)) ????????return; ????/*?Append?to?the?end?of?the?list?*/ ????for?(a?=?last?=?init_action_list;?a;?a?=?a->next)?{ ????????/*?don't?enter?action?if?it's?already?in?the?list, ?????????*?but?do?overwrite?existing?actions?*/ ????????if?((strcmp(a->command,?command)?==?0) ?????????&&?(strcmp(a->terminal,?cons)?==?0) ????????)?{ ????????????a->action?=?action; ????????????return; ????????} ????????last?=?a; ????} ????new_action?=?xzalloc(sizeof(struct?init_action)); ????if?(last)?{ ????????last->next?=?new_action; ????}?else?{ ????????init_action_list?=?new_action; ????} ????strcpy(new_action->command,?command); ????new_action->action?=?action; ????strcpy(new_action->terminal,?cons); ????messageD(L_LOG?|?L_CONSOLE,?"command='%s'?action=%d?tty='%s'n", ????????new_action->command,?new_action->action,?new_action->terminal); } /*?Set?up?a?linked?list?of?init_actions,?to?be?read?from?inittab?*/ struct?init_action?{ ? struct?init_action?*next; ?int?action; ?pid_t?pid; ?char?command[INIT_BUFFS_SIZE]; ?char?terminal[CONSOLE_NAME_SIZE]; };
new_init_action函數(shù)用于配置,參數(shù)為執(zhí)行時(shí)機(jī)、命令行、控制臺(tái)。
?結(jié)構(gòu)體指針new_action開始指向上一個(gè)配置過的程序(其存儲(chǔ)在結(jié)構(gòu)體,參數(shù)是上一個(gè)程序的執(zhí)行時(shí)機(jī)、命令行、控制臺(tái)),這里首先進(jìn)行了一個(gè)If判斷,如果控制臺(tái)等于bb_dev_null(宏定義等于 /dev/null)且action為ASKFIRST那么直接返回,不進(jìn)行任何配置。
?接著這個(gè)for循環(huán)算是這個(gè)函數(shù)的一個(gè)重點(diǎn)吧,首先令結(jié)構(gòu)體指針init_action_list賦值給a和last。
?這里的init_action_list(宏定義為NULL)開始為NULL,后來(lái)指向第一個(gè)配置的程序。
?也就是說,遍歷所有配置過的程序,如果這個(gè)程序之前被配置過(命令行和控制臺(tái)同時(shí)等于當(dāng)前遍歷的程序),那么執(zhí)行時(shí)機(jī)action被重新賦值為當(dāng)前值。
?通俗的說,這個(gè)for為了避免程序重復(fù)配置,查找之前配置過的程序有沒有當(dāng)前要配置的程序,如果有,則只改變其執(zhí)行時(shí)機(jī)action。命令行和控制臺(tái)不變。
?如果沒有,接下來(lái)為new_action重新分配內(nèi)存,并且給它賦值,令它的各項(xiàng)信息等于當(dāng)前的程序。在上面的if語(yǔ)句中,last->next=new_action,也就是說,將所有程序的配置結(jié)構(gòu)體連成一個(gè)單鏈表。
new_init_action函數(shù)講解完畢。
經(jīng)過上面的講解,我們明白了Linux根文件系統(tǒng)中,對(duì)于程序的配置是在parse_inittab函數(shù)完成的,它打開配置文件inittab,將程序信息一一填入結(jié)構(gòu)體init_action,并將它們連接成單鏈表?,F(xiàn)在配置已經(jīng)完成,下一步是執(zhí)行了。接著看init_main中的代碼是怎樣執(zhí)行應(yīng)用程序的:
busybox->?init_main ????????????parse_inittab ????????????????file?=?fopen(INITTAB,?"r");?//打開配置文件/etc/inittab ????????????????new_init_action?????//?①?創(chuàng)建一個(gè)init_action結(jié)構(gòu),填充 ????????????????????????????????????//?②?把這個(gè)結(jié)構(gòu)放入init_action_list鏈表 ????????????run_actions(SYSINIT); ????????????????waitfor(a,?0);??????//?執(zhí)行應(yīng)用程序,等待它執(zhí)行完畢 ????????????????????run(a)??????????//?創(chuàng)建process子進(jìn)程 ????????????????????waitpid(runpid,?&status,?0);?//?等待它結(jié)束 ????????????????delete_init_action(a);//?在init_action_list鏈表里刪除????????????? ????????????run_actions(WAIT); ????????????????waitfor(a,?0);??????//?執(zhí)行應(yīng)用程序,等待它執(zhí)行完畢 ????????????????????run(a)??????????//?創(chuàng)建process子進(jìn)程 ????????????????????waitpid(runpid,?&status,?0);?//?等待它結(jié)束 ????????????????delete_init_action(a);//?在init_action_list鏈表里刪除? ????????????run_actions(ONCE); ????????????????run(a); ????????????????delete_init_action(a); ????????????while(1)?{ ????????????????run_actions(RESPAWN); ????????????????????if?(a->pid?==?0)?{ ????????????????????????a->pid?=?run(a); ????????????????????} ????????????????run_actions(ASKFIRST); ????????????????????if?(a->pid?==?0)?{ ????????????????????????a->pid?=?run(a); ????????????????????????????????打印:Please?press?Enter?to?activate?this?console. ????????????????????????????????等待回車 ????????????????????????????????創(chuàng)建子進(jìn)程 ????????????????????} ????????????????wpid?=?wait(NULL);??//?等待子進(jìn)程退出 ????????????????while?(wpid?>?0)?{ ????????????????????a->pid?=?0;?????//?退出后,就設(shè)置pid=0 ????????????????} ????????????}
在/etc/inittab文件的控制下,init進(jìn)程的行為總結(jié)如下:
① 在系統(tǒng)啟動(dòng)前期,init進(jìn)程首先啟動(dòng)為sysinit、wait、once的3類子進(jìn)程。
② 在系統(tǒng)正常運(yùn)行期間,init進(jìn)程首先啟動(dòng)為respawn、askfirst的兩類子進(jìn)程。
③ 在系統(tǒng)退出時(shí),執(zhí)行為shutdown、restart、ctrlaltdel的三類子進(jìn)程(之一或全部)
從而我們可以總結(jié)出來(lái)最小的根文件系統(tǒng)由5部分組成:
1./dev/console or
/dev/null
2.init => busybox
3./etc/inittab
4.配置文件指定的程序
5.C庫(kù)
busybox的配置、編譯和安裝
打開busybox自帶的INSTALL文件查看我們?cè)撛鯓优渲谩⒕幾g和安裝busybox。
Building: ========= The?BusyBox?build?process?is?similar?to?the?Linux?kernel?build: ??make?menuconfig?????#?This?creates?a?file?called?".config" ??make????????????????#?This?creates?the?"busybox"?executable ??make?install????????#?or?make?CONFIG_PREFIX=/path/from/root?install The?full?list?of?configuration?and?install?options?is?available?by?typing: ??make?help
1.配置
進(jìn)入busybox文件夾make menuconfig
生成配置文件.config
2.編譯
由于我們文件系統(tǒng)是給嵌入式板子用的,先修改Busybox的Makefile,使用交叉編譯器。
修改前
ARCH?????????=?$(SUBARCH) CROSS_COMPILE????=
修改后
ARCH?????????=?$(SUBARCH) CROSS_COMPILE????=?arm-linux-
然后make
3.安裝
注意:如果你是在虛擬機(jī)上安裝busybox,安裝不可直接執(zhí)行make INSTALL,必須在虛擬機(jī)下自己創(chuàng)建一個(gè)文件夾,將安裝路徑指向這個(gè)文件夾的路徑。再執(zhí)行make CONFIG_PREFIX=dir_path install
否則會(huì)破壞系統(tǒng)。
注:除bin/busybox外,其他文件都是到bin/busybox的符號(hào)連接。busybox是所有命令的集合體,這些符號(hào)連接文件可以直接運(yùn)行。比如在開發(fā)板上,運(yùn)行“l(fā)s”命令和”busybox ls”命令是一樣的。