在嵌入式系統構建中,Busybox可用于構建輕量級的根文件系統,本文從源碼結構和源碼入口角度分析busybox,了解其背后的運作機制。
busybox版本:1.35.0
Busybox簡介
(1-1)開源項目
Busybox是一個開源項目,遵循GPL v2協議。Busybox將眾多的UNIX命令集合進了一個很小的可執行程序中,可以用來替代GNU fileutils、shellutils等工具集。Busybox中各種命令與相應的GNU工具相比,所能提供的選項比較少,但是對于一般的應用場景也足夠了,特別是在嵌入式系統的設計中。
(1-2)程序本體較小
Busybox在設計過程中對文件大小進行了優化,并考慮了系統資源有限(比如內存等)的情況。與一般的GNU工具集動輒幾M的體積相比,動態鏈接的Busybox只有幾百K,即使是采用靜態鏈接也只有1M左右。除此之外,Busybox按模塊設計,可以很容易地加入、去除某些命令,或增減命令的某些選項。
(1-3)使用簡單
如果使用Busybox來創建根文件系統,使用起來比較方便,只需要在/dev目錄下創建必要的設備節點,在/etc目錄下增加一些配置文件即可,當然如果Busybox是動態鏈接的,那么還需要在/lib目錄下包含相關的運行庫文件。
Busybox源碼目錄結構
在較老版本的Busybox中,對于Busybox的多個程序是全部塞進了一個名為utility.c的文件中,后來更改了Busybox的整體源碼結構和設計,將這些程序拆分成了各個工具模塊。
序號 | 目錄名稱 | 功能說明 |
---|---|---|
1 | applets | 實現applets框架的文件。目錄中包含了幾個main()的文件 |
2 | applets_sh | 此目錄包含了幾個作為shell腳本實現的applet示例。在“make install”時不會被自動安裝,需要使用時,手動處理 |
3 | arch | 包含用于不同體系架構的makefile文件。約束busybox在不同架構體系下的編譯構建過程 |
4 | archival | 與壓縮相關命令的實現源文件。 |
5 | configs | busybox自帶的默認配置文件 |
6 | console-tools | 與控制臺相關的一些命令 |
7 | coreutils | 常用的一些核心命令。例如chgrp、rm等 |
8 | debianutils | 針對Debian的套件。 |
9 | e2fsprogs | 針對Linux Ext2 FS prog的命令。例如chattr、lsattr |
10 | editors | 常用的編輯命令。例如diff、vi等 |
11 | findutils | 用于查找的命令 |
12 | include | busybox項目的頭文件 |
13 | init | init進程的實現源碼目錄 |
14 | klibc-utils | klibc命令套件 |
15 | libbb | 與busybox實現相關的庫文件 |
16 | libpwdgrp | libpwdgrp相關的命令 |
17 | loginutils | 與用戶管理相關的命令 |
18 | mailutils | 與mail相關的命令套件 |
19 | miscutils | 該文件下是一些雜項命令,針對特定應用場景 |
20 | modutils | 與模塊相關的命令 |
21 | networking | 與網絡相關的命令,例如arp |
22 | printutils | Print相關的命令 |
23 | procps | 與內存、進程相關的命令 |
24 | runit | 與Runit實現相關的命令 |
25 | shell | 與shell相關的命令 |
26 | sysklogd | 系統日志記錄工具相關的命令 |
27 | util-linux | Linux下常用的命令,主要與文件系統操作相關的命令。 |
Busybox程序主體
Busybox是在linux內核啟動后加載運行的用戶空間程序,在源碼設計上是基于C語言完成設計和開發的。與常規程序一樣,Busybox的入口同樣是main(),定義在libbb/appletlib文件的末尾處。在函數開始處,使用ENABLE_BUILD_LIBBUSYBOX對函數名稱進行了條件分支處理:如果ENABLE_BUILD_LIBBUSYBOX為真,則表示將Busybox以庫的方式進行構建。
在函數體中,以條件宏定義進行代碼的編譯邏輯控制:
?
?/*?Tweak?malloc?for?reduced?memory?consumption?*/ #ifdef?M_TRIM_THRESHOLD ?/*?M_TRIM_THRESHOLD是釋放的最頂層內存的最大數量 ??*?默認值太大,是256k ??*/ ?mallopt(M_TRIM_THRESHOLD,?8?*?1024); #endif #ifdef?M_MMAP_THRESHOLD ?/* M_MMAP_THRESHOLD是使用mmap()的請求大小閾值。 ??*?默認值是256k ??*/ ?mallopt(M_MMAP_THRESHOLD,?32?*?1024?-?256); #endif
?
上述代碼都調用了mallopt()函數,該函數用于設置內存的分配參數,由于默認值太大(為256KB),故此處調整內存分配大小,讓出多余的內存。
接著,是一個由#if -- #elif -- #else -- #endif控制的條件宏多分支判斷結構語句,此處以Busybox的一般運行情況為例(在Linux內核啟動后期,加載并運行Busybox構建出的init程序)。其執行邏輯如下:
首先Busybox是一個linux下的工具集合,本質則是一個個的命令,例如:ls、mv、cp等,在命令行我們輸入想要執行的操作時,例如:mkdir iriczhao,則會將參數傳遞給Busybox,然后由他完成對應的操作。
在源碼中,使用char * applet_name表示工具的名稱(本質是字符串),首先會調用lbb_prepare()函數:
?
lbb_prepare("busybox"?IF_FEATURE_INDIVIDUAL(,?argv));
?
將會設置applet_name的值為“busybox“,用于執行ENABLE_FEATURE_INDIVIDUAL為真時的邏輯操作:
?
void?lbb_prepare(const?char?*applet ??????IF_FEATURE_INDIVIDUAL(,?char?**argv)) { #ifdef?bb_cached_errno_ptr ?ASSIGN_CONST_PTR(&bb_errno,?get_perrno()); #endif ?applet_name?=?applet; ?if?(ENABLE_LOCALE_SUPPORT) ??setlocale(LC_ALL,?""); #if?ENABLE_FEATURE_INDIVIDUAL ?/*?Redundant?for?busybox?(run_applet_and_exit?covers?that?case) ??*?but?needed?for?"individual?applet"?mode?*/ ?if?(argv[1]?&&?!argv[2]?&&?strcmp(argv[1],?"--help")?==?0?&&?!is_prefixed_with(applet,?"busybox")) ?{ ??/*?Special?cases.?POSIX?says?"test?--help" ???*?should?be?no?different?from?e.g.?"test?--foo". ???*/ ??if?(!(ENABLE_TEST?&&?strcmp(applet_name,?"test")?==?0)?&&?!(ENABLE_TRUE?&&?strcmp(applet_name,?"true")?==?0)?&&?!(ENABLE_FALSE?&&?strcmp(applet_name,?"false")?==?0)?&&?!(ENABLE_ECHO?&&?strcmp(applet_name,?"echo")?==?0)) ???bb_show_usage(); ?} #endif }
?
接著,會解析命令行傳遞的第一個參數:
?
?applet_name?=?argv[0]; ?if?(applet_name[0]?==?'-') ??applet_name++; ?applet_name?=?bb_basename(applet_name);
?
例如,在命令行輸入mkdir iriczhao命令,則會解析到mkdir命令傳遞給applet_name,至于后面的參數(此處是iriczhao)是如何傳遞的,后文會描述到。
如果配置了FEATURE_SUID_CONFIG宏定義,在parse_config_file()函數中還將從/etc/busybox.conf文件中解析關于busybox的配置參數。
在最后,則是busybox的重要函數:run_applet_and_exit(),該函數定義如下:
?
static?NORETURN?void?run_applet_and_exit(const?char?*name,?char?**argv) { #if?ENABLE_BUSYBOX ??//檢查是否是帶有busybox前綴的字符串,如果不是,則返回NULL。 ??//如果在命令行下輸入具體的命令,則不是帶有busybox前綴的命令字符串,則不會執行該條件下的語句 ?if?(is_prefixed_with(name,?"busybox")) ??exit(busybox_main(/*unused:*/?0,?argv)); #endif #if?NUM_APPLETS?>?0 ?/*?find_applet_by_name()?search?is?more?expensive,?so?goes?second?*/ ?{ ??int?applet?=?find_applet_by_name(name); ??if?(applet?>=?0) ???run_applet_no_and_exit(applet,?name,?argv); ?} #endif ?/*bb_error_msg_and_die("applet?not?found");?-?links?in?printf?*/ ?full_write2_str(applet_name); ?full_write2_str(":?applet?not?found "); ?/*?POSIX:?"If?a?command?is?not?found,?the?exit?status?shall?be?127"?*/ ?exit(127); }
?
如果NUM_APPLETS大于0,則會執行對應的命令操作,并退出;否則,busybox將會報錯:
?
#if?NUM_APPLETS?>?0 ?/*?find_applet_by_name()?search?is?more?expensive,?so?goes?second?*/ ?{ ??int?applet?=?find_applet_by_name(name); ??if?(applet?>=?0) ???run_applet_no_and_exit(applet,?name,?argv); ?} #endif ?? ??//正常情況下(NUM_APPLETS >?0),不會執行下述代碼。 ?/*bb_error_msg_and_die("applet?not?found");?-?links?in?printf?*/ ?full_write2_str(applet_name); ?full_write2_str(":?applet?not?found "); ?/*?POSIX:?"If?a?command?is?not?found,?the?exit?status?shall?be?127"?*/ ?exit(127); ??
?
從上述代碼可知,在命令行鍵入命令后,實則起關鍵作者的函數是:find_applet_by_name()和run_applet_no_and_exit()。下文將繼續分析。
Busybox程序運行剖析
在上一小節中,已經知道當我們在busybox的命令行下,鍵入命令后,執行具體操作的函數是:find_applet_by_name()和run_applet_no_and_exit()。
在編譯構建源碼并安裝busybox后,在安裝目錄下的文件結構則是一個名為busybox的可執行程序和很多的鏈接,這些鏈接實則是我們在命令行鍵入的命令名稱。如下圖所示:
從源碼角度看,busybox中的命令都有一一對應的執行函數,其函數命名格式為xxx_main(),在源碼設計上,其內部在/include/applet_tabls.h頭文件中維護了一張命令表,定義如下(代碼太長,有省略):
?
int?(*const?applet_main[])(int?argc,?char?**argv)?=?{ test_main, test_main, acpid_main, add_remove_shell_main, addgroup_main, adduser_main, adjtimex_main, uname_main, arp_main, arping_main, ascii_main, ash_main, awk_main, baseNUM_main, baseNUM_main, basename_main, //省略大量內容 //... }
?
上述函數指針數組中的元素則是分布于busybox源碼各個目錄下命令入口函數。在代碼執行邏輯中,首先會調用find_applet_by_name()函數,通過傳入的命令名稱獲取在命令表中的數組下標。并將命令對應的下標applet、命令名稱name和命令行參數字符串argv傳遞給run_applet_no_and_exit()函數(注:解釋了上一小節中,命令行對應命令后面的參數是如何傳遞的),該函數定義如下:
?
void?FAST_FUNC?run_applet_no_and_exit(int?applet_no,?const?char?*name,?char?**argv) { ?int?argc; ?/* ??*?We?do?not?use?argv[0]:?do?not?want?to?repeat?massaging?of ??*?"-/sbin/halt"?->?"halt",?for?example. ??*/ ?applet_name?=?name; ?show_usage_if_dash_dash_help(applet_no,?argv); ?if?(ENABLE_FEATURE_SUID) ??check_suid(applet_no); ?argc?=?string_array_len(argv); ?xfunc_error_retval?=?applet_main[applet_no](argc,?argv); ?/*?Note:?applet_main()?may?also?not?return?(die?on?a?xfunc?or?such)?*/ ?xfunc_die(); } #endi
?
在上述代碼中,執行命令下的對應具體操作函數的語句是:
?
xfunc_error_retval?=?applet_main[applet_no](argc,?argv);
?
applet_main是命令表數組,applet_no是對應命令的數組下標,本質則是調用對應的applet_main命令表數組中的元素(函數指針),并將argc和argv作為參數給了對應的命令執行函數。
站在巨人的肩膀上,『敬畏』、『熱情』
評論
查看更多