天天看點

busybox詳解

目錄

1.根檔案系統簡介...2

2.Busybox簡介...2

2.1Busybox簡介...2

2.2Busybox目錄結構簡介...2

2.3init程序簡介...3

3.建構自己的根檔案系統...9

3.1編譯Busybox.9

3.2向Busybox中添加新指令...19

4.附錄...26

4.1Busybox實作的簡單分析...26

4.2Busybox配置選項說明...27

                                 Powered By chenlong12580

                                                                 4/5/2013

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檔案中,如下所示:

busybox詳解

代碼并不複雜,與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必須存在”。

busybox詳解

(2)如果變量ramdisk_execute_command為空,則将其指向/init程式,如果該程式存在,則運作該程式,并且程序不會傳回;如果該程式不存在,則置變量ramdisk_execute_command為NULL,代碼片段為:

busybox詳解
busybox詳解

(3)如果變量execute_command指定了要運作的程式,則運作它,并且不會傳回:

busybox詳解

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

busybox詳解

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

busybox詳解

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

2.3.2init的執行流程

Busybox init程式對應的源代碼在init/init.c檔案中,下面先介紹其啟動過程。

busybox詳解

核心啟動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檔案中,内容如下:

busybox詳解

上圖中标有下劃線的一行就是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”,等待使用者輸入Enter鍵後才啟動子程序
shutdown 系統關機時 ——
restart

當Busybox配置了CONFIG_FEATURE_USE_INNITTAB,

且init程序接收到了SIGHUP信号

先重新讀取、解析inittab檔案,再執行restart程式
ctraltdel 按下Ctrl+Alt_Del組合鍵時 ——

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

最後給出一個inittab檔案的内容:

busybox詳解

注意:如果inittab配置檔案不存在,那麼init就執行預設的配置:

busybox詳解

3.建構自己的根檔案系統

3.1編譯Busybox

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

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

busybox詳解

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

Busybox可裁剪,而且支援像Linux核心那樣的圖形化配置界面,運作如下指令即可:

busybox詳解

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

busybox詳解

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

busybox詳解

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

busybox詳解

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

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

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

busybox詳解

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

(2)     靜态/動态編譯

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

busybox詳解

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

busybox詳解

.config配置檔案裡面的内容記錄了我們剛剛選中了哪些功能,内容如下:

busybox詳解

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

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

busybox詳解

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

busybox詳解

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

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

busybox詳解

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

busybox詳解

從最下面的一個”ls”指令可以看出,雖然在/bin目錄下有很多指令,但是其實隻有一個真正的可執行檔案,也就是我們前面的生成的Busybox檔案,其它檔案都是到Busybox的軟連結(可以在配置界面設定為硬連結,這對于系統對inode數量有限制的情況下特别有用)。

至于軟連結,這個從”make install”安裝指令的執行過程中也可以看出來,如下圖:

busybox詳解

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

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

busybox詳解

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

(1)     解析.config檔案

這裡就是上圖示注1的那句話,主要的功能就是解析.config檔案,之前可以看到.config檔案中都是一些宏,這裡做的就是将整個檔案中的宏分别解析出來,存放到一個.h檔案中,檔案的存放的路徑為:

busybox詳解

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

檔案内容如下:

busybox詳解

(2)     生成最終的配置檔案

通過上面config目錄下的檔案生成一個完整的.h檔案,裡面是最終的一個配置檔案,内容如下:

檔案内容比較多,而且分為幾個獨立的部分,我們首先來看看最前面的部分:

busybox詳解

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

接下來看看該檔案最後部分的内容:

busybox詳解

從檔案内容可以看出,這是上面每個指令的入口函數,指令很有特點,一眼就看出來了哦。從這裡可以看出這裡是一個函數指針數組,根據傳入的下标選擇運作不同的函數,這就是為什麼在Busybox中指令”ls”的運作效果等同于”busybox ls”,如下圖:

busybox詳解

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

busybox詳解

3.2向Busybox中添加新指令

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

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

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

busybox詳解

(2)     其次就是編寫指令源檔案

我們要運作自己的指令肯定就得編寫自己的源代碼,這裡主要為了說明流程,是以使用如下簡答源代碼:

busybox詳解

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

(3)     修改相關的編譯檔案

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

首先修改如下檔案:

busybox詳解

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

busybox詳解

修改這裡主要是使得執行”make menuconfig”指令的時候,配置界面可以出現我們新增的指令,讓使用者對該指令可以配置,第一行是标示該指令的一個環境變量;第二行是出現在配置界面上的文字,是一個布爾量,取值為”Y”或者”N”;第三行是這個選項的預設值,這裡預設是選中的;第四行和第五行是該指令在配置界面的幫助資訊。

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

接下來我們還需要修改如下檔案:

busybox詳解

修改後的内容如下:

busybox詳解

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

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

busybox詳解

修改後的内容如下:

busybox詳解

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

接下來我們還要做一件非做不可的事情,就是每個指令都有幫助資訊,我們這裡也需要為新添加的指令增加幫助資訊,修改如下檔案:

busybox詳解

修改後的檔案如下圖:

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

(4)     編譯、測試

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

busybox詳解

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

busybox詳解

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

busybox詳解

檔案内容如下:

busybox詳解

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

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

busybox詳解

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

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

busybox詳解

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

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配置選項說明

繼續閱讀