【Busybox】Busybox源码分析-01 | 源码目录结构和程序入口

👀 『敬畏』、『热情』、『专注』、『分享』

👉在嵌入式系统构建中,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的整体源码结构和设计,将这些程序拆分成了各个工具模块。

序号目录名称功能说明
1applets实现applets框架的文件。目录中包含了几个main()的文件
2applets_sh此目录包含了几个作为shell脚本实现的applet示例。在“make install”时不会被自动安装,需要使用时,手动处理
3arch包含用于不同体系架构的makefile文件。约束busybox在不同架构体系下的编译构建过程
4archival与压缩相关命令的实现源文件。
5configsbusybox自带的默认配置文件
6console-tools与控制台相关的一些命令
7coreutils常用的一些核心命令。例如chgrp、rm等
8debianutils针对Debian的套件。
9e2fsprogs针对Linux Ext2 FS prog的命令。例如chattr、lsattr
10editors常用的编辑命令。例如diff、vi等
11findutils用于查找的命令
12includebusybox项目的头文件
13initinit进程的实现源码目录
14klibc-utilsklibc命令套件
15libbb与busybox实现相关的库文件
16libpwdgrplibpwdgrp相关的命令
17loginutils与用户管理相关的命令
18mailutils与mail相关的命令套件
19miscutils该文件下是一些杂项命令,针对特定应用场景
20modutils与模块相关的命令
21networking与网络相关的命令,例如arp
22printutilsPrint相关的命令
23procps与内存、进程相关的命令
24runit与Runit实现相关的命令
25shell与shell相关的命令
26sysklogd系统日志记录工具相关的命令
27util-linuxLinux下常用的命令,主要与文件系统操作相关的命令。

三、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\n");
	/* 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\n");
	/* 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命令表数组中的元素(函数指针),并将argcargv作为参数给了对应的命令执行函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iriczhao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值