2017年(111)
分类: LINUX
2017-06-19 18:00:48
缺省情况下,BusyBox是对桌面linux的一个简化,如果要定制比较特殊的功能,比如像操作文件系统一样操作Flash存储器,那么就需要预先定制BusyBox的Applet。这一次,我们就介绍一下为BusyBox追加功能(Applet)的方法。
窥探BusyBox的源代码如果要添加Applet,首先必须了解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的步骤:
我们需要在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部:
1 2 3 4 5 |
#include "libbb.h" int mtd_debug_main(int argc, char **argv) { printf("hello"); } |
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 |
有了以上的实践,我们将实际的「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" |
在 下载「mtd-utils」的源代码。解压后,在「mtd-utils-1.2.0」目录中打开「mtd_debug.c」文件。
注意到下面一行代码
1 |
#include |
由于是编译时所需的头文件,将其拷贝到「/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 #include #include "libbb.h" ←添加 ... int main (int argc,char *argv[]) ↓ int mtd_debug_main (int argc,char *argv[]) |
首先,用「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 |
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中用将信息字符串简化,共有等方式等减小程序整体的大小。
使用libbblibbb是各个Applet间使用的函数库,利用它可以提高BusyBox的高效,减小其尺寸。其函数都在「include/libb.h」文件中定义,这里介绍「x函数」「bb_show_usage」「getopt32」3个被经常用到的函数。
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函数来替换。
上面,我们在「usage.h」文件中添加了Applet的帮助信息。使用bb_show_usage函数,就可以将添加到usage.h中的信息显示相互来。这些放在「usage.h」文件中的信息字符串,所有的Applet都可以共用,所以一定程度上节约了文件大小。
「mtd_debug」的例子中将「showusage」函数用 bb_show_usage() 函数替换就可以了。
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」文件。