busybox詳解
轉載地址:https://blog.csdn.net/guodongsoft/article/details/52534617
1.根檔案系統簡介
所謂製作根檔案系統,就是建立各種目錄,並且在目錄裡建立相應的檔案。例如:在/bin目錄下放置可執行程式,在/lib下放置各種庫等等。通常配合chroot命令使用。
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做裁剪的時候參考。
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):表示該子程序使用的控制檯,如果該欄位省略,則使用與init程序一樣的控制檯。
(2):該程序的執行級別,Busybox 的init程式不支援執行級別這個概念,因此該欄位無意義,如果要支援runlevel意義,則建議使用System V Init程式。
(3):表示init如何控制該程序,是一個列舉量,可能的取值及相應的意義如下表:
(4):要執行的程式,可以為可執行程式也可以是指令碼,如果欄位前面有”-”字元,代表這個程式是可互動的,例如:/bin/sh程式。
最後給出一個inittab檔案的內容:
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檔案,裡面是最終的一個配置檔案,內容如下:
檔案內容比較多,而且分為幾個獨立的部分,我們首先來看看最前面的部分:
從內容可以看出,這就是我們最終要生成的命令的名字,將它們所有都放在一個數組中。
接下來看看該檔案最後部分的內容:
從檔案內容可以看出,這是上面每個命令的入口函式,命令很有特點,一眼就看出來了哦。從這裡可以看出這裡是一個函式指標陣列,根據傳入的下標選擇執行不同的函式,這就是為什麼在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一節已經提到了。