一、busybox简介
BusyBox 是一个集成了一百多个最常用linux命令和工具的开源软件,是嵌入式系统开发中创建根文件系统的工具。BusyBox 包含了一些简单的工具,例如ls、cat和echo等等,还包含了一些更大、更复杂的工具,例grep、find、mount以及telnet。
Busybox采用busybox 1.24.2版本
1、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等 |
libbb |
绝大多数非启动且在各个Busybox命令(applets)中共享的代码 |
2、busybox程序框架
BusyBox程序架构为BusyBox的运行提供了基本支持,程序架构主要代码在applets/applets.c文件。 在定义ENABLE_BUILD_LIBBUSYBOX宏为真的情况下,busybox程序的入口main函数在/applets/applets.c中;如果定义宏ENABLE_BUILD_LIBBUSYBOX为假时,busybox的入口main函数在/libbb/appletlib.c文件中。默认,busybox入口函数main位于/libbb/appletlib.c文件。
#if ENABLE_BUILD_LIBBUSYBOX
int lbb_main(char **argv)
#else
int main(int argc UNUSED_PARAM, char **argv)
#endif
{
#if !BB_MMU
/* NOMMU re-exec trick sets high-order bit in first byte of name */
if (argv[0][0] & 0x80) {
re_execed = 1;
argv[0][0] &= 0x7f;
}
#endif
#if defined(SINGLE_APPLET_MAIN)
/* Only one applet is selected in .config */
if (argv[1] && is_prefixed_with(argv[0], "busybox")) {
/* "busybox <applet> <params>" should still work as expected */
argv++;
}
/* applet_names in this case is just "applet\0\0" */
lbb_prepare(applet_names IF_FEATURE_INDIVIDUAL(, argv));
return SINGLE_APPLET_MAIN(argc, argv);
#else
lbb_prepare("busybox" IF_FEATURE_INDIVIDUAL(, argv));
applet_name = argv[0];
if (applet_name[0] == '-')
applet_name++;
applet_name = bb_basename(applet_name);
parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
run_applet_and_exit(applet_name, argv);
/*bb_error_msg_and_die("applet not found"); - sucks in printf */
full_write2_str(applet_name);
full_write2_str(": applet not found\n");
/* POSIX: "If a command is not found, the exit status shall be 127" */
exit(127);
#endif
}
run_applet_and_exit函数将根据applet的名字,找到相应的applet,将执行相应命令的xx_main函数,然后直接退出。在run_applet_and_exit中,所调用的find_applet_by_name中用bsearch对applets进行搜索,并返回applet。Busybox支持的命令的名称和命令的函数定义在include/applet_tables.h文件中。
二、init解析
1、init简介
init进程是由内核启动的第一个(也是唯一一个)用户进程(进程ID为1),init进程根据配置文件决定启动哪些程序,例如:执行某些脚本、启动shell或运行用户程序等。init是后续所有进程的发起者,例如:init进程启动/bin/sh程序后,我们才能够在控制台上输入各种命令。
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系统,目前大多数Linux发行版本使用的都是System V init。在嵌入式系统中常使用的是Busybox集成的init程序。
2、init的启动过程
kernel启动的最后就是调用kernel_init函数启动init进程,最终在init_post函数中调用run_init_process函数运行指定的init程序,代码在kernel/init/main.c文件中。
static int __init kernel_init(void * unused)
{
wait_for_completion(&kthreadd_done);
lock_kernel();
set_mems_allowed(node_states[N_HIGH_MEMORY]);
set_cpus_allowed_ptr(current, cpu_all_mask);
init_pid_ns.child_reaper = current;
cad_pid = task_pid(current);
smp_prepare_cpus(setup_max_cpus);
do_pre_smp_initcalls();
start_boot_trace();
smp_init();
sched_init_smp();
do_basic_setup();
/* Open the /dev/console on the rootfs, this should never fail */
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");
(void) sys_dup(0);
(void) sys_dup(0);
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
init_post();
return 0;
}
static noinline int init_post(void)
__releases(kernel_lock)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
run_init_process函数一旦执行创建进程成功,将不会返回,而是通过操作内核栈进入用户空间。所以init_post函数尾部并没有运行了四个init进程,而是根据优先级,一旦某一个init程序运行成功,就不往下继续执行了。
/sbin/init程序在busybox中由init/init.c文件中init_main函数实现。
int init_main(int argc UNUSED_PARAM, char **argv)
{
if (argv[1] && strcmp(argv[1], "-q") == 0) {
return kill(1, SIGHUP);
}
if (!DEBUG_INIT) {
/* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
if (getpid() != 1
&& (!ENABLE_FEATURE_INITRD || applet_name[0] != 'l') /* not linuxrc? */
) {
bb_error_msg_and_die("must be run as PID 1");
}
#ifdef RB_DISABLE_CAD
/* Turn off rebooting via CTL-ALT-DEL - we get a
* SIGINT on CAD so we can shut things down gracefully... */
reboot(RB_DISABLE_CAD); /* misnomer */
#endif
}
die_func = sleep_much;
/* Figure out where the default console should be */
console_init();
set_sane_term();
xchdir("/");
setsid();
/* Make sure environs is set to something sane */
putenv((char *) "HOME=/");
putenv((char *) bb_PATH_root_path);
putenv((char *) "SHELL=/bin/sh");
putenv((char *) "USER=root"); /* needed? why? */
if (argv[1])
xsetenv("RUNLEVEL", argv[1]);
#if !ENABLE_FEATURE_EXTRA_QUIET
/* Hello world */
message(L_CONSOLE | L_LOG, "init started: %s", bb_banner);
#endif
/* Check if we are supposed to be in single user mode */
if (argv[1]
&& (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1'))
) {
new_init_action(RESPAWN, bb_default_login_shell, "");
} else {
parse_inittab();
}
#if ENABLE_SELINUX
if (getenv("SELINUX_INIT") == NULL) {
int enforce = 0;
putenv((char*)"SELINUX_INIT=YES");
if (selinux_init_load_policy(&enforce) == 0) {
BB_EXECVP(argv[0], argv);
} else if (enforce > 0) {
/* SELinux in enforcing mode but load_policy failed */
message(L_CONSOLE, "can't load SELinux Policy. "
"Machine is in enforcing mode. Halting now.");
return EXIT_FAILURE;
}
}
#endif
/* Make the command line just say "init" - thats all, nothing else */
strncpy(argv[0], "init", strlen(argv[0]));
/* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
while (*++argv)
nuke_str(*argv);
/* Set up signal handlers */
if (!DEBUG_INIT) {
struct sigaction sa;
/* Stop handler must allow only SIGCONT inside itself */
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sigdelset(&sa.sa_mask, SIGCONT);
sa.sa_handler = stop_handler;
sigaction_set(SIGTSTP, &sa); /* pause */
sigaction_set(SIGSTOP, &sa); /* pause */
bb_signals_recursive_norestart(0
+ (1 << SIGINT) /* Ctrl-Alt-Del */
+ (1 << SIGQUIT) /* re-exec another init */
#ifdef SIGPWR
+ (1 << SIGPWR) /* halt */
#endif
+ (1 << SIGUSR1) /* halt */
+ (1 << SIGTERM) /* reboot */
+ (1 << SIGUSR2) /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB
+ (1 << SIGHUP) /* reread /etc/inittab */
#endif
, record_signo);
}
run_actions(SYSINIT);
check_delayed_sigs();
/* Next run anything that wants to block */
run_actions(WAIT);
check_delayed_sigs();
run_actions(ONCE);
while (1) {
int maybe_WNOHANG;
maybe_WNOHANG = check_delayed_sigs();
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
maybe_WNOHANG |= check_delayed_sigs();
/* Don't consume all CPU time - sleep a bit */
sleep(1);
maybe_WNOHANG |= check_delayed_sigs();
if (maybe_WNOHANG)
maybe_WNOHANG = WNOHANG;
while (1) {
pid_t wpid;
struct init_action *a;
wpid = waitpid(-1, NULL, maybe_WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
message(L_LOG, "process '%s' (pid %d) exited. "
"Scheduling for restart.",
a->command, wpid);
}
maybe_WNOHANG = WNOHANG;
}
} /* while (1) */
}
init程序的执行流程如下:
A、使用console_init函数初始化控制台,打开文件/dev/console作为保准输入,然后将文件描述符复制给文件描述符0、1、2
B、使用parse_inittab函数解析inittab配置文件
C、设置信号处理过程
D、执行sysinit和wait和once,然后在while(1)死循环中去执行respwan和askfirst。
3、init配置文件
init的配置文件inittab的语法格式如下:
:id:rstate:action:process
A、id 字段是最多 4 个字符的字符串,用来唯一标志表项。
B、rstate(run state)字段定义该记录项被调用时的运行级别,rstate 可以由一个或多个运行级别构成,也可以是空,空则代表运行级别 0-6。当请求init改变运行级别时,那些 rstate 字段中不包括新运行级别的进程将收到 SIGTERM 警告信号,并且最后被杀死
C、action 字段告诉init执行的动作,即如何处理process字段指定的进程,Busybox定义了八种action:
action |
含义 |
sysinit |
为init提供初始化命令脚本的路径,指定的进程在访问控制台之前执行 |
respawn |
如果 process字段指定的进程不存在,则启动该进程,init 不等待处理结束,而是继续扫描inittab文件中的后续进程,当这样的进程终止时,init 会重新启动它,如果这样的进程已存在,则什么也不做。 |
askfirst |
类似respawn,主要用途是减少系统上执行的终端应用程序的数量,会促使init在控制台上显示“Please press Enter to active this console”的信息,并在重新启动进程之前等待用户按下“enter”键 |
wait |
告诉init必须等到相应的进程执行完成之后才能继续执行,启动 process 字段指定的进程,并等到处理结束才去处理inittab中的下一记录项。 |
once |
仅执行相应的进程一次,而且不会等待它执行完成,启动process字段指定的进程,不等待处理结束就去处理下一记录项。 |
ctratldel |
当按下Ctrl+Alt+Delete组合键时,执行相应的进程 |
shutdown |
当系统关机时,执行相应的进程 |
restart |
当init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身 |
D、<process>:要执行的程序,可以为可执行程序也可以是脚本,如果<process>字段前面有”-”字符,代表这个程序是可交互的,例如:/bin/sh程序
4、inittab实例
#first:run the system script file
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:-/sbin/reboot
#umount all filesystem
::shutdown:/bin/umount -a -r
#restart init process
::restart:/sbin/init
三、busybox制作根文件系统
1、修改Makefile
Arch = arm
CROSS_COMPILE = arm-linux-
2、Busybox工程配置
make menuconfig对busybx进行配置
Busybox Settings--->
Build Options--->
[*]Build BusyBox as a static binary(no shared libs)
Busybox Settings--->
Busybox Library Tuning--->
[*]vi-style line editing commands
[*]Fancy shell prompts
Linux Module Utilities--->
[ ]Simplified modutils
[*]insmod
[*]rmmod
[*]lsmod
[*]modprobe
[*]depmod
Linux System Utilities--->[*]mdev
[*]Support /etc/mdev.conf
[*]Support subdirs/symlinks
[*]Support regular expressions substitutions when renaming dev
[*]Support command execution at device addition/removal
[*]Support loading of firmwares
3、编译工程
make -j4
编译过程报错如下:
coreutils/lib.a(sync.o): In function `sync_main':
sync.c:(.text.sync_main+0x7c): undefined reference to `syncfs'
collect2: ld returned 1 exit status
make: *** [busybox_unstripped] Error 1
错误分析:
Coreutils模块下的sync.c文件中syncfs未定义,取消sync支持
修改如下:
Coreutils --->
[ ] sync
继续编译,成功
4、inittab文件制作
#first:run the system script file
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
::ctrlaltdel:-/sbin/reboot
#umount all filesystem
::shutdown:/bin/umount -a -r
#restart init process
::restart:/sbin/init
5、rcS文件制作
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
//S表示系统为单用户模式
runlevel=S
prevlevel=N
//linux系统的umask值
umask 022
//导出环境变量
export PATH runlevel prevlevel
//挂载所有的文件系统,/etc/fstab文件中定义的文件系统
mount -a
echo /sbin/mdev > /proc/sys/kernel/hotplug
//mdev配合linux驱动生成相应的/dev目录下的设备文件。
mdev -s
//在HOSTNAME文件中设置主机名称
/bin/hostname -F /etc/sysconfig/HOSTNAME
//系统启动后设置IP
ifconfig eth0 192.168.6.210
6、fstab文件
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
7、profile文件
# No core files by default
ulimit -S -c 0 > /dev/null 2>&1
USER="`id -un`"
LOGNAME=$USER
PS1='[\u@\h \W]\# '
PATH=$PATH
HOSTNAME=`/bin/hostname`
export USER LOGNAME PS1 PATH
profile文件需要放到/etc目录下,busybox会自动调用
8、登录界面制作
在inittab中用/bin/login或者/sbin/getty去替代/bin/sh。
::sysinit:-/bin/getty
s3c2410_serial0:23456:respawn:/sbin/getty -L s3c2410_serial0 115200 vt100
添加/bin/getty后还需要添加密码验证文件
在etc目录下添加passwd和shadow文件,shadow文件中加密的字段可以为空
Passwd:
root:x:0:0:root:/root:/bin/sh
Shadow:
root::16732:0:99999:7:::
9、动态链接库的支持
非静态程序的运行需要动态链接库的支持,ARM交叉编译工具编译生成的应用程序要在嵌入式系统上运行必须有动态链接库的支持,因此需要将ARM的动态链接库文件拷贝到嵌入式系统上。ARM交叉编译工具编译过程中指定静态编译链接的程序则可以直接运行在嵌入式系统上。
如果嵌入式系统需要考虑空间节省可以使用arm-linux-strip去除动态链接库文件的符号表信息。
将ARM工具链的lib目录中的动态链接库文件拷贝到根文件系统lib目录中
10、文件系统镜像制作
制作不同文件系统镜像需要不同文件系统的镜像制作工具,文件系统镜像制作工具如下:
mkyaffs2image:制作yaffs2文件系统镜像,mkyaffs2image.rar
mkcramfs:制作cramfs文件系统镜像,cramfs-1.1.tar.gz
mkfs.jffs2 :制作jffs2文件系统镜像,mtd-utils
mkfs.ubifs:制作ubifs文件系统镜像,mtd-utils
将下载的工具包编译后得到的文件系统镜像制作工具拷贝到/usr/sbin目录。
制作各种文件系统镜像的命令如下:
yaffs2文件系统镜像:
mkyaffs2imagerootfsrootfs.yaffs2
jffs2文件系统镜像:
mkfs.jffs2 -r rootfs -o rootfs.jffs2 -p -l -n -e 0x4000
cramfs文件系统镜像:
mkcramfs rootfs rootfs.cramfs
ubifs文件系统镜像:
mkfs.ubifs -r rootfs -m 2048 -e 129024 -c 812 -o rootfs.ubifsext4
r:制定文件内容的位置
-m:页面大小
-e:逻辑擦除块大小
-p:物理擦除块大小
-c:最大的逻辑擦除块数量
以上的命令最多可以访问129024*812=100M空间
Mtd-utils编译报错处理:
ubifs-utils/mkfs.ubifs/compr.c:28:23: fatal error: lzo/lzo1x.h: No such file or directory
解决:yum install lzo-devel.x86_64
warning: uuid/uuid.h: No such file or directory
解决:yum install libuuid-devel
mtd-utils下载:
git clone git://git.infradead.org/mtd-utils.git
其余文件系统镜像制作工具见附件