之前讲过虚拟文件系统挂载根文件系统之后,会执行用户程序,参见博文:
http://blog.chinaunix.net/uid-29401328-id-4909649.html
但只提了一下内核会启动init进程,没详细讲,今天就从这里开始讲下文件系统的启动过程
linux-2.6.30.4内核, busybox-1.16.0
根据之前那篇博文我们知道,文见系统挂载最后调用的函数为:init_post,源码如下:
-
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();
-
-
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);
-
-
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);
-
}
-
-
/*
-
* 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.");
-
}
20行:ramdisk_execute_command值通过“rdinit=”指定,如果未指定,往下执行;
这个参数解析是在:
linux-2.6.30.4\init\Main.c
static int __init rdinit_setup(char *str)
{
unsigned int i;
ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);
32行:execute_command通过“init=”指定,最常见的是在uboot的参数中设置“init=/linuxrc”。如果设置了这个参数,则上述函数直接执行到:
run_init_process(execute_command);
后面的讲都不会再执行,因为run_init_process通过调用kernel_execve替换了整个进程。
init参数解析是在:
linux-2.6.30.4\init\Main.c
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);
37行:如果rdinit和init都未设置,则直接调用/sbin/init。如果没找到/sbin/init,则一次回去寻找/etc/init、/bin/init、/bin/sh,如果都没找到,则会打印“No init found. Try passing init= option to kernel”
如果uboot设置了“init=/linuxrc”(我的yaffs2文件系统的uboot启动就设置了),那么将会执行linuxrc,但是后来我发现把linuxrc删除,系统也会正常启动,没任何影响,这是为什么?
经CU论坛“arm-linux-gcc”兄弟指点和网上查阅资料,总结如下:
linuxrc其实会被当作init来使用,在busybox中busybox-1.16.0\include\Applets.h 232行有:
IF_FEATURE_INITRD(APPLET_ODDNAME(linuxrc, init, _BB_DIR_ROOT, _BB_SUID_DROP, linuxrc))
意思就是如果设置了“init=/linuxrc”,其实调用的也是/sbin/init,没有设置也会调用它。
所以我们的第一个程序就是/sbin/init。
后来又做了一个测试:把linuxrc文件改写成脚本文件,文件系统将不能正确启动,会提示“Kernel panic - not syncing: No init found”。
这里应该是因为执行了linuxrc,不再去执行/sbin/init,而linuxrc又被我们改写了,不会再被当成init使用了,所以/sbin/init得不到
执行,挂载失败,No init
下面说说/sbin/init这个进程:
init进程是第一个进程,所有进程的父进程,有了它,后面才能有shell等进程。
通过top查看:
1 0 root S 2096 3.4 0 0.0 init
可以看出它的PID为1
根据之前对busybox的分析可知,它调用的其实就是busybox里的init_main函数
源码太长,这里就不贴了,而且大部分不会涉及到,涉及到文件系统启动的最主要函数就是parse_inittab。
parse_inittab函数主要涉及到的文件就是/etc/inittab,这里先说一下/etc/inittab的格式:
busybox-1.16.0\include\usage.h 1918行说明了它的格式:
:::
这段解释是被定义在一个宏里的,#define init_notes_usage
从这个宏里我们可以获得比较详细的解释,下面说一下(基本上是从这段英文翻译过来的):
:用来指定所启动进程的controlling tty,如果是空,会被默认设置为console
:init会忽略这一项,就是当做你不存在
:所执行的程序的属性说明,这么说有点拗口,看完它的各个值的含义就明白了。
有效的取值有8种,又可以分为两种,一种是只运行一次的程序,另一种是会重复运行的程序:
运行一次的:
sysinit:就是启动运行的第一个程序,直到它所指定的执行完,才进行下一步动作
wait:在sysinit指定的程序执行完,开始执行,等到它执行完,init才能继续
once:只执行一次的进程,而且它和init是异步的,意思就是,init的只负责启动它,不需等它执行完
restart:重启时运行的程序,默认所执行的进程是init本身
ctrlaltdel:同时按下CTRL-ALT-DEL键执行的程序,常用的是reboot
shutdown:系统关机时,执行的程序
运行多次的(在运行一次的程序运行完之后才运行这里的程序):
respawn:如果这里的程序被终止了,init会自动重启它的
askfirst:和respawn功能类似,但会在启动程序之前在控制台打印“Please press Enter to activate this console”。
和respawn不同在于,respawn能够直接启动一个程序,而askfirst要等用户敲了Enter键后才能启动程序
这里把我的开发板的inittab文件贴出来当做示例理解:
# /etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::once:/usr/sbin/telnetd -l /bin/login
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
了解了inittab的格式,下面来看一下parse_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
-
char *token[4];
-
parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
-
-
if (parser == NULL)
-
#endif
-
{
-
/* 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", "");
-
/* Swapoff on halt/reboot */
-
if (ENABLE_SWAPONOFF)
-
new_init_action(SHUTDOWN, "swapoff -a", "");
-
/* Prepare to restart init when a QUIT is received */
-
new_init_action(RESTART, "init", "");
-
/* Askfirst shell on tty1-4 */
-
new_init_action(ASKFIRST, bb_default_login_shell, "");
-
//TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
-
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
-
/* optional_tty:ignored_runlevel:action:command
-
* Delims are not to be collapsed and need exactly 4 tokens
-
*/
-
while (config_read(parser, token, 4, 0, "#:",
-
PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
-
/* order must correspond to SYSINIT..RESTART constants */
-
static const char actions[] ALIGN1 =
-
"sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
-
"ctrlaltdel\0""shutdown\0""restart\0";
-
int action;
-
char *tty = token[0];
-
-
if (!token[3]) /* less than 4 tokens */
-
goto bad_entry;
-
action = index_in_strings(actions, token[2]);
-
if (action < 0 || !token[3][0]) /* token[3]: command */
-
goto bad_entry;
-
/* turn .*TTY -> /dev/TTY */
-
if (tty[0]) {
-
if (strncmp(tty, "/dev/", 5) == 0)
-
tty += 5;
-
tty = concat_path_file("/dev/", tty);
-
}
-
new_init_action(1 << action, token[3], tty);
-
if (tty[0])
-
free(tty);
-
continue;
-
bad_entry:
-
message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
-
parser->lineno);
-
}
-
config_close(parser);
-
#endif
-
}
12行:打开/etc/inittab文件
14行:如果没有/etc/inittab文件或打开失败,则系统会用new_init_action执行一些默认的配置,这个函数下面再说
42行:逐行去读取/etc/inittab文件,读出来之后同样用new_init_action去执行
来看一下new_init_action函数:
-
static void new_init_action(uint8_t action_type, const char *command, const char *cons)
-
{
-
struct init_action *a, **nextp;
-
-
/* Scenario:
-
* old inittab:
-
* ::shutdown:umount -a -r
-
* ::shutdown:swapoff -a
-
* new inittab:
-
* ::shutdown:swapoff -a
-
* ::shutdown:umount -a -r
-
* On reload, we must ensure entries end up in correct order.
-
* To achieve that, if we find a matching entry, we move it
-
* to the end.
-
*/
-
nextp = &init_action_list;
-
while ((a = *nextp) != NULL) {
-
/* Don't enter action if it's already in the list,
-
* This prevents losing running RESPAWNs.
-
*/
-
if (strcmp(a->command, command) == 0
-
&& strcmp(a->terminal, cons) == 0
-
) {
-
/* Remove from list */
-
*nextp = a->next;
-
/* Find the end of the list */
-
while (*nextp != NULL)
-
nextp = &(*nextp)->next;
-
a->next = NULL;
-
break;
-
}
-
nextp = &a->next;
-
}
-
-
if (!a)
-
a = xzalloc(sizeof(*a));
-
/* Append to the end of the list */
-
*nextp = a;
-
a->action_type = action_type;
-
safe_strncpy(a->command, command, sizeof(a->command));
-
safe_strncpy(a->terminal, cons, sizeof(a->terminal));
-
dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
-
a->command, a->action_type, a->terminal);
-
}
new_init_action函数主要做了这些工作:
1、创建init_action结构,并填充。
2、把这个结构放入init_action_list链表
现对parse_inittab函数总结如下:
如果我们没有配置文件inittab存在,会执行默认配置,根据new_init_action执行的默认配置,我们可以反推出默认inittab配置文件的内容:
::CTRLALTDEL:reboot
::SHUTDOWN:umount -a -r
::RESTART:init
::ASKFIRST:-/bin/sh
tty2::ASKFIRST:-/bin/sh
tty3::ASKFIRST:-/bin/sh
tty4::ASKFIRST:-/bin/sh
::SYSINIT:/etc/init.d/rcS
如果配置文件inittab存在,则会逐行读出,然后对每一行调用new_init_action函数
在init_main函数里,执行完parse_inittab(),后续会顺序调用:
run_actions(SYSINIT);
run_actions(WAIT);
run_actions(ONCE);
然后才调用:
run_actions(RESPAWN | ASKFIRST);
无论对于默认的inittab配置文件,还是对于我们自己文件系统中的inittab配置文件,SYSINIT对应的程序一般是/etc/init.d/rcS,下面我们去看看这个文件
阅读(1493) | 评论(0) | 转发(0) |