根文件系統及Busybox簡介

1.根文件系統簡介

所謂製作根文件系統,就是創建各種目錄,並且在目錄裏創建相應的文件。例如:在/bin目錄下放置可執行程序,在/lib下放置各種庫等等。

2.Busybox簡介

2.1Busybox簡介

Busybox是一個開源項目,遵循GPL v2協議。Busybox將衆多的UNIX命令集合進一個很小的可執行程序中,可以用來替代GNU fileutils、shellutils等工具集。Busybox中各種命令與相應的GNU工具相比,所能提供的選項比較少,但是也足夠一般的應用了。Busybox主要用於嵌入式系統。

Busybox在編寫過程中對文件大小進行了優化,並考慮了系統資源有限(比如內存等)的情況。與一般的GNU工具集動輒幾M的體積相比,動態鏈接的Busybox只有幾百K,即使是採用靜態鏈接也只有1M左右。Busybox按模塊設計,可以很容易地加入、去除某些命令,或增減命令的某些選項。

在創建根文件系統的時候,如果使用Busybox的話,只需要在/dev目錄下創建必要的設備節點,在/etc目錄下增加一些配置文件即可,當然,如果Busybox使用動態鏈接,那麼還需要再/lib目錄下包含庫文件。

2.2Busybox目錄結構簡介

下面是Busybox源碼目錄結構圖,接下來說說各個目錄的作用,方便以後對Busybox做裁剪的時候參考。

目錄

說明

applets

主要是實現applets框架的文件

applets_sh

一些有用的腳本,例如:dos2unix、unix2dos等

archival

與壓縮有關的命令源文件,例如:bzip2、gzip等

configs

自帶的一些默認配置文件

console-tools

與控制檯相關的一些命令,例如:setconsole

coreutils

常用的核心命令,例如:cat、rm等

editors

常用的編輯命令,例如:vi、diff等

findutils

用於查找的命令,例如:find、grep等

init

init進程的實現源文件

networking

與網絡相關的命令,例如:telnetl、arp等

shell

與shell相關的實現,例如:ash、msh等

util-Linux

Linux下常用的命令,主要是與文件系統相關的,例如:mkfs_ext2等

 

2.3init進程簡介

    Busybox中最重要的程序自然是init。   

大家都知道init進程是由內核啓動的第一個(也是唯一一個)用戶進程(進程ID爲1),init進程根據配置文件決定啓動哪些程序,例如:執行某些腳本、啓動shell或運行用戶程序等等。Init是後續所有進程的發起者,例如:init進程啓動/bin/sh程序後,我們才能夠在控制檯上輸入各種命令。

Init進程的執行程序通常都是/sbin/init,上述講到的init進程的作用只不過是/sbin/init這個程序的功能。如果我們想讓init執行自己想要的功能,那麼有兩種途徑:第一,使用自己的init程序,這包括使用自己的init替換/sbin/下的init程序或者修改傳遞給內核的參數,指定”init=xxx”這個參數,讓init環境變量指向自己的init程序;第二,就是修改init的配置文件,因爲init程序的很大一部分的功能都是按照其配置文件執行的。

一般而言,在Linux系統中有兩種init程序:BSD init和System V init。BSD和 System V是兩種版本的UNIX系統。這兩種init程序各有優缺點,現在大多數Linux發行版本使用的都是System V init。但在嵌入式系統中常使用的是Busybox集成的init程序,下面基於它進行介紹。

2.3.1內核如何啓動init進程

內核啓動的最後一步就是啓動init進程,代碼在init/main.c文件中,如下所示:


代碼並不複雜,與init啓動最強相關的就是run_init_process這個函數了,它運行指定的init程序,注意:一旦run_init_process運行創建進程成功,它將不會返回,而是通過操作內核棧進入用戶空間。所以上面並不是運行了四個init進程,而是根據優先級,一旦某一個運行成功,就不往下繼續執行了。

下面詳細描述一下該函數的執行過程:
(1)打開標準輸入、標準輸出和標準錯誤設備

Linux中最先打開的3個文件分別稱作標準輸入(stdin)、標準輸出(stdout)和標準錯誤(stderr),它們對應的文件描述符分別是0、1、2.。

如下代碼就是執行這個操作,先打開文件/dev/console作爲保準輸入,然後將文件描述符複製給文件描述符1、2,這樣使得標準輸入、標準輸出以及標準錯誤都使用/dev/console這個文件。注意代碼上面的註釋”該函數不能失敗,也就是說至少/dev/console必須存在”。


(2)如果變量ramdisk_execute_command爲空,則將其指向/init程序,如果該程序存在,則運行該程序,並且進程不會返回;如果該程序不存在,則置變量ramdisk_execute_command爲NULL,代碼片段爲:



(3)如果變量execute_command指定了要運行的程序,則運行它,並且不會返回:


(4)依次嘗試幾個常見的init,一旦某一個成功,則不返回:


(5)如果以上執行都失敗,那麼內核就掛了


    至於init執行失敗可能的原因,詳見內核文檔Documentation\init.txt。

2.3.2init的執行流程

Busybox init程序對應的源代碼在init/init.c文件中,下面先介紹其啓動過程。


內核啓動init進程的時候已經打開了”/dev/console”設備作爲控制檯,一般情況下Busybox init程序就是要/dev/console。但是如果內核啓動init進程的時候同時指定了環境變量CONSOLE或者console,則init使用環境變量所指定的設備。在Busybox中還會檢查這個指定的設備是否可以打開,如果不能打開,則使用/dev/null。

Busybox init進程只是作爲其它進程的發起者和控制着,並不需要控制檯與用戶交互,所以init進程會把它關掉,系統啓動後運行命令”ls /proc/l/fd/”可以看到該目錄爲空。Init進程創建其它子進程的時候,如果沒有指名該進程的控制檯,則該進程也是有前面確定的控制檯,至於怎麼爲進程指定控制檯就通過init的配置文件實現。

2.3.3init的配置文件

Init可以創建子進程,然而究竟應該創建哪些進程呢?這個是可以通過其配置文件定製的,init的配置文件爲/etc/inittab文件。

Inittab文件的相關文檔和示例代碼都在Busybox的examples/inittab文件中,內容如下:


上圖中標有下劃線的一行就是inittab文件中每一行內容的格式。Inittab文件中的每個條目用來定義一個子進程,並確定它的啓動方法。每一行都分爲四個字段,分別用”:”隔開,每個字段的意義如下:

(1)<id>:表示該子進程使用的控制檯,如果該字段省略,則使用與init進程一樣的控制檯。

(2)<runlevel>:該進程的運行級別,Busybox 的init程序不支持運行級別這個概念,因此該字段無意義,如果要支持runlevel意義,則建議使用System V Init程序。

(3)<action>:表示init如何控制該進程,是一個枚舉量,可能的取值及相應的意義如下表:

Action取值

執行條件

說明

sysinit

系統啓動後最先執行

只執行一次,init等它執行完後在執行下面的

wait

系統執行完sysinit進程後

只執行一次,init等它執行完後在執行下面的

once

系統執行完wait進程後

只執行一次,init進程不等待它結束

respawn

系統啓動完once進程後

Init進程發現子進程退出,則重新啓動它

 

askfirst

 

系統啓動完respawn進程後

與respawn類似,不過init進程先輸出”Please press Enter to active this console”,等待用戶輸入回車鍵後才啓動子進程

shutdown

系統關機時

——

 

restart

當Busybox配置了CONFIG_FEATURE_USE_INNITTAB,

且init進程接收到了SIGHUP信號

先重新讀取、解析inittab文件,再執行restart程序

ctraltdel

按下Ctrl+Alt_Del組合鍵時

——

 

(4)<process>:要執行的程序,可以爲可執行程序也可以是腳本,如果<process>字段前面有”-”字符,代表這個程序是可交互的,例如:/bin/sh程序。

最後給出一個inittab文件的內容:


注意:如果inittab配置文件不存在,那麼init就執行默認的配置:


3.構建自己的根文件系統

3.1編譯Busybox

現在我們開始構建自己的根文件系統,主要工作就是編譯Busybox,首先到官網下載最新的源代碼,加壓縮到自己的工作目錄,我這裏不列出目錄,下面的截圖中都包含了完整的路徑,請大家看仔細。

首先解壓縮後看看Busybox源代碼的目錄結構,如下圖:


在源代碼目錄下有幾個文件使我們必須關注的(很多開源代碼都有這幾個文件,建議在開展實際的工作之前仔細閱讀一下這幾個文件),主要是:INSTALL、README以及examples目錄和docs目錄下的文件。

Busybox可裁剪,而且支持像Linux內核那樣的圖形化配置界面,運行如下命令即可:


這個時候可能回報如下錯誤:


這個時候不必着急,之所以回報這個錯誤,是因爲我們採用的配置界面需要終端的一些特殊配置,而這些配置是需要ncurses庫的支持,所以當出現這個錯誤的時候,說明你的編譯環境中沒有安裝此庫,使用如下命令安裝好這些庫即可。


在這些庫安裝好了,之後在運行之前的”make menuconfig”命令,即可出現如下的配置界面:


    在這個界面中我們就可以進行裁剪,也就是選中自己需要的功能,其它的就不選擇。這裏有幾個配置選項比較重要,在這單獨拿出來說一下,至於完整的選項說明,請見附錄。

(1)     指定編譯後安裝的路徑

編譯完了Busybox後,我們需要安裝,安裝可以指定安裝路徑,在這個界面修改(當然,也可以在Makefile或者編譯命令指定)


    從上圖我們可以看出,Busybox默認的安裝路徑是源代碼目錄的_install目錄(該目錄不存在,安裝的時候自動創建)。

(2)     靜態/動態編譯

我們可以靜態或者動態編譯Busybox,Busybox支持Glibc和Uclibc。選擇動態編譯,使得Busybox可執行文件更小,選項開關在下圖:


經過上訴步驟之後,相比裁剪的工作已近完成了,這個時候選擇配置界面的Exit退出,這個時候會彈出對話框,詢問是否保存剛剛的配置,這裏選擇”保存”,之後就可以看到在源代碼目錄下多了一個.config文件,如下圖:


.config配置文件裏面的內容記錄了我們剛剛選中了哪些功能,內容如下:


每一個都是名值對的形式,名稱是一個環境變量,後面的值如果爲”Y”就代表選中,註釋行代表裁減掉的功能。

好了,現在配置階段的事情就做完了,接下來就是編譯Busybox了,相信大家對編譯開源代碼不會陌生,直接執行如下命令即可:


編譯之後看看源代碼目錄都生成了一些啥:


從上圖可以很清楚的看到生成了兩個可執行文件,也就是我們需要的Busybox可執行文件,編譯階段的工作也做完了。

接下來我們安裝Busybox,使用如下命令:


接下來到安裝目錄_install下看看,都安裝了些啥:


從最下面的一個”ls”命令可以看出,雖然在/bin目錄下有很多命令,但是其實只有一個真正的可執行文件,也就是我們前面的生成的Busybox文件,其它文件都是到Busybox的軟鏈接(可以在配置界面設置爲硬鏈接,這對於系統對inode數量有限制的情況下特別有用)。

至於軟鏈接,這個從”make install”安裝命令的執行過程中也可以看出來,如下圖:


好了,至此,我們的Busybox也就完成了。

雖說Busybox編譯成功了,需要的文件也生成了,但是不是意味着我們學習Busybox的過程也結束了呢?顯然不是,我們剛剛簡單執行了一個”make”命令,就編譯成功了,但是我們必須要知道”make”命令背後執行了哪些操作,這個可以從編譯過程終端的輸出看到執行流程,如下圖:


這裏編譯輸出非常多,我們主要關注其中標註1和2的兩條,分別給出解釋:

(1)     解析.config文件

這裏就是上圖標註1的那句話,主要的功能就是解析.config文件,之前可以看到.config文件中都是一些宏,這裏做的就是將整個文件中的宏分別解析出來,存放到一個.h文件中,文件的存放的路徑爲:


    注意:config目錄是編譯過程中生成的。

文件內容如下:


(2)     生成最終的配置文件

通過上面config目錄下的文件生成一個完整的.h文件,裏面是最終的一個配置文件,內容如下:

文件內容比較多,而且分爲幾個獨立的部分,我們首先來看看最前面的部分:


從內容可以看出,這就是我們最終要生成的命令的名字,將它們所有都放在一個數組中。

接下來看看該文件最後部分的內容:


從文件內容可以看出,這是上面每個命令的入口函數,命令很有特點,一眼就看出來了哦。(後面記得在這裏添加hello_busybox_main的入口函數)從這裏可以看出這裏是一個函數指針數組,根據傳入的下標選擇運行不同的函數,這就是爲什麼在Busybox中命令”ls”的運行效果等同於”busybox ls”,如下圖:


好了,最後再讓我們看看編譯完Busybox後的安裝目錄吧:


3.2向Busybox中添加新命令

接下來我們就介紹一下怎麼想Busybox中添加自己的命令,這個也就是搞清楚Busybox的組織框架。之前如果有在內核中添加驅動的同學相信在Busybox中添加新的命令難不倒大家哦。

(1)     首先選擇命令存放的路徑

Busybox目錄下有非常多的子目錄,每個目錄都放着一類命令,例如:net目錄放着與網絡相關的,shell放置着與shell相關的命令,我們這裏只是爲了舉例說明添加一個命令的流程,所以我將命令放置在如下目錄:


(2)     其次就是編寫命令源文件

我們要運行自己的命令肯定就得編寫自己的源代碼,這裏主要爲了說明流程,所以使用如下簡答源代碼:


     這裏編寫源代碼有一點一定要注意,Busybox採用統一的命名風格,這個從之前的函數指針數組也能看出,所以我這裏命令是”hello_busybox”,那麼我的函數名就一定是”hello_busybox_main”。

(3)     修改相關的編譯文件

我們將自己的源文件編譯進去之後,整個Busybox是不會理會這個文件的存在,即使你這個時候使用”make”命令編譯Busybox,也會發現上面的.c源文件並沒有被編譯,因爲我們並沒有將這個文件告訴Busybox的編譯系統,類似之前放置驅動程序需要修改內核的Kconfig文件一樣,我們也需要修改Busybox中類似的文件。

首先修改如下文件:


添加自己的命令,格式仿造其它已經存在的條目即可,修改後內容如下:


修改這裏主要是使得執行”make menuconfig”命令的時候,配置界面可以出現我們新增的命令,讓用戶對該命令可以配置,第一行是標示該命令的一個環境變量;第二行是出現在配置界面上的文字,是一個布爾量,取值爲”Y”或者”N”;第三行是這個選項的默認值,這裏默認是選中的;第四行和第五行是該命令在配置界面的幫助信息。

修改上面的文件只是讓配置界面出現我們這個命令,以及根據是否選擇置環境變量”HELLO_BUSY_BOX”爲”Y”或”N”,但是它還不能影響Busybox的編譯系統是否編譯我們的源文件,Busybox到現在甚至不知道我們的源文件叫啥名字。

接下來我們還需要修改如下文件:


修改後的內容如下:


到這裏讀者應該明白前面修改那個文件最主要的最用了,根據環境變量”HELLO_BUSYBOX”的取值,決定是否編譯我們的源文件。

到這主要的工作已經完成了,但是還有部分工作必須得做,首先想想我們的命令(也就是一個名爲hello_busybox的指向busybox的軟鏈接文件)生成了放在哪裏呢?系統中存放命令的地方很多,例如“/bin”、“/sbin”、“/usr/bin”和“/usr/sbin”等,這就需要修改下面的文件:


修改後的內容如下:


這裏我們主要關注括號裏面的三個參數:第一個是命令的名字;第二個是命令存放的路徑,第三個是命令的權限。

接下來我們還要做一件非做不可的事情,就是每個命令都有幫助信息,我們這裏也需要爲新添加的命令增加幫助信息,修改如下文件:


修改後的文件如下圖:


     好了,至此,在Busybox中添加一條新的命令該做的修改該做都做完了,剩下的就是測試添加的命令是否生效,是否可用。

(4)     編譯、測試

首先是執行配置操作,”make menuconfig”命令,出現頂層的配置界面,選中下圖的那一條,按下回車鍵:


進入子條目後就很容易看到我們添加的那條命令了,如下圖中選中的那條:


做好了配置工作之後我們就可以執行編譯操作了,在看編譯過程之前,先讓我們看看有沒有生成我們的配置文件,如下圖:


文件內容如下:


這裏有個很奇怪的問題,我們新加的命令的名字是”hello_busybox”,那麼生成的配置文件應該是”hello_busybox.h”,但是各位看官仔細看看上面出現了什麼情況:竟然在config目錄下生成了hello子目錄,然後在裏面放置”busybox.h”文件,相信大家也猜到了規律,那就是Busybox會將名字做拆分,以”_”爲分割字符,最後一個纔是文件名,前面的都是子目錄,這個我沒有再去驗證,但我認爲應該是這樣的。

好了,接下來我們就執行”make”命令,截圖如下:


從上圖中可以看到,我們新加的命令成功生成,也安裝的目錄也正確。

接下來我們就去執行一下我們的命令,如下圖:


從上面圖中三條命令的執行情況來看,我們添加命令成功。

4.附錄

4.1Busybox實現的簡單分析

在這裏,我們來簡要的分析一下Busybox的實現過程,在前面的第3點中已經提及了一部分這方面的內容。

在前面也分析了Busybox的目錄結構,那種分法是比較僵硬的,因爲完全是按照目錄來劃分的,其實如果要更好的理解Busybox的實現,那麼我們應該將它劃分爲兩個部分:第一,這部分主要是各個命令(applets)的實現,其實大家也發現了,很多目錄都屬於這部分,只不過它們按照功能細分了,例如網絡命令(networking目錄)、編輯命令(editors目錄)等,這部分也可以理解爲是Busybox(各個命令)的啓動代碼部分;第二部分則是libbb目錄下的內容,也就是Busybox(各個命令)的共享代碼部分。

下面我們分別來介紹這兩部分的主要內容:

4.1.1applets的實現

目錄”applets”包含了Busybox的啓動代碼(applets.c和Busybox.c),以及幾個包含獨立命令的子目錄。

Busybox從applets/busybox.c文件中的main()函數開始執行,該main函數將變量applet_name賦值爲argv[0],然後調用applets/applets.c文件中的run_applet_and_exit()函數繼續執行。run_applet_and_exit()函數使用applets[]數組(定義在include/busybox.h中,在include/applets.h中填充內容)將程序的控制權傳遞給APPLET_main()函數(例如:cat_main()或sed_main())。獨立的applet命令從這裏開始接管執行。

這就是爲什麼Busybox下的不同名稱的命令調用不同的功能:main()函數使用argv[0]作爲參數在applets[]數組中查找合適的指向APPLET_main()函數的函數指針。

Busybox中的applets同樣可以通過複用器”busybox”applet(查看libbb/appletlib.c文件中的函數Busybox_main())調用,以及通過單獨的shell(在shell/*.c中使用grep命令查找SH_STANDALONE)。關於使用這兩種機制調用命令更多的信息可以查看官網信息,其實它們只是通過不同的路徑調用APPLET_main()函數。

命令(applet)子目錄(archival,console-tools, coreutils, debianutils, e2fsprogs, editors, findutils, init, loginutils,miscutils, modutils, networking, procps, shell, sysklogd, and util-linux)對應着menuconfig中的子菜單的配置項。每一個子目錄都包含實現相應子菜單命令的代碼,每一個子目錄下有一個Config.src文件,用於產生menuconfig菜單,有一個Kbuild.src文件用於生產類似Makefile功能的文件。

運行時的—help信息是保存在usage_message[]數組中的,該數組通過從usage.h中獲取幫助信息,在applets/applets.c中初始化該數組。在編譯的過程中,這些幫助信息同樣被用於在docs目錄下產生Busybox的文檔(html,txt和man頁面格式)

4.1.2libbb的實現

絕大多數非啓動且在各個Busybox命令(applets)中共享的代碼都放在libbb目錄下。該目錄多年未清理,比較雜亂。如果有人想尋找一個好的項目參加到Busybox的開發中,那麼將libbb進行文檔結構化將會是十分有幫助的,而且是個不錯的鍛鍊機會。  

在libbb的共同主題包括分配功能測試失敗和中止程序的錯誤消息,以便調用者不用測試返回值(xmalloc(),xstrdup()等),經過封裝的open(),close(),read(),write(),這些經過封裝的函數可以測試自己的失敗和/或自動重試,也包含鏈表管理功能的函數(llist.c),命令行參數的解析(getopt32.c),和一大堆其它的內容。

4.2Busybox配置選項說明

下面說一下Busybox中主要的配置項及其含義,主要是頂層的配置項:頂層的配置項分爲兩類,第一類是支持的命令,這部分其實也就是各個子目錄的配置,在2.2Busybox目錄結構簡介一節已經提到了;第二類就是Busybox自身相關的,例如:編譯選項、安裝路徑等,這部分在3.1編譯Busybox一節已經提到了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章