分析版本为linux-2.6.22.6
UBOOT:启动内核 。
内核:1. 挂接根文件系统。 2. 启动根文件系统上的应用程序 。
内核启动流程:init_post
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
static int noinline init_post(void)
{
free_initmem();
unlock_kernel();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
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);
}
/*
* 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(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.");
}
1 先打开一个设备。打开 /dev/console 设备:
```c
sys_open((const char __user *) "/dev/console", O_RDWR, 0) //打开的第 0 个文件,指向 dev/console
(void) sys_dup(0); //复制第0个文件,得到第 1 个文件
(void) sys_dup(0); //复制第0个文件,得到第 2 个文件
//(这三个文件代表标准输入,标准输出,标准错误)
2 用 run_init_process 启动第一个应用程序:
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");
一般来说第一个应用程序运行后,就不会从系统返回了。
这个应用程序:
(1)可以是 UBOOT 传进来的命令行参数,如: init=linuxrc
(2)也可以是 sbin/init, 要是 sbin/init 不成功,则还有 /etc/init 或 bin/init, bin/sh
例如bootargs里指定的inint bootargs=noinitrd root=/dev/mtcblock3 init=/linuxrc console=ttySAC0
init进程分析
init程序要做的事:
- 读取配置文件
- 解析配置文件
- 执行(用户程序)
分析busybox init.c 源码
int init_main(int argc, char **argv)
{
struct init_action *a;
pid_t wpid;
die_sleep = 30 * 24*60*60; /* if xmalloc will ever die... */
if (argc > 1 && !strcmp(argv[1], "-q")) {
return kill(1, SIGHUP);
}
#if !ENABLE_DEBUG_INIT
/* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
if (getpid() != 1
&& (!ENABLE_FEATURE_INITRD || !strstr(applet_name, "linuxrc"))
) {
bb_show_usage();
}
/* Set up sig handlers -- be sure to
* clear all of these in run() */
signal(SIGHUP, exec_signal); //这些是信号处理函数。里面有 ctrl+alt+del 信号
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);
/* Turn off rebooting via CTL-ALT-DEL -- we get a
* SIGINT on CAD so we can shut things down gracefully... */
init_reboot(RB_DISABLE_CAD);
#endif
/* Figure out where the default console should be */
console_init(); //控制台初始化
set_sane_term();
chdir("/");
setsid();
{
const char *const *e;
/* Make sure environs is set to something sane */
for (e = environment; *e; e++)
putenv((char *) *e);
}
if (argc > 1) setenv("RUNLEVEL", argv[1], 1);
/* Hello world */
message(MAYBE_CONSOLE | L_LOG, "init started: %s", bb_banner);
/* Make sure there is enough memory to do something useful. */
if (ENABLE_SWAPONOFF) {
struct sysinfo info;
if (!sysinfo(&info) &&
(info.mem_unit ? : 1) * (long long)info.totalram < 1024*1024)
{
message(L_CONSOLE, "Low memory, forcing swapon");
/* swapon -a requires /proc typically */
new_init_action(SYSINIT, "mount -t proc proc /proc", "");
/* Try to turn on swap */
new_init_action(SYSINIT, "swapon -a", "");
run_actions(SYSINIT); /* wait and removing */
}
}
/* Check if we are supposed to be in single user mode */
if (argc > 1 //linux 内核启动时,执行了run_init_process("/sbin/init");没有给参数。所以argc = 1,就执行else分支,即执行parse_inittab()
&& (!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();
}
#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 */
/* At this point, we probably can't open /dev/console, so log() won't work */
message(L_CONSOLE, "Cannot load SELinux Policy. "
"Machine is in enforcing mode. Halting now.");
exit(1);
}
}
#endif /* CONFIG_SELINUX */
/* Make the command line just say "init" -- thats all, nothing else */
fixup_argv(argv);
/* 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);
#if ENABLE_FEATURE_USE_INITTAB
/* Redefine SIGHUP to reread /etc/inittab */
signal(SIGHUP, reload_signal);
#else
signal(SIGHUP, SIG_IGN);
#endif /* FEATURE_USE_INITTAB */
/* 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);
}
}
}
init_main一开始时,会设置“信号量”:
/* 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);//按下 ctrlaltdel 时,内核会给 init 进程发一个信号 SIGINT。init 收到 SIGINT 信号时,就会执行“ctrlaltdel_signal”信号处理函数
signal(SIGTERM, shutdown_signal);
signal(SIGCONT, cont_handler);
signal(SIGSTOP, stop_handler);
signal(SIGTSTP, stop_handler);
static void ctrlaltdel_signal(int sig ATTRIBUTE_UNUSED)
{
run_actions(CTRLALTDEL); //表明会去执行 CTRLALTDEL 这一类的应用程序。
}
parse_inittab()
解析 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)
{
#if ENABLE_FEATURE_USE_INITTAB
FILE *file;
char buf[INIT_BUFFS_SIZE], lineAsRead[INIT_BUFFS_SIZE];
char tmpConsole[CONSOLE_NAME_SIZE];
char *id, *runlev, *action, *command, *eol;
const struct init_action_type *a = actions;
file = fopen(INITTAB, "r");
if (file == NULL) { //如果没有配置文件,那么这里会有一系列的默认配置项
/* No inittab file -- set up some default behavior */
#endif
/* Reboot on Ctrl-Alt-Del */
new_init_action(CTRLALTDEL, "reboot", "");
/* Umount all filesystems on halt/reboot */
new_init_action(SHUTDOWN, "umount -a -r", "");
/* Swapoff on halt/reboot */
if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
/* 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;
#if ENABLE_FEATURE_USE_INITTAB
}
while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {//如果 INITTAB 可以打开,则放到一个缓冲区去解析
/* 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 加一个 /dev 前缀
id += 5;
strcpy(tmpConsole, "/dev/");
safe_strncpy(tmpConsole + 5, id,
sizeof(tmpConsole) - 5);
id = tmpConsole;
}
new_init_action(a->action, command, id);//解析完后,最终执行new_init_action
break;
}
}
if (a->name == 0) {
/* Choke on an unknown action */
message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", lineAsRead);
}
}
fclose(file);
#endif /* FEATURE_USE_INITTAB */
}
file = fopen(INITTAB, "r");
#define INITTAB "/etc/inittab" /* inittab file location */
/etc/inittab 就是配置文件。
主要过程为:parse_ininttab 解析inittab, 填充数据到 init_action 结构体中,结构体保存id, 执行时机,对应的应用程序,将init_action结构体放到链表init_action_list中。
inittab 格式的说明。
inittab格式: :::
**id 项**:会加一个 /dev 前缀成为/dev/id, 用作终端(终端:stdin-printf,stdout-scanf,stderr-err)。id 可以省略。
**runlevels 项**:完全可以忽略掉。
**action 项**:指示程序何时执行。
**process 项**:是应用程序或脚本。
# : Valid actions include: sysinit, respawn, askfirst, wait, once,
# restart, ctrlaltdel, and shutdown.
new_init_action 这个函数
①.创建结构体 init_action,填充:执行时机,对应执行程序,终端
②.将结构体放入链表 init_action_list
函数原型:
static void new_init_action(int action, const char *command, const char *cons)
参一 action :应用程序执行的时机。
参二 *command :应用程序或脚本----“"-/bin/sh"”
参三 *cons :这是 id 应用终端---“"/dev/tty2"”
static void new_init_action(int action, const char *command, const char *cons)
{
struct init_action *new_action, *a, *last;
if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST))
return;
/* Append to the end of the list */
for (a = last = init_action_list; a; a = a->next) {
/* don't enter action if it's already in the list,
* but do overwrite existing actions */ //若原来链表中已经有个结构和传进来的 action,*command,*cons 一样就覆盖它。
if ((strcmp(a->command, command) == 0)
&& (strcmp(a->terminal, cons) == 0)
) {
a->action = action;
return;
}
last = a;
}
//若没有,则分配一块内存,相当于创建一个 init_action 结构。将传进来的action,command,cons 写进链表
new_action = xzalloc(sizeof(struct init_action));//分配一块内存
if (last) {
last->next = new_action;
} else {
init_action_list = new_action;
}
strcpy(new_action->command, command); //这里分别对应传进来的command, action和cons
new_action->action = action;
strcpy(new_action->terminal, cons);
messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
new_action->command, new_action->action, new_action->terminal);
}
里面有一个链表: init_action_list 和结构体init_action:
struct init_action {
struct init_action *next;
int action; //执行时机
pid_t pid; //进程号
char command[INIT_BUFFS_SIZE]; //对应执行程序 (-/bin/sh)
char terminal[CONSOLE_NAME_SIZE]; //终端(/dev/tty2)
};
run_actions(SYSINIT)首先执行的是 SYSINIT 这一类。(接着执行“run_actions(WAIT)”--"run_actions(ONCE)")。
分析run_actions
/* 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); // 将这个init_action结构从init_action_list链表中删除掉。所以用SYSINIT定义的应用程序,执行完一次就扔掉了。后面的几类与SYSINIT是一样的,只是比在init_main中SYSINIT后执行。
} else if (a->action & ONCE) { //对于“ONCE”这类程序,init进程不会等待它执行完毕(无waitfor函数下调用的waitpid函数)。
run(a); // 创建process子进程,就是inittab结构中的 process中指定的应用程序(如-/bin/sh)
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);
}
/*
对于“RESPAWN” “ASKFIRST”这类程序, 开始a->pid是等于0的,若run(a)它, 则给你赋个PID, 即a->pid = run(a)。init_mainwpid = wait(NULL); 是等待子进程退出。只要任意一个子进程退出时,这个子进程的 pid 就又设置为 0。然后回到 while(1)开始处重新运行,但并不是重新运行所有的子进程,而是运行那个已经退出来的子进程,因为只有a->pid == 0的程序才执行。*/
}
}
}
}