linux内核启动过程的后期,在kernel_init()函数代表的init线程中,会尝试执行用户空间的init进程:
从上述代码可见,会尝试执行/sbin/、/etc、/bin三个目录中的init。从《busybox源码分析笔记(一)》一文可以知道,在busybox编译构建完成并安装后,会生成对应的目录(注:/etc目录不存在)。在/sbin目录中,则会存在一个init链接:
查看其属性,其本质则是链接到了../bin/busybox:
综上所述,证明linux内核启动后期,运行的第一个用户空间程序是init,在busybox源码中,init程序则由位于/init目录中的init.c编译构建而成,程序入口是:init_main(),小生在该函数中添加一行标识代码:
linux内核运行后期的结果如下:
可见,linux内核后期加载的就是busybox下的init程序。
init_main分析
贴上该函数的完整代码,下文将分段描述:
intinit_main(intargcUNUSED_PARAM,char**argv) { structsigactionsa; INIT_G(); /*SomeuserssendpoweroffsignalstoinitVERYearly. *Tohandlethis,masksignalsearly. */ /*sigemptyset(&G.delayed_sigset);-donebyINIT_G()*/ sigaddset(&G.delayed_sigset,SIGINT);/*Ctrl-Alt-Del*/ sigaddset(&G.delayed_sigset,SIGQUIT);/*re-execanotherinit*/ #ifdefSIGPWR sigaddset(&G.delayed_sigset,SIGPWR);/*halt*/ #endif sigaddset(&G.delayed_sigset,SIGUSR1);/*halt*/ sigaddset(&G.delayed_sigset,SIGTERM);/*reboot*/ sigaddset(&G.delayed_sigset,SIGUSR2);/*poweroff*/ #ifENABLE_FEATURE_USE_INITTAB sigaddset(&G.delayed_sigset,SIGHUP);/*reread/etc/inittab*/ #endif sigaddset(&G.delayed_sigset,SIGCHLD);/*makesigtimedwait()exitonSIGCHLD*/ sigprocmask(SIG_BLOCK,&G.delayed_sigset,NULL); #ifDEBUG_SEGV_HANDLER memset(&sa,0,sizeof(sa)); sa.sa_sigaction=handle_sigsegv; sa.sa_flags=SA_SIGINFO; sigaction_set(SIGSEGV,&sa); sigaction_set(SIGILL,&sa); sigaction_set(SIGFPE,&sa); sigaction_set(SIGBUS,&sa); #endif if(argv[1]&&strcmp(argv[1],"-q")==0){ returnkill(1,SIGHUP); } #if!DEBUG_INIT /*ExpecttobeinvokedasinitwithPID=1orbeinvokedaslinuxrc*/ if(getpid()!=1 &&(!ENABLE_LINUXRC||applet_name[0]!='l')/*notlinuxrc?*/ ){ bb_simple_error_msg_and_die("mustberunasPID1"); } #ifdefRB_DISABLE_CAD /*TurnoffrebootingviaCTL-ALT-DEL-wegeta *SIGINTonCADsowecanshutthingsdowngracefully...*/ reboot(RB_DISABLE_CAD);/*misnomer*/ #endif #endif /*If,say,xmallocwouldeverdie,wedon'twanttooopskernel *byexiting. *NB:wesetdie_func*after*PID1checkandbb_show_usage. *Otherwise,forexample,"initu"("pleaserexecyourself" *commandforsysvinit)willshowhelptext(whichisn'ttoobad), **andsleepforever*(whichisbad!) */ die_func=sleep_much; /*Figureoutwherethedefaultconsoleshouldbe*/ console_init(); set_sane_term(); xchdir("/"); setsid(); /*Makesureenvironsissettosomethingsane*/ putenv((char*)"HOME=/"); putenv((char*)bb_PATH_root_path); putenv((char*)"SHELL=/bin/sh"); putenv((char*)"USER=root");/*needed?why?*/ if(argv[1]) xsetenv("RUNLEVEL",argv[1]); #if!ENABLE_FEATURE_INIT_QUIET /*Helloworld*/ message(L_CONSOLE|L_LOG,"initstarted:%s",bb_banner); #endif /*Checkifwearesupposedtobeinsingleusermode*/ if(argv[1] &&(strcmp(argv[1],"single")==0||strcmp(argv[1],"-s")==0||LONE_CHAR(argv[1],'1')) ){ /*???shouldn'twesetRUNLEVEL="b"here?*/ /*Startashellonconsole*/ new_init_action(RESPAWN,bb_default_login_shell,""); }else{ /*Notinsingleusermode-seewhatinittabsays*/ /*NOTEthatifCONFIG_FEATURE_USE_INITTABisNOTdefined, *thenparse_inittab()simplyaddsinsomedefault *actions(i.e.,INIT_SCRIPTandapair *of"askfirst"shells)*/ parse_inittab(); } #ifENABLE_SELINUX if(getenv("SELINUX_INIT")==NULL){ intenforce=0; putenv((char*)"SELINUX_INIT=YES"); if(selinux_init_load_policy(&enforce)==0){ BB_EXECVP(argv[0],argv); }elseif(enforce>0){ /*SELinuxinenforcingmodebutload_policyfailed*/ message(L_CONSOLE,"can'tloadSELinuxPolicy." "Machineisinenforcingmode.Haltingnow."); returnEXIT_FAILURE; } } #endif #ifENABLE_FEATURE_INIT_MODIFY_CMDLINE /*Makethecommandlinejustsay"init"-that'sall,nothingelse*/ strncpy(argv[0],"init",strlen(argv[0])); /*Wipeargv[1]-argv[N]sotheydon'tclutterthepslisting*/ while(*++argv) nuke_str(*argv); #endif /*SetupSTOPsignalhandlers*/ /*StophandlermustallowonlySIGCONTinsideitself*/ memset(&sa,0,sizeof(sa)); sigfillset(&sa.sa_mask); sigdelset(&sa.sa_mask,SIGCONT); sa.sa_handler=stop_handler; sa.sa_flags=SA_RESTART; sigaction_set(SIGTSTP,&sa);/*pause*/ /*Doesnotworkasintended,atleastin2.6.20. *SIGSTOPissimplyignoredbyinit *(NB:behaviormightdifferunderstrace): */ sigaction_set(SIGSTOP,&sa);/*pause*/ /*Nowruneverythingthatneedstoberun*/ /*Firstrunthesysinitcommand*/ run_actions(SYSINIT); check_delayed_sigs(&G.zero_ts); /*Nextrunanythingthatwantstoblock*/ run_actions(WAIT); check_delayed_sigs(&G.zero_ts); /*Nextrunanythingtoberunonlyonce*/ run_actions(ONCE); /*Nowruntheloopingstufffortherestofforever*/ while(1){ /*(Re)runtherespawn/askfirststuff*/ run_actions(RESPAWN|ASKFIRST); /*Waitforanysignal(typicallyit'sSIGCHLD)*/ check_delayed_sigs(NULL);/*NULLtimespecmakesitwait*/ /*Waitforanychildprocess(es)toexit*/ while(1){ pid_twpid; structinit_action*a; wpid=waitpid(-1,NULL,WNOHANG); if(wpid<= 0) break; a = mark_terminated(wpid); if (a) { message(L_LOG, "process '%s' (pid %u) exited. " "Scheduling for restart.", a->command,(unsigned)wpid); } } /*Don'tconsumeallCPUtime-sleepabit*/ sleep1(); }/*while(1)*/ }
跳过条件宏定义下的编译分支,主要分析其通用的代码部分:
(1)首先使用sigaddset()将信号添加到信号集合,添加的信号有:Ctrl-Alt-Del、SIGQUIT、SIGPWR、SIGUSR1、SIGTERM、SIGUSR2、SIGUSR2。
(2)然后找出系统默认的控制台,并将路径切换到/,接着重新创建一个新的会话:
console_init(); set_sane_term(); xchdir("/"); setsid();
(3)设置默认的环境变量:
putenv((char*)"HOME=/"); putenv((char*)bb_PATH_root_path); putenv((char*)"SHELL=/bin/sh"); putenv((char*)"USER=root");/*needed?why?*/
(4)如果是单用户模式,则会调用new_init_action(RESPAWN, bb_default_login_shell, "");在控制台启动一个shell;否则,则会调用parse_inittab()函数。
parse_inittab()函数定义如下:
staticvoidparse_inittab(void) { #ifENABLE_FEATURE_USE_INITTAB char*token[4]; parser_t*parser=config_open2("/etc/inittab",fopen_for_read); if(parser==NULL) #endif { /*Noinittabfile-setupsomedefaultbehavior*/ /*Sysinit*/ new_init_action(SYSINIT,INIT_SCRIPT,""); /*Askfirstshellontty1-4*/ new_init_action(ASKFIRST,bb_default_login_shell,""); //TODO:VC_1insteadof""?""isconsole->cttyproblems->angryusers 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); /*RebootonCtrl-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",""); return; } #ifENABLE_FEATURE_USE_INITTAB /*optional_ttyaction:command *Delimsarenottobecollapsedandneedexactly4tokens */ while(config_read(parser,token,4,0,"#:", PARSE_NORMAL&~(PARSE_TRIM|PARSE_COLLAPSE))){ /*ordermustcorrespondtoSYSINIT..RESTARTconstants*/ staticconstcharactions[]ALIGN1= "sysinit�""wait�""once�""respawn�""askfirst�" "ctrlaltdel�""shutdown�""restart�"; intaction; char*tty=token[0]; if(!token[3])/*lessthan4tokens*/ gotobad_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 }
如果定义了CONFIG_FEATURE_USE_INITTAB,则会使用/etc/inittab文件中的action;如果CONFIG_FEATURE_USE_INITTAB没有定义,parse_inittab()则会简单添加一些默认actions(例如,运行INIT_SCRIPT,然后启动一个“askfirst”shell)。如果ENABLE_FEATURE_USE_INITTAB已定义,但是/etc/inittab文件缺失也会使用相同的默认行为集合。
(5)设置STOP信号处理程序:
memset(&sa,0,sizeof(sa)); sigfillset(&sa.sa_mask); sigdelset(&sa.sa_mask,SIGCONT); sa.sa_handler=stop_handler; sa.sa_flags=SA_RESTART; sigaction_set(SIGTSTP,&sa);/*pause*/ sigaction_set(SIGSTOP,&sa);/*pause*/
(6)接下来运行想要运行的命令,依次运行:SYSINIT、WAIT、ONCE:
run_actions(SYSINIT); check_delayed_sigs(&G.zero_ts); run_actions(WAIT); check_delayed_sigs(&G.zero_ts); run_actions(ONCE);
(7)在最后,则是一个while(1)的死循环,用于处理我们在命令行下输入的命令:
while(1){ /*重新运行respawn/askfisrt*/ run_actions(RESPAWN|ASKFIRST); /*等待信号*/ check_delayed_sigs(NULL);/*NULLtimespecmakesitwait*/ /*等待所有的子进程退出*/ while(1){ pid_twpid; structinit_action*a; wpid=waitpid(-1,NULL,WNOHANG); if(wpid<= 0) break; a = mark_terminated(wpid); if (a) { message(L_LOG, "process '%s' (pid %u) exited. " "Scheduling for restart.", a->command,(unsigned)wpid); } } /*短暂让出CPU,不要消耗所有的CPU时间*/ sleep1(); }/*while(1)*/
补充
BusyBox的init不支持运行级别。runlevels字段在BusyBox init中将会被完全忽略。
所以如果想要使用运行级别的系统,需使用sysvinit作为启动进程。
一、7个运行级别
(1)运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动。其实就是关机。
(2)运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆。在忘记root密码时一般用这个运行级别,进去修改root密码。
(3)运行级别2:多用户状态(没有NFS),没有网络连接。
(4)运行级别3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式。linux很常见的运行级别
(5)运行级别4:系统未使用,保留。
(6)运行级别5:X11控制台,登陆后进入图形GUI模式。就是图形模式。
(7)运行级别6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动。
二、查看运行级别
1、runlevel命令:打印系统的上一个和当前运行级别:
N:“N”表示自系统启动后运行级别尚未更改。从上图可见,小生的Ubuntu系统的运行级别为5。
审核编辑:刘清
-
busybox
+关注
关注
0文章
21浏览量
11294 -
GUI
+关注
关注
3文章
659浏览量
39658 -
LINUX内核
+关注
关注
1文章
316浏览量
21644 -
NFS
+关注
关注
1文章
53浏览量
26103 -
Ubuntu系统
+关注
关注
0文章
91浏览量
3927
原文标题:busybox源码分析笔记(二) | init程序
文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论