關鍵詞:buysbox、applet等。
busybox常用於嵌入式環境,集成中斷Linux命令和工具。這些工具簡單高效。
下面從如下方面了解:
- 這些命令是一個軟鏈接到busybox,那么是如何從軟連接到busybox再到執行相應的功能的?
- 如何添加自己的applet命令,進而擴展busybox?
- 以及一個applet是如何嵌入到busybox環境的。
1. 如何從軟鏈接到busybox的applet調用?
在busybox環境中,調用命令比如ls,其實是指向/bin/ls -> busybox的。
那么buysbox又是如何將這個軟鏈接對應的實際功能的呢?
int main(int argc UNUSED_PARAM, char **argv) { ... #if defined(SINGLE_APPLET_MAIN) ... #else lbb_prepare("busybox" IF_FEATURE_INDIVIDUAL(, argv)); # if !ENABLE_BUSYBOX if (argv[1] && is_prefixed_with(bb_basename(argv[0]), "busybox")) argv++; # endif applet_name = argv[0]; if (applet_name[0] == '-') applet_name++; applet_name = bb_basename(applet_name); parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */ run_applet_and_exit(applet_name, argv); #endif } static NORETURN void run_applet_and_exit(const char *name, char **argv) { # if ENABLE_BUSYBOX if (is_prefixed_with(name, "busybox")) exit(busybox_main(argv));--------------------------------顯示busybox幫助信息、applet列表等。 # endif # if NUM_APPLETS > 0 /* find_applet_by_name() search is more expensive, so goes second */ { int applet = find_applet_by_name(name);------------------根據applet的name找到其在applet_main[]中的序號。 if (applet >= 0) run_applet_no_and_exit(applet, name, argv); } # endif ... } static int busybox_main(char **argv) { if (!argv[1]) {-----------------------------------------------只有busybox情況下,顯示幫助信息。 /* Called without arguments */ ... } if (is_prefixed_with(argv[1], "--list")) {---------------------busybox --list顯示busybox所有applet。 ... } ... if (strcmp(argv[1], "--help") == 0) {--------------------------busybox --help顯示busybox幫助信息。 /* "busybox --help [<applet>]" */ if (!argv[2]) goto help; /* convert to "<applet> --help" */ argv[0] = argv[2]; argv[2] = NULL; } else { /* "busybox <applet> arg1 arg2 ..." */ argv++; } /* We support "busybox /a/path/to/applet args..." too. Allows for * "#!/bin/busybox"-style wrappers */ applet_name = bb_get_last_path_component_nostrip(argv[0]); run_applet_and_exit(applet_name, argv);-----------------------類似於執行busybox ls,然后調用ls applet。 } void FAST_FUNC run_applet_no_and_exit(int applet_no, const char *name, char **argv) { ... if (1 # if defined APPLET_NO_test && applet_no != APPLET_NO_test # endif # if defined APPLET_NO_true && applet_no != APPLET_NO_true # endif # if defined APPLET_NO_false && applet_no != APPLET_NO_false # endif ) { if (argc == 2 && strcmp(argv[1], "--help") == 0) {-------如果是applet對應的--help。 /* Make "foo --help" exit with 0: */ xfunc_error_retval = 0; bb_show_usage(); } } if (ENABLE_FEATURE_SUID) check_suid(applet_no); xfunc_error_retval = applet_main[applet_no](argc, argv);-----根據applet_no好找到對應的函數,比如ls對應ls_main()。 /* Note: applet_main() may also not return (die on a xfunc or such) */ xfunc_die(); } void FAST_FUNC bb_show_usage(void) { if (ENABLE_SHOW_USAGE) { #ifdef SINGLE_APPLET_STR ... #else const char *p; const char *usage_string = p = unpack_usage_messages(); int ap = find_applet_by_name(applet_name);---------------根據全局變量applet_name找到對應的序號,然后根據需要找到對應的usage字符串。 if (ap < 0) /* never happens, paranoia */ xfunc_die(); while (ap) { while (*p++) continue; ap--; } ... } xfunc_die(); }
2. 如何添加applet
若需要添加自己的applet,比如在miscutils下創建一個monitor.c文件,在include創建一個monitor.h文件。
//config:config MONITOR-----------------------------------------------------Config.src會讀取下面內容寫入到Config.in中,用於配置monitor功能。 //config: bool "monitor" //config: default n //config: select PLATFORM_LINUX //config: help //config: Monitor will collect system exception, daemon corruption, critical app exit. //applet:IF_MONITOR(APPLET(monitor, BB_DIR_SBIN, BB_SUID_DROP))--------------此句會寫入include/applets.h中,等於是聲明了monitor_main()函數。 //kbuild:lib-$(CONFIG_MONITOR) += monitor.o----------------------------------經由Kbuild.src生成寫入到Kbuild中,是對是否編譯monitor.c的控制。 //usage:#define monitor_trivial_usage----------------------------------------寫入到include/usage.h中,是monitor的幫助信息。 //usage: "[-q] [-o OFF] [-f FREQ] [-p TCONST] [-t TICK]" //usage:#define monitor_full_usage "\n\n" //usage: "Monitor system or app exception.\n" //usage: "\n -q Quiet" #include "libbb.h" #include <syslog.h> #include <sys/un.h>
上面的config/applet/kbuild/usage,分別生成到miscutils/Config.in、include/applets.h、miscutils/Kbuild、include/usage.h四個文件中。
所以在配置了CONFIG_MONITOR之后,根據miscutils/Kbuild會編譯monitor.c,入口函數是monitor_main()。
3. applet是如何嵌入到busybox的?
通過applets/applet_tables.c生成可執行文件applet_tables。
applets/Kbuild中執行cmd_gen_applet_tables = applets/applet_tables include/applet_tables.h include/NUM_APPLETS.h,生成applet_tables.h文件,以及applet總數的NUM_APPLETS定義。
#define NUM_APPLETS 260 #define KNOWN_APPNAME_OFFSETS 8 const uint16_t applet_nameofs[] ALIGN2 = { 172, 401, 612, 836, 1048, 1299, 1514, }; const char applet_names[] ALIGN1 = ""--------------------------------------在find_applet_by_name()等函數中根據applet名稱找到applet對應序號。 "[" "\0" "[[" "\0" "addgroup" "\0" "adduser" "\0" ... "zcat" "\0" ; #define APPLET_NO_addgroup 2 #define APPLET_NO_adduser 3 ... #define APPLET_NO_zcat 259 #ifndef SKIP_applet_main int (*const applet_main[])(int argc, char **argv) = {---------------------根據applet序號,找到對應的入口函數。 test_main, test_main, addgroup_main, adduser_main, ... gunzip_main, }; #endif const uint8_t applet_suid[] ALIGN1 = { 0x00, 0x00, ...0x00, }; const uint8_t applet_install_loc[] ALIGN1 = { 0x33, 0x44, ...0x13, };
其他生成文件還包括:usage.h、applets.h等。
usage.h中包含了函數的幫助信息,是由usage.c編譯的usage生成的。
#define monitor_trivial_usage \ "[-q] [-o OFF] [-f FREQ] [-p TCONST] [-t TICK]" \ #define monitor_full_usage "\n\n" \ "Monitor system or app exception.\n" \ "\n -q Quiet" \
4. 小結
一個是添加applet的路徑,新增.c和.h文件,其中最重要的是.c文件中特殊注釋:config:、applet:、kbuild:、usage:。
然后busybox編譯系統,解析.c中的注釋,並將其添加到include/applets.h、include/usage.h、include/applet_tables.h等文件中。
兩一個是applet的執行路徑,busybox的入口函數mian()根據傳入的applet_name,然后通過find_applet_by_name()找到對應序號,然后執行applet_main[]函數。即完成對applet的調用。
參考文檔:《擴充BusyBox,追加Applet的方法》、《如何向busybox添加自己的命令》。
