本文内容:
1- 内核启动最后阶段调用的第一个应用程序接口
2- 制作最小根文件系统的内容
1- 终端设备: /dev/console, /dev/null
2- init程序,这里指busybox程序
3- 配置文件,这里指/etc/inittab 文件
4- 配置文件指定的应用程序,例如ls, cp, init等
5- C库文件--busybox会用到printf, 需要由c库等其他库文件
3- 根文件系统中启动应用程序配置/etc/inittab
1- 内核接口分析init_post
uboot启动内核,内核启动应用程序,应用程序存在于根文件系统中。
1.1- init_post()
内核启动应用程序的接口为init_post().
该函数打开输出终端,并且启动应用程序run_init_process(execute_command);
其中ramdisk_execute_command是uboot传给内核的,再uboot阶段输入print可以看到该值为:/linuxrc
对于内核启动的应用程序是死循环不会返回,如果应用程序出错则依次执行/sbin/init; /etc/init;/bin/init;/bin/sh。
如果这几个都失败则打印异常“Failed to execute %s. Attempting defaults...”
static int noinline init_post(void)
{
/*1- 打开终端输出,并复制标注输出,标准错误输出 */
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) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*2- run_init_process启动命令行传入的程序或者/sbin/init */
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) { run_init_process为死循环
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.");
}
/* 3- execute_command 值为init=xxx */
static int __init init_setup(char *str)
{
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup);
查看uboot传递的内核参数,其中传入的应用程序名字为/linuxrc。
OpenJTAG> print
bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
bootdelay=2
baudrate=115200
ethaddr=08:00:3e:26:0a:5b
netmask=255.255.255.0
mtdids=nand0=nandflash0
mtdparts=mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)
ipaddr=192.168.31.16
serverip=192.168.31.15
stdin=serial
stdout=serial
stderr=serial
partition=nand0,0
mtddevnum=0
mtddevname=bootloader
Environment size: 452/131068 bytes
1.2- 擦除、烧录文件系统
1.2.1 删除nand flash上的文件系统
开发板重新上电,上电过程中进入uboot ,输入擦除文件系统命令:nand erase root
操作后,开发板重新上电,发现开发板无法进入到linux系统,打印如下:
和init_post()函数中的异常打印一致。
1.2.2 重新烧写文件系统
重新给开发板烧写跟文件系统: fs_mini.yaffs2
烧写完成再uboot中输入b重启系统,发现linux系统正常启动。
烧写yaffs文件系统
重启系统
系统启动成功后,可以发现有init程序和shell程序
2- busybox源码分析
系统起来后,我们可以使用ls, cp等命令,这些命令都指向busybox,甚至/sbin/init程序也指向busybox,即这些程序都是由busybox实现的。
源码中可以搜索到ls.c, cp.c, init.c等文件,这些文件用于实现ls,cp,/sbin/init等命令。
busybox源码路径:\002_JZ2440资料光盘_20200423(免费)\资料光盘\B盘\uboot+kernel+filesystem的镜像源码及补丁\busybox\busybox-1.7.0_patched.tar.bz2
2.1- init程序分析
busybox源码init_main()是init程序的实现。
主要做3件事:设置信号量程序;读取、解析配置文件/etc/inittab;运行配置文件中应用程序
对于信号量程序不解读,下面重点解读下如何读取配置文件/etc/inittab并执行程序。
int init_main(int argc, char **argv)
{
/*1- 设置信号量程序 */
/* Set up sig handlers -- be sure to
* clear all of these in run() */
signal(SIGHUP, exec_signal);
signal(SIGQUIT, exec_signal);
signal(SIGUSR1, shutdown_signal);
signal(SIGUSR2, shutdown_signal);
signal(SIGINT, ctrlaltdel_signal);
signal(SIGTERM, shutdown_signal);
signal(SIGCONT, cont_handler);
signal(SIGSTOP, stop_handler);
signal(SIGTSTP, stop_handler);
/*2- parse_inittab()读取、解析配置文件/etc/inittab */
/* Check if we are supposed to be in single user mode */
if (argc > 1
&& (!strcmp(argv[1], "single") || !strcmp(argv[1], "-s") || LONE_CHAR(argv[1], '1'))
) {
/* Start a shell on console */
new_init_action(RESPAWN, bb_default_login_shell, "");
} else {
/* Not in single user mode -- see what inittab says */
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
* then parse_inittab() simply adds in some default
* actions(i.e., runs INIT_SCRIPT and then starts a pair
* of "askfirst" shells */
parse_inittab();
}
/* 3- 运行配置文件中应用程序 */
/* Now run everything that needs to be run */
/* First run the sysinit command */
run_actions(SYSINIT);
/* Next run anything that wants to block */
run_actions(WAIT);
/* Next run anything to be run only once */
run_actions(ONCE);
/* Now run the looping stuff for the rest of forever */
while (1) {
/* run the respawn stuff */
run_actions(RESPAWN);
/* run the askfirst stuff */
run_actions(ASKFIRST);
/* Don't consume all CPU time -- sleep a bit */
sleep(1);
/* Wait for a child process to exit */
wpid = wait(NULL);
while (wpid > 0) {
/* Find out who died and clean up their corpse */
for (a = init_action_list; a; a = a->next) {
if (a->pid == wpid) {
/* Set the pid to 0 so that the process gets
* restarted by run_actions() */
a->pid = 0;
message(L_LOG, "process '%s' (pid %d) exited. "
"Scheduling it for restart.",
a->command, wpid);
}
}
/* see if anyone else is waiting to be reaped */
wpid = waitpid(-1, NULL, WNOHANG);
}
}
}
2.2- 配置文件分析
如果配置文件/etc/inittab不存在,则添加默认的应用程序。
如果配置文件存在,则读取配置文件,并添加配置文件中的应用程序。
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
* then parse_inittab() simply adds in some default
* actions(i.e., runs INIT_SCRIPT and then starts a pair
* of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB
* _is_ defined, but /etc/inittab is missing, this
* results in the same set of default behaviors.
*/
static void parse_inittab(void)
{
/*1- 读取配置文件 /etc/inittab */
file = fopen(INITTAB, "r");
/*1.1- 如果没有配置文件,则执行默认程序 */
if (file == NULL) {
/* No inittab file -- set up some default behavior */
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Prepare to restart init when a HUP is received */
new_init_action(RESTART, "init", "");
/* Askfirst shell on tty1-4 */
new_init_action(ASKFIRST, bb_default_login_shell, "");
new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
/* sysinit */
new_init_action(SYSINIT, INIT_SCRIPT, "");
return;
}
/* 2- 如果存在配置文件则解析配置文件 */
while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {
/* Skip leading spaces */
for (id = buf; *id == ' ' || *id == '\t'; id++);
/* Skip the line if it's a comment */
if (*id == '#' || *id == '\n')
continue;
/* Trim the trailing \n */
//XXX: chomp() ?
eol = strrchr(id, '\n');
if (eol != NULL)
*eol = '\0';
/* Keep a copy around for posterity's sake (and error msgs) */
strcpy(lineAsRead, buf);
/* Separate the ID field from the runlevels */
runlev = strchr(id, ':');
if (runlev == NULL || *(runlev + 1) == '\0') {
message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
continue;
} else {
*runlev = '\0';
++runlev;
}
/* Separate the runlevels from the action */
action = strchr(runlev, ':');
if (action == NULL || *(action + 1) == '\0') {
message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
continue;
} else {
*action = '\0';
++action;
}
/* Separate the action from the command */
command = strchr(action, ':');
if (command == NULL || *(command + 1) == '\0') {
message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
continue;
} else {
*command = '\0';
++command;
}
/* Ok, now process it */
for (a = actions; a->name != 0; a++) {
if (strcmp(a->name, action) == 0) {
if (*id != '\0') {
if (strncmp(id, "/dev/", 5) == 0)
id += 5;
strcpy(tmpConsole, "/dev/");
safe_strncpy(tmpConsole + 5, id,
sizeof(tmpConsole) - 5);
id = tmpConsole;
}
new_init_action(a->action, command, id);
break;
}
}
if (a->name == 0) {
/* Choke on an unknown action */
message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
}
}
fclose(file);
}
配置文件格式:
在busybox源码路径:\busybox-1.7.0\examples\inittab,可以找到配置文件的示例。
示例中说,配置文件格式为:<id>:<runlevels>:<action>:<process>
id:表示输出,可以为tty2
runlevels:运行等级,可以忽略
action:程序运行时机
process:程序入口
文档中给出示例程序: tty2::askfirst:/bin/sh
详细说明见文档,摘抄部分如下:
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# <id>: WARNING: This field has a non-traditional meaning for BusyBox init!
#
# The id field is used by BusyBox init to specify the controlling tty for
# the specified process to run on. The contents of this field are
# appended to "/dev/" and used as-is. There is no need for this field to
# be unique, although if it isn't you may have strange results. If this
# field is left blank, it is completely ignored. Also note that if
# BusyBox detects that a serial console is in use, then all entries
# containing non-empty id fields will be ignored. BusyBox init does
# nothing with utmp. We don't need no stinkin' utmp.
#
# <runlevels>: The runlevels field is completely ignored.
#
# <action>: Valid actions include: sysinit, respawn, askfirst, wait, once,
# restart, ctrlaltdel, and shutdown.
#
# Note: askfirst acts just like respawn, but before running the specified
# process it displays the line "Please press Enter to activate this
# console." and then waits for the user to press enter before starting
# the specified process.
#
# Note: unrecognised actions (like initdefault) will cause init to emit
# an error message, and then go along with its business.
#
# <process>: Specifies the process to be executed and it's command line.
如何处理配置文件中的程序?
parse_inittab()接口中会读取文件,new_init_action()将每一行的程序加入到init_action_list链表中。
init_action_list链表格式为:
/* Set up a linked list of init_actions, to be read from inittab */
struct init_action {
struct init_action *next;
int action;
pid_t pid;
char command[INIT_BUFFS_SIZE];
char terminal[CONSOLE_NAME_SIZE];
};
从init_action_list数据可以知道,将配置文件中读取的时机、命令、输出终端参数传入到对应的action,command, terminal中。
2.3- 执行配置文件中程序
上面已经读取配置文件/etc/inittab,并将配置文件的参数添加到链表init_action_list中。
接下来在init_main()中调用run_actions()执行init_action_list中的程序。
就是遍历init_action_list中的元素,根据元素的执行时机不同进行不同操作。
SYSINIT、WAIT、ONCE的程序只执行一次;RESPAWN、ASKFIRST程序则会在死循环中一直执行,即使程序退出,下次再重新启动。
run_actions()接口对于不同时机的函数由不同的处理:
对于时机为SYSINIT ,WAIT, CTRLALTDEL,SHUTDOWN , RESTART的程序,需要等待程序结束后,从init_action_list链表删除
对于时机为ONCE的程序,则创建子线程后就从init_action_list链表删除;
对于RESPAWN 、 ASKFIRST程序则创建子线程后直接返回,不从init_action_list链表删除;
static int waitfor(const struct init_action *a, pid_t pid)
{
int runpid;
int status, wpid;
runpid = (NULL == a)? pid : run(a);
while (1) {
wpid = waitpid(runpid, &status, 0);
if (wpid == runpid)
break;
if (wpid == -1 && errno == ECHILD) {
/* we missed its termination */
break;
}
/* FIXME other errors should maybe trigger an error, but allow
* the program to continue */
}
return wpid;
}
/* Run all commands of a particular type */
static void run_actions(int action)
{
struct init_action *a, *tmp;
for (a = init_action_list; a; a = tmp) {
tmp = a->next;
if (a->action == action) {
/* a->terminal of "" means "init's console" */
if (a->terminal[0] && access(a->terminal, R_OK | W_OK)) {
delete_init_action(a);
} else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) {
waitfor(a, 0);
delete_init_action(a);
} else if (a->action & ONCE) {
run(a);
delete_init_action(a);
} else if (a->action & (RESPAWN | ASKFIRST)) {
/* Only run stuff with pid==0. If they have
* a pid, that means it is still running */
if (a->pid == 0) {
a->pid = run(a);
}
}
}
}
}
2.4- 最小根文件系统组成
从内核接口init_post() 到busybox分析,我们可以得出最小跟文件系统的组成为:
1- 终端设备: /dev/console, /dev/null
2- init程序,这里指busybox程序
3- 配置文件,这里指/etc/inittab文件
4- 配置文件指定的应用程序,例如ls, cp, init等
5- 库文件--busybox会用到printf, 需要由c库等其他库文件
3- busybox编译安装
busybox 源码文件INSTALL里由说明
3.1- make menuconfig配置busybox
选择上TBL自动补全命令,其他基本不用管,直接保存
3.2- 编译。
我们使用交叉编译工具arm-linux编译工具链编译busybox,需要修改makefile文件:
CROSS_COMPILE ?= arm-linux-
执行编译命令
make
3.3- 安装busybox。
我们在虚机上不能直接make install,这样会直接替换到虚机的busybox,破坏虚机!
我们需要安装到指定路径: make CONFIG_PREFIX=xxxt install
The BusyBox build process is similar to the Linux kernel build:
make menuconfig # This creates a file called ".config"
make # This creates the "busybox" executable
make install # or make CONFIG_PREFIX=/path/from/root install
安装busybox后的文件
bin linuxrc sbin usr
3.4- 遇到问题
建议使用韦东山光盘里提供的ubuntu,里面的编译环境都配置好了,自己配置编译环境可能出现一堆奇奇怪怪问题,增加学习成本!
1- 编译busybox失败,报错:
Makefile:405: *** mixed implicit and normal rules: deprecated syntax
Makefile:1242: *** mixed implicit and normal rules: deprecated syntax
make: *** No rule to make target 'menuconfig'。 停止。
解决方案参考:最小根文件系统制作(jffs2(NOR Flash)及yaffs2(NAND Flash))_Leon的博客-CSDN博客
busybox的编译使用及安装_whatday的专栏-CSDN博客_busybox编译
2- 安装busybox到指定路径出错
问题场景即解决方案:我使用vmtool在虚机和windows机器间传输文件,busybox放在了二者的交换文件夹中。可以把busybox放在/home路径。
/mnt/hgfs/xxx/bin/addgroup -> busybox
ln: failed to create symbolic link '/mnt/hgfs/xxx/bin/addgroup': Operation not supported
/mnt/hgfs/xxx/busybox-1.7.0/Makefile.custom:16: recipe for target 'install' failed
make: *** [install] Error 1