在嵌入式系統(tǒng)構(gòu)建中,Busybox可用于構(gòu)建輕量級(jí)的根文件系統(tǒng),本文從源碼結(jié)構(gòu)和源碼入口角度分析busybox,了解其背后的運(yùn)作機(jī)制。
busybox版本:1.35.0
Busybox簡介
(1-1)開源項(xiàng)目
Busybox是一個(gè)開源項(xiàng)目,遵循GPL v2協(xié)議。Busybox將眾多的UNIX命令集合進(jìn)了一個(gè)很小的可執(zhí)行程序中,可以用來替代GNU fileutils、shellutils等工具集。Busybox中各種命令與相應(yīng)的GNU工具相比,所能提供的選項(xiàng)比較少,但是對(duì)于一般的應(yīng)用場景也足夠了,特別是在嵌入式系統(tǒng)的設(shè)計(jì)中。
(1-2)程序本體較小
Busybox在設(shè)計(jì)過程中對(duì)文件大小進(jìn)行了優(yōu)化,并考慮了系統(tǒng)資源有限(比如內(nèi)存等)的情況。與一般的GNU工具集動(dòng)輒幾M的體積相比,動(dòng)態(tài)鏈接的Busybox只有幾百K,即使是采用靜態(tài)鏈接也只有1M左右。除此之外,Busybox按模塊設(shè)計(jì),可以很容易地加入、去除某些命令,或增減命令的某些選項(xiàng)。
(1-3)使用簡單
如果使用Busybox來創(chuàng)建根文件系統(tǒng),使用起來比較方便,只需要在/dev目錄下創(chuàng)建必要的設(shè)備節(jié)點(diǎn),在/etc目錄下增加一些配置文件即可,當(dāng)然如果Busybox是動(dòng)態(tài)鏈接的,那么還需要在/lib目錄下包含相關(guān)的運(yùn)行庫文件。
Busybox源碼目錄結(jié)構(gòu)
在較老版本的Busybox中,對(duì)于Busybox的多個(gè)程序是全部塞進(jìn)了一個(gè)名為utility.c的文件中,后來更改了Busybox的整體源碼結(jié)構(gòu)和設(shè)計(jì),將這些程序拆分成了各個(gè)工具模塊。
序號(hào) | 目錄名稱 | 功能說明 |
---|---|---|
1 | applets | 實(shí)現(xiàn)applets框架的文件。目錄中包含了幾個(gè)main()的文件 |
2 | applets_sh | 此目錄包含了幾個(gè)作為shell腳本實(shí)現(xiàn)的applet示例。在“make install”時(shí)不會(huì)被自動(dòng)安裝,需要使用時(shí),手動(dòng)處理 |
3 | arch | 包含用于不同體系架構(gòu)的makefile文件。約束busybox在不同架構(gòu)體系下的編譯構(gòu)建過程 |
4 | archival | 與壓縮相關(guān)命令的實(shí)現(xiàn)源文件。 |
5 | configs | busybox自帶的默認(rèn)配置文件 |
6 | console-tools | 與控制臺(tái)相關(guān)的一些命令 |
7 | coreutils | 常用的一些核心命令。例如chgrp、rm等 |
8 | debianutils | 針對(duì)Debian的套件。 |
9 | e2fsprogs | 針對(duì)Linux Ext2 FS prog的命令。例如chattr、lsattr |
10 | editors | 常用的編輯命令。例如diff、vi等 |
11 | findutils | 用于查找的命令 |
12 | include | busybox項(xiàng)目的頭文件 |
13 | init | init進(jìn)程的實(shí)現(xiàn)源碼目錄 |
14 | klibc-utils | klibc命令套件 |
15 | libbb | 與busybox實(shí)現(xiàn)相關(guān)的庫文件 |
16 | libpwdgrp | libpwdgrp相關(guān)的命令 |
17 | loginutils | 與用戶管理相關(guān)的命令 |
18 | mailutils | 與mail相關(guān)的命令套件 |
19 | miscutils | 該文件下是一些雜項(xiàng)命令,針對(duì)特定應(yīng)用場景 |
20 | modutils | 與模塊相關(guān)的命令 |
21 | networking | 與網(wǎng)絡(luò)相關(guān)的命令,例如arp |
22 | printutils | Print相關(guān)的命令 |
23 | procps | 與內(nèi)存、進(jìn)程相關(guān)的命令 |
24 | runit | 與Runit實(shí)現(xiàn)相關(guān)的命令 |
25 | shell | 與shell相關(guān)的命令 |
26 | sysklogd | 系統(tǒng)日志記錄工具相關(guān)的命令 |
27 | util-linux | Linux下常用的命令,主要與文件系統(tǒng)操作相關(guān)的命令。 |
Busybox程序主體
Busybox是在linux內(nèi)核啟動(dòng)后加載運(yùn)行的用戶空間程序,在源碼設(shè)計(jì)上是基于C語言完成設(shè)計(jì)和開發(fā)的。與常規(guī)程序一樣,Busybox的入口同樣是main(),定義在libbb/appletlib文件的末尾處。在函數(shù)開始處,使用ENABLE_BUILD_LIBBUSYBOX對(duì)函數(shù)名稱進(jìn)行了條件分支處理:如果ENABLE_BUILD_LIBBUSYBOX為真,則表示將Busybox以庫的方式進(jìn)行構(gòu)建。
在函數(shù)體中,以條件宏定義進(jìn)行代碼的編譯邏輯控制:
?
?/*?Tweak?malloc?for?reduced?memory?consumption?*/ #ifdef?M_TRIM_THRESHOLD ?/*?M_TRIM_THRESHOLD是釋放的最頂層內(nèi)存的最大數(shù)量 ??*?默認(rèn)值太大,是256k ??*/ ?mallopt(M_TRIM_THRESHOLD,?8?*?1024); #endif #ifdef?M_MMAP_THRESHOLD ?/* M_MMAP_THRESHOLD是使用mmap()的請(qǐng)求大小閾值。 ??*?默認(rèn)值是256k ??*/ ?mallopt(M_MMAP_THRESHOLD,?32?*?1024?-?256); #endif
?
上述代碼都調(diào)用了mallopt()函數(shù),該函數(shù)用于設(shè)置內(nèi)存的分配參數(shù),由于默認(rèn)值太大(為256KB),故此處調(diào)整內(nèi)存分配大小,讓出多余的內(nèi)存。
接著,是一個(gè)由#if -- #elif -- #else -- #endif控制的條件宏多分支判斷結(jié)構(gòu)語句,此處以Busybox的一般運(yùn)行情況為例(在Linux內(nèi)核啟動(dòng)后期,加載并運(yùn)行Busybox構(gòu)建出的init程序)。其執(zhí)行邏輯如下:
首先Busybox是一個(gè)linux下的工具集合,本質(zhì)則是一個(gè)個(gè)的命令,例如:ls、mv、cp等,在命令行我們輸入想要執(zhí)行的操作時(shí),例如:mkdir iriczhao,則會(huì)將參數(shù)傳遞給Busybox,然后由他完成對(duì)應(yīng)的操作。
在源碼中,使用char * applet_name表示工具的名稱(本質(zhì)是字符串),首先會(huì)調(diào)用lbb_prepare()函數(shù):
?
lbb_prepare("busybox"?IF_FEATURE_INDIVIDUAL(,?argv));
?
將會(huì)設(shè)置applet_name的值為“busybox“,用于執(zhí)行ENABLE_FEATURE_INDIVIDUAL為真時(shí)的邏輯操作:
?
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 }
?
接著,會(huì)解析命令行傳遞的第一個(gè)參數(shù):
?
?applet_name?=?argv[0]; ?if?(applet_name[0]?==?'-') ??applet_name++; ?applet_name?=?bb_basename(applet_name);
?
例如,在命令行輸入mkdir iriczhao命令,則會(huì)解析到mkdir命令傳遞給applet_name,至于后面的參數(shù)(此處是iriczhao)是如何傳遞的,后文會(huì)描述到。
如果配置了FEATURE_SUID_CONFIG宏定義,在parse_config_file()函數(shù)中還將從/etc/busybox.conf文件中解析關(guān)于busybox的配置參數(shù)。
在最后,則是busybox的重要函數(shù):run_applet_and_exit(),該函數(shù)定義如下:
?
static?NORETURN?void?run_applet_and_exit(const?char?*name,?char?**argv) { #if?ENABLE_BUSYBOX ??//檢查是否是帶有busybox前綴的字符串,如果不是,則返回NULL。 ??//如果在命令行下輸入具體的命令,則不是帶有busybox前綴的命令字符串,則不會(huì)執(zhí)行該條件下的語句 ?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,則會(huì)執(zhí)行對(duì)應(yīng)的命令操作,并退出;否則,busybox將會(huì)報(bào)錯(cuò):
?
#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),不會(huì)執(zhí)行下述代碼。 ?/*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); ??
?
從上述代碼可知,在命令行鍵入命令后,實(shí)則起關(guān)鍵作者的函數(shù)是:find_applet_by_name()和run_applet_no_and_exit()。下文將繼續(xù)分析。
Busybox程序運(yùn)行剖析
在上一小節(jié)中,已經(jīng)知道當(dāng)我們?cè)赽usybox的命令行下,鍵入命令后,執(zhí)行具體操作的函數(shù)是:find_applet_by_name()和run_applet_no_and_exit()。
在編譯構(gòu)建源碼并安裝busybox后,在安裝目錄下的文件結(jié)構(gòu)則是一個(gè)名為busybox的可執(zhí)行程序和很多的鏈接,這些鏈接實(shí)則是我們?cè)诿钚墟I入的命令名稱。如下圖所示:
從源碼角度看,busybox中的命令都有一一對(duì)應(yīng)的執(zhí)行函數(shù),其函數(shù)命名格式為xxx_main(),在源碼設(shè)計(jì)上,其內(nèi)部在/include/applet_tabls.h頭文件中維護(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, //省略大量內(nèi)容 //... }
?
上述函數(shù)指針數(shù)組中的元素則是分布于busybox源碼各個(gè)目錄下命令入口函數(shù)。在代碼執(zhí)行邏輯中,首先會(huì)調(diào)用find_applet_by_name()函數(shù),通過傳入的命令名稱獲取在命令表中的數(shù)組下標(biāo)。并將命令對(duì)應(yīng)的下標(biāo)applet、命令名稱name和命令行參數(shù)字符串a(chǎn)rgv傳遞給run_applet_no_and_exit()函數(shù)(注:解釋了上一小節(jié)中,命令行對(duì)應(yīng)命令后面的參數(shù)是如何傳遞的),該函數(shù)定義如下:
?
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
?
在上述代碼中,執(zhí)行命令下的對(duì)應(yīng)具體操作函數(shù)的語句是:
?
xfunc_error_retval?=?applet_main[applet_no](argc,?argv);
?
applet_main是命令表數(shù)組,applet_no是對(duì)應(yīng)命令的數(shù)組下標(biāo),本質(zhì)則是調(diào)用對(duì)應(yīng)的applet_main命令表數(shù)組中的元素(函數(shù)指針),并將argc和argv作為參數(shù)給了對(duì)應(yīng)的命令執(zhí)行函數(shù)。
站在巨人的肩膀上,『敬畏』、『熱情』
評(píng)論
查看更多