扩充BusyBox,追加Applet的方法
缺省情况下,BusyBox是对桌面linux的一个简化,如果要定制比较特殊的功能,比如像操作文件系统一样操作Flash存储器,那么就需要预先定制BusyBox的Applet。这一次,我们就介绍一下为BusyBox追加功能(Applet)的方法。窥探BusyBox的源代码如果要添加Applet,首先必须了解BusyBox的源代码结构。在BusyBox的魅力一文中,我们
缺省情况下,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。
该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」下,我们就可以找到以下的函数定义。
这回我们添加「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」目录。
接下来,创建「mtd-utils/Config.in」文件,首先在根目录下的「Config.in」文件中追加:
「mtd-utils/Config.in」的文件内容如下所示:
实际的显示如下图所示:
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」文件,内容如下所示:
-
在「include/applets.h」文件中添加<Applet_name>_main函数声明
- 「applets.h」中添加以下代码:
mtd_debug是Applet的名称,_BB_DIR_SBIN是指把该Applet链接到「/sbin」目录,_BB_SUID_NEVER是指「busybox」执行文件的suid位无效。另外,添加USE_MTD_DEBUG的时候,要注意前后的字母顺序,如果不按顺序,有可能出错。
在applets.h代码中可以看到以下的数组
该数组就是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」文件中像以下输入,这里我们输入的是空信息。
<applet_name>_trivial_usage是简单使用帮助, <applet_name>_full_usage是详细解释,<applet_name>_example_usage是举例说明。
-
修改Makefile
在Makefile中需要指定编译「mtd-utils」目录的信息,如下所示:
-
动作确认
make menuconfig后选择「mtd_debug」后编译。如果有以下输出则表示修改正确。
-
移植,调试Applet
有了以上的实践,我们将实际的「mtd_debug」移植过来。
使用「nandsim」来模拟Flash存储器(在PC的RAM上虚拟一块区域来使用)。nandsim 内嵌在 Linux 内核中,作为标准NAND-Flash的模拟器来使用。首先来搭建「nandsim」的环境:
-
1. 用root身份建立/加载设备文件
このカーネルモジュールは、RAM上に仮想的なNANDフラッシュメモリを構築します。カーネルのメッセージバッファの内容
を表示する「dmesg」コマンドで、カーネルメッセージを確認してみると、以下のように128Mbytesの仮想的なNANDフラッシュ
メモリが作られていることが分かります。
用 dmesg 命令,我们可以查看在RAM上已经建立了一个128Mbytes的虚拟NAND-Falsh。
-
2. 编写并移植Applet
在 这里 下载「mtd-utils」的源代码。解压后,在「mtd-utils-1.2.0」目录中打开「mtd_debug.c」文件。
注意到下面一行代码
由于是编译时所需的头文件,将其拷贝到「/usr/include」下:
接下来,因为在BusyBox中使用的是「mtd_debug_main」函数,而不是main函数,所以将其替换。另外,添加#include"libbb.h"一行。最后把修改好的mtd_debug.c文件拷贝到BusyBox的「mtd-utils」目录下就好了。
-
mtd_debug Applet功能确认
首先,用「mtd_debug info <设备名>」来确认「/dev/mtd0」的容量等信息是否正确。
-
调试Applet
BusyBox中的Applet需要尽量做到短小精干,即使1byte也不能多。实际上,BusyBox内部就准备了许多减小尺寸的技巧。
-
用最简单的代码表述
- 简单的错误处理
一般的程序都有许多谨慎的错误处理功能。比如打开文件失败的情况需要释放内存,然后返回调用函数,最后表示错误信息,最后exit(1)结束程序等步骤。
而BusyBox中释放内存,错误信息表示等的步骤都被省略,直接调用exit(1)终了处理。这样一来,尺寸肯定是减小了。BusyBox中的观点是没有释放的内存由OS来释放,这样就没有内存泄露的问题了。
来比较一下修改前后的代码:
之前
之后
- 用简单的信息表示
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)退出。
比如
中的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函数,用来解释命令行。
比如
如果输入「-a」命令行参数时,「flags」的第1bit是「1」,输入「-b」时,「flags」的第2bit是「1」。
另外,像以下的使用也是可以的
「a:」表示输入「-a」命令行参数。具体的值保存到「value」中。关于「getopt32」的使用,可以参考「libbb/getopt32.c」文件。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)