当前位置: 首页 > 科技观察

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

时间:2023-03-12 21:21:42 科技观察

概述本文简单分析了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();....../*确保environsisssettosomethingsane*/putenv((char*)"HOME=/");putenv((char*)bb_PATH_root_path);putenv((char*)"SHELL=/bin/sh");putenv((char*)"USER=root");/*needed?why?*/这一段是init进程首先要做的,设置一些信号相关的东西,初始化console,然后设置Environmentvariables貌似和启动app没有关系,不用管,继续往下看/*Checkifwearesupposedtobeinsingleusermode*/if(argv[1]&&(strcmp(argv[1],"single")==0||strcmp(argv[1],"-s")==0||LONE_CHAR(argv[1],'1'))){/*?*Notinsingleusermode-seewhatinittabsays*//*NOTEthatifCONFIG_FEATURE_USE_INITTABisNOTdefined,*thenparse_inittab()simplyaddsinsomedefault*actions(i.e.,INIT_SCRIPTandapair*of"askfirst"shells)*/parse_inittab();}如果这段代码是一个ifing判断,评论说,然后去前半段代码,如果不是单用户模式,调用parse_inittab()函数,因为内核启动init进程时没有传入额外的参数,所以argv[1]不存在,程序经过parse_inittab()注释说如果没有定义CONFIG_FEATURE_USE_INITTAB宏,程序会执行一些默认动作。你怎么知道这个宏是否被定义了?猜测这个宏应该是busybox配置的一个选项。那么,如何检查busybox配置呢?和linux内核配置一样,结合makemenuconfig和各级config文件,看是否定义了宏CONFIG_FEATURE_USE_INITTAB?在busybox中执行makemeunconfig,进入熟悉的配置界面看看。里面好像有一个initutilities项和init相关,里面有个项“Supportreadinganinittabfile”,选中这个配置项,描述里有“inittab”字样,很像初始化源代码中提到的parse_inittab()。好了,先把makemenuconfig放一边,我们看一下配置文件,打开顶层目录的Config.in,全局搜索“init”,只找到最下面:sourceinit/Config.in进入init文件夹,打开Config.in文件,找到配置项configFEATURE_USE_INITTABbool"Supportreadinganinittabfile"defaultydependsonINIThelpAllowinittoreadanittabfilewhenthesystemboot。猜对了,宏CONFIG_FEATURE_USE_INITTAB确实定义了,回到init源码分析,进入parse_inittab()函数,先看到这个函数前有一大段注释,看它写的是什么"shells").IfCONFIG_FEATURE_USE_INITTAB*_is_defined,但是/etc/inittabismissing,this*resultsinthesamesetofdefaultbehaviors.*/前面的话和前面的if判断意思差不多,如果定义了XXX宏,但是/etc/inittab文件没有,同样会去default的动作,嗯,大致猜一下,parse_inttab()函数好像跟要分析的app启动有关系,如果定义了XXX宏,就会解析/etc/inittab文件,执行里面的内容,如果XXXmacroisnotdefined或者/etc/inittab文件不存在,执行一些默认的东西就好了,弄清楚一件事,/etc/inittab文件很重要,你可能需要自己创建这个文件,写里面有东西,但是要写什么?唐还不知道。那么如果不走/etc/inittab这个路径,默认会执行的动作是什么意思呢?下面分析一下函数parse_inittab()setupsomedefaultbehavior*//*Sysinit*/new_init_action(SYSINIT,INIT_SCRIPT,"");/*Askfirstshellontty1-4*/new_init_action(ASKFIRST,bb_default_login_shell,"");//TODO:VC_1insteadof""?""isconsole->cttyproblems->angryusersnew_init_action(ASKFIRST,bb_default_login_shell,VC_2);new_init_action(ASKFIRST,bb_default_login_shell,VC_3);new_init_log_shell,ASKfa);/*重启Ctrl-Alt-Del*/new_init_action(CTRLALTDEL,"reboot","");/*Umountallfilesystemsonhalt/reboot*/new_init_action(SHUTDOWN,"umount-a-r","");/*Swapoffonhalt/reboot*/new_init_action(SHUTDOWN,"swapoff-a","");/*RestartinitwhenaQUITisreceived*/new_init_action(RESTART,"init","");返回;}#ifENABLE_FEATURE_USE_INITTAB/*optional_tty:ignored_runlevel:action:command*Delimsarenottobecollapsedandneedexactly4tokens*/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";intaction;char*tty=token[0];如果(!token[3])/*lessthan4tokens*/gotobad_entry;action=index_in_strings(actions,token[2]);if(action<0||!token[3][0])/*token[3]:command*/gotobad_entry;/*turn.*TTY->/dev/TTY*/if(tty[0]){tty=concat_path_file("/dev/",skip_dev_pfx(tty));}new_init_action(1<lineno);}config_close(parser);#endif}首先读取文件/etc/inittab,如果不存在,执行大量的new_init_action(),如果存在,去一会儿()循环,猜测应该是解析/etc/inittab文件的内容,根据文件内容执行new_init_action()。那么,那么inittab文件是什么格式,写的是什么?while()循环中有一个staticconstcharactions[]数组,看来跟inittab的内容有关系。里面有"sysinit"之类的字符串,但是我还是想不通inittab文件怎么写。类似于格式描述的一句话:Formatforeachentry::::猜测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_actions的内容代码。往下看,出现的第一个exampleentry::sysinit:/etc/init.d/rcS是我不是很熟悉。在Linux系统的嵌入式设备中通常有一个文件/etc/init.d/rcS。它是一个shell脚本。按照前面的格式,分析一下。id和runlevel都是空的,action是sysinit,process是/etc/init.d/rcS,所以首先要做的就是执行rcS脚本,rcS脚本可以为所欲为。下例是::askfirst:-/bin/shcomment是启动shell到console口,不管,继续看tty4::respawn:/sbin/getty38400tty5tty5::respawn:/sbin/getty38400tty6打开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]转化为astring,即“sysinit”的等价值,然后调用net_init_action(),分析net_init_action()的源码,我们可以看到,其实这些action和processes只是加入了一个链表,而没有进行任何实际处理。真正的处理是在后面的代码中,parse_inittab()函数返回,....../*Nowruneverythingthatneedstoberun*//*Firstrunthesysinitcommand*/run_actions(SYSINIT);check_delayed_sigs();/*Nextrunanythingthatwantstoblock*/run_actions(WAIT);check_delayed_sigs();*/run_actions(ONCE);/*Nowruntheloopingstufffortherestoffforever.*/while(1){...这里调用run_action()运行链表中的每一项,最先运行的是action为sysinit的action.我已经大致弄清楚了init进程是如何启动应用程序的。简单来说,上面的流程图,init进程首先分析/etc/inittab文件。当然,你可以自己修改busybox源码,让它从任意文件开始分析。如果inittab文件不存在,则执行默认动作;如果inittab文件存在,则根据inittab文件中的条目执行,一般是到/etc/init.d/rcS文件中执行脚本命令,当然修改源码,也可以让它执行其他脚本rcS脚本是用shell脚本语言编写的。大体套路就是加载驱动模块配置网络,搭建网桥,配置网卡地址启动app。