本文经过简要分析init进程源码,梳理其处理流程,重点关注init进程如何启动应用程序,总结启动脚本文件的编写思路linux
init进程是linux内核启动的第一个进程,怎么知道的?从内核源码linux-2.6.xxx/init/main.c代码的kernel_init()
函数分析,能够发现,内核会根据uboot传入的参数来启动第一个进程,通常都是initshell
怎么启动的呢,调用kernel_execve()
函数完成的,猜想是从根文件系统的/sbin/init来启动的,linux的任何应用程序都是基于文件系统的,启动应用程序前提是根文件系统已经挂载好了。好,那么根文件系统又是从哪里来的呢,是由busybox这个工具配置编译生成的,因此要分析init源码,要去busybox里找init的源码数组
源码位置:/busybox/init/init.c,在其中查找main()
函数,发现只有init_main()
,没有main()
,能够猜想busybox是经过一些方法将init进程的入口修改成init_main()
,实际上全部busybox的命令工具都是一个到busybox程序的连接,网络
cd /sbin ls -l init lrwxrwxrwx 1 root 0 14 Nov 16 2016 init -> ../bin/busybox
能够看到,init进程实际上是到busybox的连接,不用管它,知道init进程的入口是init_main()函数就好了app
#if DEBUG_SEGV_HANDLER { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handle_sigsegv; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); ...... } #endif ...... console_init(); set_sane_term(); ...... /* Make sure environs is set to something sane */ putenv((char *) "HOME=/"); putenv((char *) bb_PATH_root_path); putenv((char *) "SHELL=/bin/sh"); putenv((char *) "USER=root"); /* needed? why? */
这一段是init进程最开始要作的事情,设置一些信号相关的东西,初始化console,而后设置环境变量,跟启动app彷佛没有什么关系,不用管,继续往下看less
/* Check if we are supposed to be in single user mode */ if (argv[1] && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1')) ) { /* ??? shouldn't we set RUNLEVEL="b" here? */ /* 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., INIT_SCRIPT and a pair * of "askfirst" shells) */ parse_inittab(); }
这一段代码是一个if判断,注释说若是是single user mode,则走上半段代码,若是不是single user mode,则调用parse_inittab()
函数,由于内核启动init进程没有传入附加参数,因此argv[1]不存在,程序走parse_inittab()
函数
注释还说若是没有定义CONFIG_FEATURE_USE_INITTAB
这个宏,程序会执行一些默认的action,那怎么知道这个宏定义了没有呢,猜想这个宏应该是对busybox配置时的选项,好,怎么查看busybox配置呢,和linux内核配置同样的道理,结合make menuconfig和各级config文件来看工具
在busybox中执行make meunconfig,进入熟悉的配置界面
大概浏览一下,和init有关系的好像有个Init Utilities项,进去
这里面有一项“Support reading an inittab file”,这个配置项是选中的,描述中有“inittab”这个单词,和init源码中说到的parse_inittab()很类似,好,make menuconfig先放到一边,来看看配置文件,打开顶层目录的Config.in,全局搜一下"init",发现只有最下面有:oop
source init/Config.in
进入init文件夹,打开其中的Config.in文件,发现配置项源码分析
config FEATURE_USE_INITTAB bool "Support reading an inittab file" default y depends on INIT help Allow init to read an inittab file when the system boot.
猜想没错,CONFIG_FEATURE_USE_INITTAB
这个宏确实定义了,回到init源码分析,进入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. */
前面的话和以前的if判断意思差很少,若是定义了XXX这个宏,可是/etc/inittab这个文件没有,也会走默认的action,好,大概猜测一下,parse_inttab()
函数好像和要分析的app启动有点关系了,若是定义了XXX宏,就去解析/etc/inittab这个文件,执行里面的东西,若是没有定义XXX宏或者/etc/inittab文件不存在,执行一些默认的东西
好,搞清楚一件事,/etc/inittab这个文件很重要,可能须要本身来建立这个文件,往里面写东西,可是写什么内容呢?目前还不知道。那若是不走/etc/inittab这一条路呢,默认会执行的action又是什么意思?来分析一下parse_inittab()这个函数
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 */ /* Sysinit */ new_init_action(SYSINIT, INIT_SCRIPT, ""); /* 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); /* 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 */ new_init_action(SHUTDOWN, "swapoff -a", ""); /* Restart init when a QUIT is received */ new_init_action(RESTART, "init", ""); 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]) { tty = concat_path_file("/dev/", skip_dev_pfx(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 }
首先去读了/etc/inittab
这个文件,若是不存在,执行了不少new_init_action()
,若是存在,就走了一个while()
循环,猜想应该是解析/etc/inittab
文件的内容,根据文件的内容执行new_init_action()
。好,那么inittab文件到底写什么格式,什么东西呢,while()循环里面有一个static const char actions[]
数组看起来像是和inittab的内容有关系,里面有“sysinit”等字符串,可是仍是没办法搞清楚怎么写inittab文件
/busybox/examples/
下面找到一个inittab
脚本的例子,打开,看到一个相似格式说明的句子:
Format for each entry: <id>:<runlevels>:<action>:<process>
猜想inittab文件里能够有多条entry,每条entry格式中有id、runlevels、action和process这四项内容,这里也出现了action,和代码里的action数组很像。文件里又说id和runlevels可有可无,好,要搞清楚inittab怎么写,关键在于理解action和process,继续看说明
action包括:sysinit、respawn、askfirst、wait、once、restart、ctrlaltdel、和shutdown共八种,
指定要运行的程序和它的参数
而后还说了若是没有inittab文件,则运行如下内容
::sysinit:/etc/init.d/rcS ::askfirst:/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r ::restart:/sbin/init tty2::askfirst:/bin/sh tty3::askfirst:/bin/sh tty4::askfirst:/bin/sh
这应该就是代码中若是读不到inittab文件,则执行的一系列net_init_action
的内容
再往下看,出现的第一条示例entry
::sysinit:/etc/init.d/rcS
是否是很熟悉,linux系统嵌入式设备里一般会有/etc/init.d/rcS
这个文件,它是一个shell脚本,根据前面的格式,分析一下,id和runlevel为空,action为sysinit
,process为/etc/init.d/rcS
,因此第一件要干的事情是去执行rcS脚本,而rcS脚本里能够作本身想作的任何事情了
下一条示例是
::askfirst:-/bin/sh
注释说的是启动shell到console口,无论,继续看
tty4::respawn:/sbin/getty 38400 tty5 tty5::respawn:/sbin/getty 38400 tty6
开启getty
::restart:/sbin/init
指定init进程的重启位置
::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r ::shutdown:/sbin/swapoff -a
在重启以前要作的事情
再回到代码上,这个while()循环遍历了inittab文件的每个entry,解析出entry的四个部分:id、runlevel、action和process,放到一个指针数组char *token[4]
中,则token[2]
和token[3]
表明action和process,程序里调用index_in_strings()
函数将token[2]
转成字符串,即“sysinit”等值,再调用net_init_action()
,分析net_init_action()
源码能够看出,其实只是把这些action和process添加到一个链表中,并无作实际的处理,真正的处理在后续的代码中,parse_inittab()函数返回,
...... /* Now run everything that needs to be run */ /* First run the sysinit command */ run_actions(SYSINIT); check_delayed_sigs(); /* Next run anything that wants to block */ run_actions(WAIT); check_delayed_sigs(); /* Next run anything to be run only once */ run_actions(ONCE); /* Now run the looping stuff for the rest of forever. */ while (1) { ......
这里调用run_action()
运行链表中每个entry,而且首先运行的是action为sysinit的动做
到这里,大体搞清楚了init进程是怎么启动app的了,上流程图
简单来讲,init进程首先分析/etc/inittab文件,固然,能够本身修改busybox源码,让它从任意文件开始分析,若是不存在inittab文件,则执行默认的action;若是inittab文件存在,则根据inittab文件中的条目执行,一般是去/etc/init.d/rcS文件中执行脚本命令,固然,修改源码,你也可让它执行别的脚本
rcS脚本是以shell脚本语言编写,通常的套路是