Chinaunix首页 | 论坛 | 博客
  • 博客访问: 31757
  • 博文数量: 13
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2014-08-04 21:15
文章分类
文章存档

2015年(11)

2014年(2)

我的朋友

分类: LINUX

2015-06-01 14:48:29

之前讲过虚拟文件系统挂载根文件系统之后,会执行用户程序,参见博文:
http://blog.chinaunix.net/uid-29401328-id-4909649.html
但只提了一下内核会启动init进程,没详细讲,今天就从这里开始讲下文件系统的启动过程

linux-2.6.30.4内核, busybox-1.16.0

根据之前那篇博文我们知道,文见系统挂载最后调用的函数为:init_post,源码如下:

点击(此处)折叠或打开

  1. static noinline int init_post(void)
  2.     __releases(kernel_lock)
  3. {
  4.     /* need to finish all async __init code before freeing the memory */
  5.     async_synchronize_full();
  6.     free_initmem();
  7.     unlock_kernel();
  8.     mark_rodata_ro();
  9.     system_state = SYSTEM_RUNNING;
  10.     numa_default_policy();

  11.     if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
  12.         printk(KERN_WARNING "Warning: unable to open an initial console.\n");

  13.     (void) sys_dup(0);
  14.     (void) sys_dup(0);

  15.     current->signal->flags |= SIGNAL_UNKILLABLE;

  16.     if (ramdisk_execute_command) {
  17.         run_init_process(ramdisk_execute_command);
  18.         printk(KERN_WARNING "Failed to execute %s\n",
  19.                 ramdisk_execute_command);
  20.     }

  21.     /*
  22.      * We try each of these until one succeeds.
  23.      *
  24.      * The Bourne shell can be used instead of init if we are
  25.      * trying to recover a really broken machine.
  26.      */
  27.     if (execute_command) {
  28.         run_init_process(execute_command);
  29.         printk(KERN_WARNING "Failed to execute %s. Attempting "
  30.                     "defaults...\n", execute_command);
  31.     }
  32.     run_init_process("/sbin/init");
  33.     run_init_process("/etc/init");
  34.     run_init_process("/bin/init");
  35.     run_init_process("/bin/sh");

  36.     panic("No init found. Try passing init= option to kernel.");
  37. }
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的源码:

点击(此处)折叠或打开

  1. /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined,
  2.  * then parse_inittab() simply adds in some default
  3.  * actions(i.e., runs INIT_SCRIPT and then starts a pair
  4.  * of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB
  5.  * _is_ defined, but /etc/inittab is missing, this
  6.  * results in the same set of default behaviors.
  7.  */
  8. static void parse_inittab(void)
  9. {
  10. #if ENABLE_FEATURE_USE_INITTAB
  11.     char *token[4];
  12.     parser_t *parser = config_open2("/etc/inittab", fopen_for_read);

  13.     if (parser == NULL)
  14. #endif
  15.     {
  16.         /* No inittab file - set up some default behavior */
  17.         /* Reboot on Ctrl-Alt-Del */
  18.         new_init_action(CTRLALTDEL, "reboot", "");
  19.         /* Umount all filesystems on halt/reboot */
  20.         new_init_action(SHUTDOWN, "umount -a -r", "");
  21.         /* Swapoff on halt/reboot */
  22.         if (ENABLE_SWAPONOFF)
  23.             new_init_action(SHUTDOWN, "swapoff -a", "");
  24.         /* Prepare to restart init when a QUIT is received */
  25.         new_init_action(RESTART, "init", "");
  26.         /* Askfirst shell on tty1-4 */
  27.         new_init_action(ASKFIRST, bb_default_login_shell, "");
  28. //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
  29.         new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
  30.         new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
  31.         new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
  32.         /* sysinit */
  33.         new_init_action(SYSINIT, INIT_SCRIPT, "");
  34.         return;
  35.     }

  36. #if ENABLE_FEATURE_USE_INITTAB
  37.     /* optional_tty:ignored_runlevel:action:command
  38.      * Delims are not to be collapsed and need exactly 4 tokens
  39.      */
  40.     while (config_read(parser, token, 4, 0, "#:",
  41.                 PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {
  42.         /* order must correspond to SYSINIT..RESTART constants */
  43.         static const char actions[] ALIGN1 =
  44.             "sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
  45.             "ctrlaltdel\0""shutdown\0""restart\0";
  46.         int action;
  47.         char *tty = token[0];

  48.         if (!token[3]) /* less than 4 tokens */
  49.             goto bad_entry;
  50.         action = index_in_strings(actions, token[2]);
  51.         if (action < 0 || !token[3][0]) /* token[3]: command */
  52.             goto bad_entry;
  53.         /* turn .*TTY -> /dev/TTY */
  54.         if (tty[0]) {
  55.             if (strncmp(tty, "/dev/", 5) == 0)
  56.                 tty += 5;
  57.             tty = concat_path_file("/dev/", tty);
  58.         }
  59.         new_init_action(1 << action, token[3], tty);
  60.         if (tty[0])
  61.             free(tty);
  62.         continue;
  63.  bad_entry:
  64.         message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",
  65.                 parser->lineno);
  66.     }
  67.     config_close(parser);
  68. #endif
  69. }

12行:打开/etc/inittab文件

14行:如果没有/etc/inittab文件或打开失败,则系统会用new_init_action执行一些默认的配置,这个函数下面再说

42行:逐行去读取/etc/inittab文件,读出来之后同样用new_init_action执行

来看一下new_init_action函数

点击(此处)折叠或打开

  1. static void new_init_action(uint8_t action_type, const char *command, const char *cons)
  2. {
  3.     struct init_action *a, **nextp;

  4.     /* Scenario:
  5.      * old inittab:
  6.      * ::shutdown:umount -a -r
  7.      * ::shutdown:swapoff -a
  8.      * new inittab:
  9.      * ::shutdown:swapoff -a
  10.      * ::shutdown:umount -a -r
  11.      * On reload, we must ensure entries end up in correct order.
  12.      * To achieve that, if we find a matching entry, we move it
  13.      * to the end.
  14.      */
  15.     nextp = &init_action_list;
  16.     while ((a = *nextp) != NULL) {
  17.         /* Don't enter action if it's already in the list,
  18.          * This prevents losing running RESPAWNs.
  19.          */
  20.         if (strcmp(a->command, command) == 0
  21.          && strcmp(a->terminal, cons) == 0
  22.         ) {
  23.             /* Remove from list */
  24.             *nextp = a->next;
  25.             /* Find the end of the list */
  26.             while (*nextp != NULL)
  27.                 nextp = &(*nextp)->next;
  28.             a->next = NULL;
  29.             break;
  30.         }
  31.         nextp = &a->next;
  32.     }

  33.     if (!a)
  34.         a = xzalloc(sizeof(*a));
  35.     /* Append to the end of the list */
  36.     *nextp = a;
  37.     a->action_type = action_type;
  38.     safe_strncpy(a->command, command, sizeof(a->command));
  39.     safe_strncpy(a->terminal, cons, sizeof(a->terminal));
  40.     dbg_message(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
  41.         a->command, a->action_type, a->terminal);
  42. }
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,下面我们去看看这个文件



阅读(1423) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~