构造根文件系统3---Busybox

时间:2022-08-04 16:29:07


目录(?)[+]

  1. 根文件系统简介
  2. Busybox简介
    1. 1Busybox简介
    2. 2Busybox目录结构简介
    3. 3init进程简介
    4. 31内核如何启动init进程
    5. 32init的执行流程
    6. 33init的配置文件
    7. 构建自己的根文件系统
    8. 1编译Busybox
    9. 2向Busybox中添加新命令
    10. 附录
    11. 1Busybox实现的简单分析
    12. 11applets的实现
    13. 12libbb的实现
    14. 2Busybox配置选项说明





                              

     转自http://blog.csdn.net/wqc02/article/details/8930184                                                           

1.根文件系统简介

所谓制作根文件系统,就是创建各种目录,并且在目录里创建相应的文件。例如:在/bin目录下放置可执行程序,在/lib下放置各种库等等。

2.Busybox简介

2.1Busybox简介

Busybox是一个开源项目,遵循GPL v2协议。Busybox将众多的UNIX命令集合进一个很小的可执行程序中,可以用来替代GNU fileutils、shellutils等工具集。Busybox中各种命令与相应的GNU工具相比,所能提供的选项比较少,但是也足够一般的应用了。Busybox主要用于嵌入式系统。

Busybox在编写过程中对文件大小进行了优化,并考虑了系统资源有限(比如内存等)的情况。与一般的GNU工具集动辄几M的体积相比,动态链接的Busybox只有几百K,即使是采用静态链接也只有1M左右。Busybox按模块设计,可以很容易地加入、去除某些命令,或增减命令的某些选项。

在创建根文件系统的时候,如果使用Busybox的话,只需要在/dev目录下创建必要的设备节点,在/etc目录下增加一些配置文件即可,当然,如果Busybox使用动态链接,那么还需要再/lib目录下包含库文件。

2.2Busybox目录结构简介

下面是Busybox源码目录结构图,接下来说说各个目录的作用,方便以后对Busybox做裁剪的时候参考。

目录

说明

applets

主要是实现applets框架的文件

applets_sh

一些有用的脚本,例如:dos2unix、unix2dos等

archival

与压缩有关的命令源文件,例如:bzip2、gzip等

configs

自带的一些默认配置文件

console-tools

与控制台相关的一些命令,例如:setconsole

coreutils

常用的核心命令,例如:cat、rm等

editors

常用的编辑命令,例如:vi、diff等

findutils

用于查找的命令,例如:find、grep等

init

init进程的实现源文件

networking

与网络相关的命令,例如:telnetl、arp等

shell

与shell相关的实现,例如:ash、msh等

util-Linux

Linux下常用的命令,主要是与文件系统相关的,例如:mkfs_ext2等

 

2.3init进程简介

    Busybox中最重要的程序自然是init。   

大家都知道init进程是由内核启动的第一个(也是唯一一个)用户进程(进程ID为1),init进程根据配置文件决定启动哪些程序,例如:执行某些脚本、启动shell或运行用户程序等等。Init是后续所有进程的发起者,例如:init进程启动/bin/sh程序后,我们才能够在控制台上输入各种命令。

Init进程的执行程序通常都是/sbin/init,上述讲到的init进程的作用只不过是/sbin/init这个程序的功能。如果我们想让init执行自己想要的功能,那么有两种途径:第一,使用自己的init程序,这包括使用自己的init替换/sbin/下的init程序或者修改传递给内核的参数,指定”init=xxx”这个参数,让init环境变量指向自己的init程序;第二,就是修改init的配置文件,因为init程序的很大一部分的功能都是按照其配置文件执行的。

一般而言,在Linux系统中有两种init程序:BSD init和System V init。BSD和 System V是两种版本的UNIX系统。这两种init程序各有优缺点,现在大多数Linux发行版本使用的都是System V init。但在嵌入式系统中常使用的是Busybox集成的init程序,下面基于它进行介绍。

2.3.1内核如何启动init进程

内核启动的最后一步就是启动init进程,代码在init/main.c文件中,如下所示:

构造根文件系统3---Busybox

代码并不复杂,与init启动最强相关的就是run_init_process这个函数了,它运行指定的init程序,注意:一旦run_init_process运行创建进程成功,它将不会返回,而是通过操作内核栈进入用户空间。所以上面并不是运行了四个init进程,而是根据优先级,一旦某一个运行成功,就不往下继续执行了。

下面详细描述一下该函数的执行过程:
(1)打开标准输入、标准输出和标准错误设备

Linux中最先打开的3个文件分别称作标准输入(stdin)、标准输出(stdout)和标准错误(stderr),它们对应的文件描述符分别是0、1、2.。

如下代码就是执行这个操作,先打开文件/dev/console作为保准输入,然后将文件描述符复制给文件描述符1、2,这样使得标准输入、标准输出以及标准错误都使用/dev/console这个文件。注意代码上面的注释”该函数不能失败,也就是说至少/dev/console必须存在”。

构造根文件系统3---Busybox

(2)如果变量ramdisk_execute_command为空,则将其指向/init程序,如果该程序存在,则运行该程序,并且进程不会返回;如果该程序不存在,则置变量ramdisk_execute_command为NULL,代码片段为:

构造根文件系统3---Busybox

构造根文件系统3---Busybox

(3)如果变量execute_command指定了要运行的程序,则运行它,并且不会返回:

构造根文件系统3---Busybox

(4)依次尝试几个常见的init,一旦某一个成功,则不返回:

构造根文件系统3---Busybox

(5)如果以上执行都失败,那么内核就挂了

构造根文件系统3---Busybox

    至于init执行失败可能的原因,详见内核文档Documentation\init.txt。

2.3.2init的执行流程

Busybox init程序对应的源代码在init/init.c文件中,下面先介绍其启动过程。

构造根文件系统3---Busybox

内核启动init进程的时候已经打开了”/dev/console”设备作为控制台,一般情况下Busybox init程序就是要/dev/console。但是如果内核启动init进程的时候同时指定了环境变量CONSOLE或者console,则init使用环境变量所指定的设备。在Busybox中还会检查这个指定的设备是否可以打开,如果不能打开,则使用/dev/null。

Busybox init进程只是作为其它进程的发起者和控制着,并不需要控制台与用户交互,所以init进程会把它关掉,系统启动后运行命令”ls /proc/l/fd/”可以看到该目录为空。Init进程创建其它子进程的时候,如果没有指名该进程的控制台,则该进程也是有前面确定的控制台,至于怎么为进程指定控制台就通过init的配置文件实现。

2.3.3init的配置文件

Init可以创建子进程,然而究竟应该创建哪些进程呢?这个是可以通过其配置文件定制的,init的配置文件为/etc/inittab文件。

Inittab文件的相关文档和示例代码都在Busybox的examples/inittab文件中,内容如下:

构造根文件系统3---Busybox

上图中标有下划线的一行就是inittab文件中每一行内容的格式。Inittab文件中的每个条目用来定义一个子进程,并确定它的启动方法。每一行都分为四个字段,分别用”:”隔开,每个字段的意义如下:

(1)<id>:表示该子进程使用的控制台,如果该字段省略,则使用与init进程一样的控制台。

(2)<runlevel>:该进程的运行级别,Busybox 的init程序不支持运行级别这个概念,因此该字段无意义,如果要支持runlevel意义,则建议使用System V Init程序。

(3)<action>:表示init如何控制该进程,是一个枚举量,可能的取值及相应的意义如下表:

Action取值

执行条件

说明

sysinit

系统启动后最先执行

只执行一次,init等它执行完后在执行下面的

wait

系统执行完sysinit进程后

只执行一次,init等它执行完后在执行下面的

once

系统执行完wait进程后

只执行一次,init进程不等待它结束

respawn

系统启动完once进程后

Init进程发现子进程退出,则重新启动它

 

askfirst

 

系统启动完respawn进程后

与respawn类似,不过init进程先输出”Please press Enter to active this console”,等待用户输入回车键后才启动子进程

shutdown

系统关机时

——

 

restart

当Busybox配置了CONFIG_FEATURE_USE_INNITTAB,

且init进程接收到了SIGHUP信号

先重新读取、解析inittab文件,再执行restart程序

ctraltdel

按下Ctrl+Alt_Del组合键时

——

 

(4)<process>:要执行的程序,可以为可执行程序也可以是脚本,如果<process>字段前面有”-”字符,代表这个程序是可交互的,例如:/bin/sh程序。

最后给出一个inittab文件的内容:

构造根文件系统3---Busybox

注意:如果inittab配置文件不存在,那么init就执行默认的配置:

构造根文件系统3---Busybox

3.构建自己的根文件系统

3.1编译Busybox

现在我们开始构建自己的根文件系统,主要工作就是编译Busybox,首先到官网下载最新的源代码,加压缩到自己的工作目录,我这里不列出目录,下面的截图中都包含了完整的路径,请大家看仔细。

首先解压缩后看看Busybox源代码的目录结构,如下图:

构造根文件系统3---Busybox

在源代码目录下有几个文件使我们必须关注的(很多开源代码都有这几个文件,建议在开展实际的工作之前仔细阅读一下这几个文件),主要是:INSTALL、README以及examples目录和docs目录下的文件。

Busybox可裁剪,而且支持像Linux内核那样的图形化配置界面,运行如下命令即可:

构造根文件系统3---Busybox

这个时候可能回报如下错误:

构造根文件系统3---Busybox

这个时候不必着急,之所以回报这个错误,是因为我们采用的配置界面需要终端的一些特殊配置,而这些配置是需要ncurses库的支持,所以当出现这个错误的时候,说明你的编译环境中没有安装此库,使用如下命令安装好这些库即可。

构造根文件系统3---Busybox

在这些库安装好了,之后在运行之前的”make menuconfig”命令,即可出现如下的配置界面:

构造根文件系统3---Busybox

    在这个界面中我们就可以进行裁剪,也就是选中自己需要的功能,其它的就不选择。这里有几个配置选项比较重要,在这单独拿出来说一下,至于完整的选项说明,请见附录。

(1)     指定编译后安装的路径

编译完了Busybox后,我们需要安装,安装可以指定安装路径,在这个界面修改(当然,也可以在Makefile或者编译命令指定)

构造根文件系统3---Busybox

    从上图我们可以看出,Busybox默认的安装路径是源代码目录的_install目录(该目录不存在,安装的时候自动创建)。

(2)     静态/动态编译

我们可以静态或者动态编译Busybox,Busybox支持Glibc和Uclibc。选择动态编译,使得Busybox可执行文件更小,选项开关在下图:

构造根文件系统3---Busybox

经过上诉步骤之后,相比裁剪的工作已近完成了,这个时候选择配置界面的Exit退出,这个时候会弹出对话框,询问是否保存刚刚的配置,这里选择”保存”,之后就可以看到在源代码目录下多了一个.config文件,如下图:

构造根文件系统3---Busybox

.config配置文件里面的内容记录了我们刚刚选中了哪些功能,内容如下:

构造根文件系统3---Busybox


每一个都是名值对的形式,名称是一个环境变量,后面的值如果为”Y”就代表选中,注释行代表裁减掉的功能。

好了,现在配置阶段的事情就做完了,接下来就是编译Busybox了,相信大家对编译开源代码不会陌生,直接执行如下命令即可:

构造根文件系统3---Busybox

编译之后看看源代码目录都生成了一些啥:

构造根文件系统3---Busybox

从上图可以很清楚的看到生成了两个可执行文件,也就是我们需要的Busybox可执行文件,编译阶段的工作也做完了。

接下来我们安装Busybox,使用如下命令:

构造根文件系统3---Busybox

接下来到安装目录_install下看看,都安装了些啥:

构造根文件系统3---Busybox

从最下面的一个”ls”命令可以看出,虽然在/bin目录下有很多命令,但是其实只有一个真正的可执行文件,也就是我们前面的生成的Busybox文件,其它文件都是到Busybox的软链接(可以在配置界面设置为硬链接,这对于系统对inode数量有限制的情况下特别有用)。

至于软链接,这个从”make install”安装命令的执行过程中也可以看出来,如下图:

构造根文件系统3---Busybox

好了,至此,我们的Busybox也就完成了。

虽说Busybox编译成功了,需要的文件也生成了,但是不是意味着我们学习Busybox的过程也结束了呢?显然不是,我们刚刚简单执行了一个”make”命令,就编译成功了,但是我们必须要知道”make”命令背后执行了哪些操作,这个可以从编译过程终端的输出看到执行流程,如下图:

构造根文件系统3---Busybox

这里编译输出非常多,我们主要关注其中标注1和2的两条,分别给出解释:

(1)     解析.config文件

这里就是上图标注1的那句话,主要的功能就是解析.config文件,之前可以看到.config文件中都是一些宏,这里做的就是将整个文件中的宏分别解析出来,存放到一个.h文件中,文件的存放的路径为:

构造根文件系统3---Busybox

    注意:config目录是编译过程中生成的。

文件内容如下:

构造根文件系统3---Busybox

(2)     生成最终的配置文件

通过上面config目录下的文件生成一个完整的.h文件,里面是最终的一个配置文件,内容如下:

文件内容比较多,而且分为几个独立的部分,我们首先来看看最前面的部分:

构造根文件系统3---Busybox

从内容可以看出,这就是我们最终要生成的命令的名字,将它们所有都放在一个数组中。

接下来看看该文件最后部分的内容:

构造根文件系统3---Busybox

从文件内容可以看出,这是上面每个命令的入口函数,命令很有特点,一眼就看出来了哦。从这里可以看出这里是一个函数指针数组,根据传入的下标选择运行不同的函数,这就是为什么在Busybox中命令”ls”的运行效果等同于”busybox ls”,如下图:

构造根文件系统3---Busybox

好了,最后再让我们看看编译完Busybox后的安装目录吧:

构造根文件系统3---Busybox

3.2向Busybox中添加新命令

接下来我们就介绍一下怎么想Busybox中添加自己的命令,这个也就是搞清楚Busybox的组织框架。之前如果有在内核中添加驱动的同学相信在Busybox中添加新的命令难不倒大家哦。

(1)     首先选择命令存放的路径

Busybox目录下有非常多的子目录,每个目录都放着一类命令,例如:net目录放着与网络相关的,shell放置着与shell相关的命令,我们这里只是为了举例说明添加一个命令的流程,所以我将命令放置在如下目录:

构造根文件系统3---Busybox

(2)     其次就是编写命令源文件

我们要运行自己的命令肯定就得编写自己的源代码,这里主要为了说明流程,所以使用如下简答源代码:

构造根文件系统3---Busybox

     这里编写源代码有一点一定要注意,Busybox采用统一的命名风格,这个从之前的函数指针数组也能看出,所以我这里命令是”hello_busybox”,那么我的函数名就一定是”hello_busybox_main”。

(3)     修改相关的编译文件

我们将自己的源文件编译进去之后,整个Busybox是不会理会这个文件的存在,即使你这个时候使用”make”命令编译Busybox,也会发现上面的.c源文件并没有被编译,因为我们并没有将这个文件告诉Busybox的编译系统,类似之前放置驱动程序需要修改内核的Kconfig文件一样,我们也需要修改Busybox中类似的文件。

首先修改如下文件:

构造根文件系统3---Busybox

添加自己的命令,格式仿造其它已经存在的条目即可,修改后内容如下:

构造根文件系统3---Busybox

修改这里主要是使得执行”make menuconfig”命令的时候,配置界面可以出现我们新增的命令,让用户对该命令可以配置,第一行是标示该命令的一个环境变量;第二行是出现在配置界面上的文字,是一个布尔量,取值为”Y”或者”N”;第三行是这个选项的默认值,这里默认是选中的;第四行和第五行是该命令在配置界面的帮助信息。

修改上面的文件只是让配置界面出现我们这个命令,以及根据是否选择置环境变量”HELLO_BUSY_BOX”为”Y”或”N”,但是它还不能影响Busybox的编译系统是否编译我们的源文件,Busybox到现在甚至不知道我们的源文件叫啥名字。

接下来我们还需要修改如下文件:

构造根文件系统3---Busybox

修改后的内容如下:

构造根文件系统3---Busybox

到这里读者应该明白前面修改那个文件最主要的最用了,根据环境变量”HELLO_BUSYBOX”的取值,决定是否编译我们的源文件。

到这主要的工作已经完成了,但是还有部分工作必须得做,首先想想我们的命令(也就是一个名为hello_busybox的指向busybox的软链接文件)生成了放在哪里呢?系统中存放命令的地方很多,例如“/bin”、“/sbin”、“/usr/bin”和“/usr/sbin”等,这就需要修改下面的文件:

构造根文件系统3---Busybox

修改后的内容如下:

构造根文件系统3---Busybox

这里我们主要关注括号里面的三个参数:第一个是命令的名字;第二个是命令存放的路径,第三个是命令的权限。

接下来我们还要做一件非做不可的事情,就是每个命令都有帮助信息,我们这里也需要为新添加的命令增加帮助信息,修改如下文件:

构造根文件系统3---Busybox

修改后的文件如下图:

构造根文件系统3---Busybox

     好了,至此,在Busybox中添加一条新的命令该做的修改该做都做完了,剩下的就是测试添加的命令是否生效,是否可用。

(4)     编译、测试

首先是执行配置操作,”make menuconfig”命令,出现顶层的配置界面,选中下图的那一条,按下回车键:

构造根文件系统3---Busybox

进入子条目后就很容易看到我们添加的那条命令了,如下图中选中的那条:

构造根文件系统3---Busybox

做好了配置工作之后我们就可以执行编译操作了,在看编译过程之前,先让我们看看有没有生成我们的配置文件,如下图:

构造根文件系统3---Busybox

文件内容如下:

构造根文件系统3---Busybox

这里有个很奇怪的问题,我们新加的命令的名字是”hello_busybox”,那么生成的配置文件应该是”hello_busybox.h”,但是各位看官仔细看看上面出现了什么情况:竟然在config目录下生成了hello子目录,然后在里面放置”busybox.h”文件,相信大家也猜到了规律,那就是Busybox会将名字做拆分,以”_”为分割字符,最后一个才是文件名,前面的都是子目录,这个我没有再去验证,但我认为应该是这样的。

好了,接下来我们就执行”make”命令,截图如下:

构造根文件系统3---Busybox

从上图中可以看到,我们新加的命令成功生成,也安装的目录也正确。

接下来我们就去执行一下我们的命令,如下图:

构造根文件系统3---Busybox

从上面图中三条命令的执行情况来看,我们添加命令成功。

4.附录

4.1Busybox实现的简单分析

在这里,我们来简要的分析一下Busybox的实现过程,在前面的第3点中已经提及了一部分这方面的内容。

在前面也分析了Busybox的目录结构,那种分法是比较僵硬的,因为完全是按照目录来划分的,其实如果要更好的理解Busybox的实现,那么我们应该将它划分为两个部分:第一,这部分主要是各个命令(applets)的实现,其实大家也发现了,很多目录都属于这部分,只不过它们按照功能细分了,例如网络命令(networking目录)、编辑命令(editors目录)等,这部分也可以理解为是Busybox(各个命令)的启动代码部分;第二部分则是libbb目录下的内容,也就是Busybox(各个命令)的共享代码部分。

下面我们分别来介绍这两部分的主要内容:

4.1.1applets的实现

目录”applets”包含了Busybox的启动代码(applets.c和Busybox.c),以及几个包含独立命令的子目录。

Busybox从applets/busybox.c文件中的main()函数开始执行,该main函数将变量applet_name赋值为argv[0],然后调用applets/applets.c文件中的run_applet_and_exit()函数继续执行。run_applet_and_exit()函数使用applets[]数组(定义在include/busybox.h中,在include/applets.h中填充内容)将程序的控制权传递给APPLET_main()函数(例如:cat_main()或sed_main())。独立的applet命令从这里开始接管执行。

这就是为什么Busybox下的不同名称的命令调用不同的功能:main()函数使用argv[0]作为参数在applets[]数组中查找合适的指向APPLET_main()函数的函数指针。

Busybox中的applets同样可以通过复用器”busybox”applet(查看libbb/appletlib.c文件中的函数Busybox_main())调用,以及通过单独的shell(在shell/*.c中使用grep命令查找SH_STANDALONE)。关于使用这两种机制调用命令更多的信息可以查看官网信息,其实它们只是通过不同的路径调用APPLET_main()函数。

命令(applet)子目录(archival,console-tools, coreutils, debianutils, e2fsprogs, editors, findutils, init, loginutils,miscutils, modutils, networking, procps, shell, sysklogd, and util-linux)对应着menuconfig中的子菜单的配置项。每一个子目录都包含实现相应子菜单命令的代码,每一个子目录下有一个Config.src文件,用于产生menuconfig菜单,有一个Kbuild.src文件用于生产类似Makefile功能的文件。

运行时的—help信息是保存在usage_message[]数组中的,该数组通过从usage.h中获取帮助信息,在applets/applets.c中初始化该数组。在编译的过程中,这些帮助信息同样被用于在docs目录下产生Busybox的文档(html,txt和man页面格式)

4.1.2libbb的实现

绝大多数非启动且在各个Busybox命令(applets)*享的代码都放在libbb目录下。该目录多年未清理,比较杂乱。如果有人想寻找一个好的项目参加到Busybox的开发中,那么将libbb进行文档结构化将会是十分有帮助的,而且是个不错的锻炼机会。  

在libbb的共同主题包括分配功能测试失败和中止程序的错误消息,以便调用者不用测试返回值(xmalloc(),xstrdup()等),经过封装的open(),close(),read(),write(),这些经过封装的函数可以测试自己的失败和/或自动重试,也包含链表管理功能的函数(llist.c),命令行参数的解析(getopt32.c),和一大堆其它的内容。

4.2Busybox配置选项说明

下面说一下Busybox中主要的配置项及其含义,主要是顶层的配置项:顶层的配置项分为两类,第一类是支持的命令,这部分其实也就是各个子目录的配置,在2.2Busybox目录结构简介一节已经提到了;第二类就是Busybox自身相关的,例如:编译选项、安装路径等,这部分在3.1编译Busybox一节已经提到了。