当前位置: 首页 > Linux

Linux启动分析——init进程与app启动

时间:2023-04-06 02:08:41 Linux

概述本文简单分析了init进程的源码,梳理了它的处理流程,重点介绍了init进程是如何启动应用的,并总结了启动脚本的编写思路文件。init进程源码分析init进程如何知道linux内核启动的第一个进程?从内核源码linux-2.6.xxx/init/main.c的kernel_init()函数分析可以发现,内核会根据uboot传入的参数启动第一个进程。一般init是怎么启动的呢?调用kernel_execve()函数完成。猜测是从根文件系统的/sbin/init启动的。Linux中的任何应用程序都是基于文件系统的。启动应用程序的前提条件是已经挂载了根文件系统。好的,那么根文件系统是从哪里来的呢?它是由工具busybox编译生成的,所以要分析init源码,去busybox找到init的源码源位置:/busybox/init/init.c,在里面找到main()函数,并发现只有init_main(),没有main()。可以猜测是busybox通过一些方法修改了init进程的入口为init_main()。其实busybox的所有命令工具都是一个busybox程序。cd/sbinls-linitlrwxrwxrwx1root014Nov162016init->../bin/busybox可以看到init进程其实是一个busybox的链接。别担心。你知道init进程的入口是init_main()函数在#ifDEBUG_SEGV_HANDLER{structsigactionsa;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();....../*确保environs设置为合理的*/putenv((char*)"HOME=/");putenv((char*)bb_PATH_root_path);putenv((char*)"SHELL=/bin/sh");putenv((char*)"USER=root");/*需要吗?为什么?*/这一段是init进程首先要做的是设置一些信号相关的东西,初始化控制台,然后设置环境变量。似乎与启动应用程序无关。别担心。继续往下看/*检查我们是否应该处于单用户模式??/if(argv[1]&&(strcmp(argv[1],"single")==0||strcmp(argv[1],"-s")==0||LONE_CHAR(argv[1],'1'))){/*???我们不应该在这里设置RUNLEVEL="b"吗?*//*在控制台上启动一个shell*/new_init_action(RESPAWN,bb_default_login_shell,"");}else{/*不是单用户模式??-看看inittab说的是什么*//*注意如果CONFIG_FEATURE_USE_INITTAB没有定义,*然后parse_inittab()只是加入了一些默认的*动作(即INIT_SCRIPT和一对*"askfirst"shell)*/parse_inittab();}这段代码是一个if判断。注释说如果是单用户模式,就看前半部分代码。如果不是单用户模式,调用parse_inittab()函数,因为内核启动init进程时没有传入额外的参数,所以argv[1]不是Existence,程序通过parse_inittab()注释说如果CONFIG_FEATURE_USE_INITTAB这个宏没有定义,程序会执行一些默认的动作,那么怎么知道这个宏有没有定义呢?猜猜这个宏应该是配置busybox时的一个选项。好吧怎么办?查看busybox配置,和linux内核配置一致。结合各级makemenuconfig和config文件,看看是否定义了宏CONFIG_FEATURE_USE_INITTAB?在busybox中执行makemeunconfig,进入熟悉的配置界面,四处浏览,好像有一个和init相关的InitUtilities项,还有一项“Supportreadinganinittab”file”,这个配置项被选中,在描述中加入了“inittab”二字,和init源码中提到的parse_inittab()非常相似。好了,先把makemenuconfig放一边,我们来看看配置文件,并打开顶层??目录Config.in,全局搜索“init”,只找到最下面:sourceinit/Config.in进入init文件夹,打开Config.in文件,找到配置itemconfigFEATURE_USE_INITTABbool"Supportreadinganinittabfile"defaultydependsINIThelp允许init在系统启动时读取一个inittab文件猜的没错,确实定义了CONFIG_FEATURE_USE_INITTAB宏,返回init源码分析,进入parse_inittab()函数首先看到这个函数之前有一个大注释,看看它说了什么/*注意如果CONFIG_FEATURE_USE_INITTAB没有定义,*然后parse_inittab()简单地添加一些默认*操作(即运行INIT_SCRIPT然后启动一对*“先问”炮弹)。如果CONFIG_FEATURE_USE_INITTAB*_is_已定义,但缺少/etc/inittab,则此*会导致相同的一组默认行为。*/前面的话和前面的if判断意义相同。如果定义了宏XXX,但没有/etc/inittab文件,将执行默认操作。嗯,我猜,parse_inttab()函数好像和要分析的app的启动有关。如果定义了XXX宏,就会解析/etc/inittab这个文件的内容来执行。如果没有定义XXX宏或者/etc/inittab文件不存在,执行一些默认的东西就好了。搞清楚一件事,/etc/inittab这个文件很重要,你可能需要自己创建一个文件,在里面写点东西,但是写什么呢?还不知道。那么如果不走/etc/inittab这个路径,默认会执行的动作是什么意思呢?我们来分析函数parse_inittab()staticvoidparse_inittab(void){#ifENABLE_FEATURE_USE_INITTABchar*token[4];parser_t*parser=config_open2("/etc/inittab",fopen_for_read);if(parser==NULL)#endif{/*没有inittab文件-设置一些默认行为*//*Sysinit*/new_init_action(SYSINIT,INIT_SCRIPT,"");/*Askfirstshellontty1-4*/new_init_action(ASKFIRST,bb_default_login_shell,"");//TODO:VC_1而不是""?“”是控制台->ctty问题->愤怒的用户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_3);按Ctrl-Alt-Del重启*/new_init_action(CTRLALTDEL,"reboot","");/*在停止/重启时卸载所有文件系统*/new_init_action(SHUTDOWN,"umount-a-r","");/*交换在停止/重启时*/new_init_action(SHUTDOWN,"swapoff-a","");/*当收到QUIT时重启init*/new_init_action(RESTART,"init","");返回;}#ifENABLE_FEATURE_USE_INITTAB/*optional_tty:ignored_runlevel:action:command*Delims不会折叠并且需要恰好4个标记*/while(config_read(parser,token,4,0,"#:",PARSE_NORMAL&~(PARSE_TRIM|PARSE_COLLAPSE))){/*命令必须对应于SYSINIT..RESTART常量*/staticconstcharactions[]ALIGN1="sysinit\0""wait\0""once\0""respawn\0""askfirst\0""ctrlaltdel\0""关机\0""重启\0";内部行动;char*tty=token[0];if(!token[3])/*少于4个标记*/gotobad_entry;action=index_in_strings(actions,token[2]);if(action<0||!token[3][0])/*token[3]:命令*/gotobad_entry;/*转动。*TTY->/dev/TTY*/if(tty[0]){tty=concat_path_file("/dev/",skip_dev_pfx(tty));}new_init_action(1<lineno);}config_close(parser);#endif}goreadfirst/etc/inittab文件被创建。如果不存在,则执行大量的new_init_action()。如果存在,则执行while()循环。猜测应该是解析/etc/inittab文件的内容,执行new_init_action()好了,那么inittab文件是什么格式,写的是什么?while()循环中有一个staticconstcharactions[]数组,貌似和inittab的内容有关。里面有"sysinit"之类的字符串,但是还是搞不懂inittab文件怎么写Howtowriteinittabfiles/busybox/examples/下面找一个inittab脚本的例子,打开,看到类似这样的一句话格式说明:每个条目的格式::::猜测inittab文件中可以有多个条目。每个条目格式有四个项目:id、runlevels、action和process。这里也出现了action,和代码中的action数组很像。该文件还说id和运行级别是无关紧要的。那么,要想弄清楚inittab怎么写,关键是要了解action和process。继续阅读说明。Action动作包括:sysinit、respawn、askfirst、wait、once、restart、ctrlaltdel、shutdown,一共八种,process指定要运行的程序及其参数然后说如果没有inittab文件,运行下面::sysinit:/etc/init.d/rcS::askfirst:/bin/sh::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff-a::shutdown:/bin/umount-a-r::restart:/sbin/inittty2::askfirst:/bin/shtty3::askfirst:/bin/shtty4::askfirst:/bin/sh这应该是inittab文件无法启动时执行的一系列net_init_action的内容读入代码。往下看,出现的第一个例子entry::sysinit:/etc/init.d你熟悉/rcS吗?在Linux系统的嵌入式设备中通常有一个文件/etc/init.d/rcS。它是一个shell脚本。按照前面的格式,分析一下。id和runlevel为空,action为sysinit,进程是/etc/init.d/rcS,所以首先要做的就是执行rcS脚本,rcS脚本可以为所欲为。下一个例子是::askfirst:-/bin/sh注释说启动shell到console口,不管,继续看tty4::respawn:/sbin/getty38400tty5tty5::respawn:/sbin/getty38400tty6打开getty::restart:/sbin/init指定init进程Restartposition::ctrlaltdel:/sbin/reboot::shutdown:/bin/umount-a-r::shutdown:/sbin/swapoff-aThingsto重启前做回到代码,这个while()循环遍历inittab文件中的每一项,解析入口的四部分: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()的源码,我们可以看到其实这些动作和流程只是添加到一个链表中,并没有做任何实际操作。处理,真正的处理在后面的代码中,parse_inittab()函数返回,....../*现在运行所有需要运行的东西*//*先运行sysinit命令*/run_actions(SYSINIT);check_delayed_sigs();/*接下来运行任何想要阻塞的东西*/run_actions(WAIT);检查延迟信号();/*接下来运行任何只运行一次的东西*/run_actions(ONCE);/*现在永远运行循环的东西。*/while(1){......这里调用run_action()运行链表中的每一项,首先运行的是sysinit的action。启动应用程序。上面的流程图很简单。init进程首先分析/etc/inittab文件。当然,你可以自己修改busybox源代码,让它从任何文件分析。如果inittab文件不存在,则执行默认动作;如果inittab文件存在,则根据inittab文件中的条目执行,一般是到/etc/init.d/rcS文件中执行脚本命令,当然修改源码,也可以让它执行其他脚本rcS脚本是基于shell脚本语言,一般例程是添加加载驱动模块配置网络,搭建网桥,配置网卡地址启动app