缺省情况下,BusyBox是对桌面linux的一个简化,如果要定制比较特殊的功能,比如像操作文件系统一样操作Flash存储器,那么就需要预先定制BusyBox的Applet。这一次,我们就介绍一下为BusyBox追加功能(Applet)的方法。

窥探BusyBox的源代码

如果要添加Applet,首先必须了解BusyBox的源代码结构。

BusyBox的魅力一文中,我们已经知道了BusyBox就是一个Applet的集合体。在设定BusyBox的时候,每个Applet会有一个目录,其目录下就保存了对应的源代码。比如「editors」目录下,保存了BusyBox的「Editors」项目的Applet(如果是「vi」,则源代码是「editors/vi.c」)。

除此以外,我们还需要知道「libbb」。libbb是各个Applet间使用的函数库,正因为有了它才最大限度的缩小了BusyBox的尺寸。其源代码保存在「libbb」目录下。

Applet是怎样启动的

为了更好地理解Applet的源代码,我们需要了解其启动过程。

我们已经知道BusyBox中实际执行的各个命令都是link到/bin/busybox,其关键代码就是libbb/appletlib.c。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(int argc ATTRIBUTE_UNUSED, char **argv)
{
    applet_name = argv[0];
    if (applet_name[0] == '-')
        applet_name++;
    applet_name = bb_basename(applet_name);

    parse_config_file();

    run_applet_and_exit(applet_name, argv);

    full_write2_str(applet_name);
    full_write2_str(": applet not found\n");
    xfunc_die();
}

该main函数是BusyBox的main函数。argv[0]是applet_name,即实际的命令名称。比如「/bin/ls」的情况下,applet_name就是「/bin/ls」。第6行用bb_basename函数去除applet_name中的目录名,得到命令名。「/bin/ls」的情况下,就是「ls」。在第10行通过run_applet_and_exit调用对应的Applet。实际调用的是<Applet名>_main这样的一个函数。比如applet_name是「ls」的情况下,调用的函数就是ls_main。

如果在源代码文件「coreutils/ls.c」下,我们就可以找到以下的函数定义。

1
int ls_main(int argc UNUSED_PARAM, char **argv)

这回我们添加「mtd-utils」中的「mtd_debug」命令。「mtd-utils」经常被用到嵌入式系统中来处理Flash存储器,但是一般没有包含在BusyBox中。「mtd_debug」命令是用来浏览,写入Flash存储器信息的命令。

下面是添加Applet的步骤:

  • 编译菜单中添加
  • 制作Applet的原型
  • 修改Makefile
  • 移植Applet
  • 调试Applet
编译菜单中添加

我们需要在BusyBox的设定画面中添加「mtd_debug」Applet的选项。而配置菜单是由「Config.in」文件管理的。该文件存在于各个子目录中。

首先,为保存我们的Applet,我们制作一个「mtd-utils」目录。

1
# mkdir mtd-utils

接下来,创建「mtd-utils/Config.in」文件,首先在根目录下的「Config.in」文件中追加:

1
2
3
source modutils/Config.in
source mtd-utils/Config.in  ←追加
source util-linux/Config.in

「mtd-utils/Config.in」的文件内容如下所示:

1
2
3
4
5
6
7
8
9
menu "MTD utils"

config MTD_DEBUG
    bool "mtd_debug"
    default n
    help
    Enable support mtd_debug

endmenu

实际的显示如下图所示:

BusyBox的主画面

选择MTD utils后画面

制作Applet的原型

接下来,我们来设计一个Applet原型,简单起见,只输出「hello」字符。大体需要下面3部:

  • 在Applet源文件中定义<Applet_name>_main函数
  • 在「include/applets.h」文件中添加<Applet_name>_main函数声明
  • 在「include/usage.h」文件中添加Applet帮助
在Applet源文件中定义<Applet_name>_main函数
建立「mtd-utils/mtd_debug.c」文件,内容如下所示:
1
2
3
4
5
#include "libbb.h"

int mtd_debug_main(int argc, char **argv) {
        printf("hello");
}
在「include/applets.h」文件中添加<Applet_name>_main函数声明
「applets.h」中添加以下代码:
1
2
3
USE_MT(APPLET(mt, _BB_DIR_BIN, _BB_SUID_NEVER))
USE_MTD_DEBUG(APPLET(mtd_debug, _BB_DIR_SBIN, _BB_SUID_NEVER))  ←这里
USE_MV(APPLET(mv, _BB_DIR_BIN, _BB_SUID_NEVER))

mtd_debug是Applet的名称,_BB_DIR_SBIN是指把该Applet链接到「/sbin」目录,_BB_SUID_NEVER是指「busybox」执行文件的suid位无效。另外,添加USE_MTD_DEBUG的时候,要注意前后的字母顺序,如果不按顺序,有可能出错。

在applets.h代码中可以看到以下的数组

1
2
3
static struct bb_applet applets[] = {
// ...
}

该数组就是BusyBox调用<Applet_name>_main函数的时候使用的。我们刚才追加的USE_MTD_DEBUG(APPLET(mtd_debug,_BB_DIR_SBIN, _BB_SUID_NEVER)) 就被展开为 {mtd_debug_main, 2, 0}。

在「include/usage.h」文件中添加Applet帮助
BusyBox启动某个Applet时如果遇到错误,会表示一段帮助信息,现在,我们就添加这段信息。在「include/usage.h」文件中像以下输入,这里我们输入的是空信息。
1
2
3
4
5
6
#define mtd_debug_trivial_usage \
        "\n"
#define mtd_debug_full_usage "\n\n" \
        "\n"
#define mtd_example_usage \
        "\n"

 <applet_name>_trivial_usage是简单使用帮助, <applet_name>_full_usage是详细解释,<applet_name>_example_usage是举例说明。

修改Makefile

在Makefile中需要指定编译「mtd-utils」目录的信息,如下所示:

1
2
3
4
5
6
7
8
9
10
libs-y:= \
                        archival/ \
(...)
                        modutils/ \
                        mtd-utils/ \  ←这里
(..)

# 将mtd_debug.c文件作为编译对象
lib-y:=
lib-$(CONFIG_MTD_DEBUG)          += mtd_debug.o
动作确认

make menuconfig后选择「mtd_debug」后编译。如果有以下输出则表示修改正确。

1
2
$ ./busybox mtd_debug
hello
移植,调试Applet

有了以上的实践,我们将实际的「mtd_debug」移植过来。

使用「nandsim」来模拟Flash存储器(在PC的RAM上虚拟一块区域来使用)。nandsim 内嵌在 Linux 内核中,作为标准NAND-Flash的模拟器来使用。首先来搭建「nandsim」的环境:

1. 用root身份建立/加载设备文件
1
2
# mknod /dev/mtd0 c 90 0
# modprobe nandsim

このカーネルモジュールは、RAM上に仮想的なNANDフラッシュメモリを構築します。カーネルのメッセージバッファの内容
を表示する「dmesg」コマンドで、カーネルメッセージを確認してみると、以下のように128Mbytesの仮想的なNANDフラッシュ
メモリが作られていることが分かります。

用 dmesg 命令,我们可以查看在RAM上已经建立了一个128Mbytes的虚拟NAND-Falsh。

1
2
Creating 1 MTD partitions on "NAND 128MiB 1,8V 8-bit":
0x00000000-0x08000000 : "NAND simulator partition 0"
2. 编写并移植Applet

在 这里 下载「mtd-utils」的源代码。解压后,在「mtd-utils-1.2.0」目录中打开「mtd_debug.c」文件。

注意到下面一行代码

1
#include <mtd/mtd-user.h>

由于是编译时所需的头文件,将其拷贝到「/usr/include」下:

1
# cp mtd-utils-1.2.0/include/mtd /usr/include/ -a

接下来,因为在BusyBox中使用的是「mtd_debug_main」函数,而不是main函数,所以将其替换。另外,添加#include"libbb.h"一行。最后把修改好的mtd_debug.c文件拷贝到BusyBox的「mtd-utils」目录下就好了。

1
2
3
4
5
6
7
8
#include <fcntl.h>
#include <mtd/mtd-user.h>
#include "libbb.h"  ←添加

...
int main (int argc,char *argv[])
   ↓
int mtd_debug_main (int argc,char *argv[])
mtd_debug Applet功能确认

首先,用「mtd_debug info <设备名>」来确认「/dev/mtd0」的容量等信息是否正确。

1
2
3
4
5
6
7
8
$ ./busybox mtd_debug info /dev/mtd0
mtd.type = MTD_NANDFLASH
mtd.flags = MTD_CAP_NANDFLASH
mtd.size = 134217728 (128M)
mtd.erasesize = 16384 (16K)
mtd.writesize = 512
mtd.oobsize = 16
regions = 0
调试Applet

BusyBox中的Applet需要尽量做到短小精干,即使1byte也不能多。实际上,BusyBox内部就准备了许多减小尺寸的技巧。

用最简单的代码表述
  • 简单的错误处理
     

    一般的程序都有许多谨慎的错误处理功能。比如打开文件失败的情况需要释放内存,然后返回调用函数,最后表示错误信息,最后exit(1)结束程序等步骤。

  而BusyBox中释放内存,错误信息表示等的步骤都被省略,直接调用exit(1)终了处理。这样一来,尺寸肯定是减小了。BusyBox中的观点是没有释放的内存由OS来释放,这样就没有内存泄露的问题了。

来比较一下修改前后的代码:

之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int flash_to_file (int fd,u_int32_t offset,size_t len,const char *filename)
{
        outfd = creat (filename,O_WRONLY);
        if (outfd < 0)
        {
                perror ("creat()");
                goto err1;
        }

err1:
        if (buf != NULL)
                free (buf);

        return (1);
}

之后

1
2
3
4
5
6
7
8
9
10
int flash_to_file (int fd,u_int32_t offset,size_t len,const char *filename)
{
        outfd = creat (filename,O_WRONLY);
        if (outfd < 0)
        {
                perror ("creat()");
                exit(1);
        }
        return (1);
}
  • 用简单的信息表示

  CUI程序经常用printf来输出一些信息。这些信息字符串也被作为程序的一部分保存在执行文件当中。BusyBox中用将信息字符串简化,共有等方式等减小程序整体的大小。

使用libbb

libbb是各个Applet间使用的函数库,利用它可以提高BusyBox的高效,减小其尺寸。其函数都在「include/libb.h」文件中定义,这里介绍「x函数」「bb_show_usage」「getopt32」3个被经常用到的函数。

  • x函数

      x函数是以x开头的一系列函数。比如「xopendir」「xmalloc」「xstrdup」「xstrndup」「xmalloc」「xzalloc」「xfopen」「xopen」等。这些标准函数做到了简单地错误处理,简单的信息表示。比如用xfopen函数打开文件失败的情况下,只显示"can't open <file_name>"后,即调用exit(1)退出。

比如

1
2
3
4
5
if ((fd = open (argv[2],O_SYNC | open_flag)) < 0)
{
    perror ("open()");
    exit (1);
}

中的open函数就可以简单地用xopen函数来替换。

  • bb_show_usage

    上面,我们在「usage.h」文件中添加了Applet的帮助信息。使用bb_show_usage函数,就可以将添加到usage.h中的信息显示相互来。这些放在「usage.h」文件中的信息字符串,所有的Applet都可以共用,所以一定程度上节约了文件大小。

 「mtd_debug」的例子中将「showusage」函数用 bb_show_usage() 函数替换就可以了。

  • getopt32

    getopt32类似于linux上的getopt函数,用来解释命令行。

比如

1
flags = getopt32(argv, “ab”);

如果输入「-a」命令行参数时,「flags」的第1bit是「1」,输入「-b」时,「flags」的第2bit是「1」。

另外,像以下的使用也是可以的

1
flags = getopt32(argv, "a:b”, &value);

「a:」表示输入「-a」命令行参数。具体的值保存到「value」中。关于「getopt32」的使用,可以参考「libbb/getopt32.c」文件。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐