根文件系统的构建与分析(一)之流程分析
根文件系统的构建与分析(一)
转载请注明 http://blog.csdn.net/jianchi88 Author:Lotte 邮箱:baihaowen08@126.com
此系列,我想从初学者的角度来体验怎样构建根文件系统,显然,我也是个初学者。
根文件系统第一个进程init到启动用户应用程序的过程:
qt应用程序也做了,u-boot、内核也移植了,那启动内核后,怎样才能运行qt应用程序呢?内核启动后,操作系统做了些什么?
U-Boot 目的 ――→ 启动内核
|
↓
内核 目的 ――→ 启动第一个进程init(放在根文件系统上)
|
↓
根文件系统
|
↓
用户应用程序
在内核初始化收尾阶段(/kernel-2.6.30.4/init/main.c)流程,start_kernel → rest_init → kernel_init → init_post。
下面的代码是内核启动最后阶段执行的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.");
- }
在启动第一个程序前,首先打开控制台设备sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0),这样init进程就拥有一个控制台,并可以从中读取输入信息在串口终端显示,也可以向其中写入信息。
在bootloader里设置传给内核的参数init=/linuxrc,execute_command就等于/linuxrc,如果定义了,就会run_init_process(execute_command);执行这个应用程序,如果没有定义这个参数,就会往下走。执行
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
中的其中一个。另外要说明的是,run_init_process();一旦执行了init或者linuxrc,就不会返回,一直执行。init这个进程是所有进程的父进程,有了它,后面才能有shell、qt等进程。
Init进程的功能:
• 它是所有其他进程的父进程。
• init进程根据文件的内容运行一系列程序和脚本文件,完成系统的各项配置,最终达到启动用户应用程序的目的。
下面我们来追随init的运行。
================================== i am a line ========================================
当我们进入根文件系统,发现有很多命令,如ls、cp、rm等,这些都是应用程序,但这些都是通过链接到busybox实现的功能。如:
/bin/ls -> busybox
/bin/cp -> busybox
/bin/rm -> busybox
就连sbin/init进程也是链接到busybox的,所以busybox是很多命令程序和工具的集合。所以要分析init怎么运行,就要分析busybox。
解压busybox源码,在/init/init.c。(这个就是编译busybox后就是被init链接的代码)
函数调用流程:
busybox -> init_main() -> parse_inittab() -> run_actions()
解析inittab文件函数parse_inittab();
在parse_inittab()函数里,首先打开/etc/inittab这个文件,接着解析inittab。inittab配置文件的格式说明和例子在busybox的源码里面有,搜索一下就能找到。
----------------------------------------------------------------------------------------------------------------------
inittab配置文件的格式:
id:runlevel:action:process
• 其中:
–Id:用来指定所启动进程的控制台,在嵌入式系统中一般不添;
–Runlevel:busybox完全忽略runlevel字段;
–Action:指出init程序在执行相应process时,对process所采取的动作
<action>: Valid actions include: sysinit, respawn, askfirst, wait, once, restart, ctrlaltdel, and shutdown.
–Process:具体的执行程序或者脚本;
-----------------------------------------------------------------------------------------------------------------------
回到源码,在parse_inittab()函数里,为inittab文件作一些添油加醋的修饰后,最终调用new_init_action(),下面进行分析。
- 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""respawn\0""askfirst\0""wait\0""once\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
- }
可以看见,如果没配置文件inittab存在,它会默认一些配置。其中抽取一个来分析,如下:
- static void new_init_action(uint8_t action_type, const char *command, const char *cons)
- new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
bb_default_login_shell -> "-/bin/sh"
VC_2 -> "/dev/tty2"
action_type对应inittab配置文件中Action字段
command对应inittab配置文件中Process字段
cons对应inittab配置文件中id字段
new_init_action函数做了这些工作:
1、创建init_action结构,并填充。
2、把这个结构放入init_action_list链表
假如我们没有配置文件inittab存在,它会默认一些配置,我们从默认的new_init_action反推出默认的配置文件inittab。
- 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;
- }
根据这段代码写成inittab配置文件:
- #id:runlevel:action:process
- ::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
我们把这些包含执行时机、应用程序等结构体填充到init_action_list链表,这就完成了解析inittab文件函数parse_inittab(),在init_main()函数继续走下去,执行run_actions()。
- /* 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);
- /* Redefine SIGHUP to reread /etc/inittab */
- #if ENABLE_FEATURE_USE_INITTAB
- signal(SIGHUP, reload_signal);
- #else
- signal(SIGHUP, SIG_IGN);
- #endif
- /* Now run the looping stuff for the rest of forever */
- while (1) {
- /* run the respawn/askfirst stuff */
- run_actions(RESPAWN | ASKFIRST);
- /* Don't consume all CPU time -- sleep a bit */
- sleep(1);
- /* Wait for any 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 for restart.",
- a->command, wpid);
- }
- }
- /* see if anyone else is waiting to be reaped */
- wpid = wait_any_nohang(NULL);
- }
- }
可见,最先执行是的Action字段里的sysinit这类,接着是wait,接着是once,最后是respawn和askfirst。在run_actions函数里,它提取init_action_list链表的项来用waitfor()执行应用程序或者脚本,等执行完毕,再用delete_init_action()删除链表中的该项。
小结,根文件系统要顺利执行用户应用程序需要:
1、/dev/console /dev/null 当不设置inittab里id字段的话,标准输入、输出、错误会自动定位到/dev/null
2、/etc/inittab
3、配置文件里指定的程序或者脚本
4、/lib下的动态库
5、init本身,即busybox