Busybox源码简介

嵌入式技术

1368人已加入

描述

在嵌入式系统构建中,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作为参数给了对应的命令执行函数。

站在巨人的肩膀上,『敬畏』、『热情』

  审核编辑:汤梓红
 
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分