YIFEIYANG 易飞扬的博客

5十一/090

BusyBox之追加Applet的方法

  • 博主:易飞扬
  • 原文链接 : http://www.yifeiyang.net/busyboxu002639s-an-additional-method-of-applet/
  • 转载请保留上面文字。




  • 扩充BusyBox,追加Applet的方法

    缺省情况下,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」文件。

    评论 (0) 引用 (0)

    还没有评论.


    发表评论


    还没有引用.