0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

linux内核启动过程会执行用户空间的init进程

嵌入式小生 来源:嵌入式小生 作者:iriczhao 2022-10-14 09:12 次阅读

linux内核启动过程的后期,在kernel_init()函数代表的init线程中,会尝试执行用户空间的init进程:

a9272532-4b57-11ed-a3b6-dac502259ad0.png

从上述代码可见,会尝试执行/sbin/、/etc、/bin三个目录中的init。从《busybox源码分析笔记(一)》一文可以知道,在busybox编译构建完成并安装后,会生成对应的目录(注:/etc目录不存在)。在/sbin目录中,则会存在一个init链接:

a955e37c-4b57-11ed-a3b6-dac502259ad0.png

查看其属性,其本质则是链接到了../bin/busybox:

a975872c-4b57-11ed-a3b6-dac502259ad0.png

综上所述,证明linux内核启动后期,运行的第一个用户空间程序是init,在busybox源码中,init程序则由位于/init目录中的init.c编译构建而成,程序入口是:init_main(),小生在该函数中添加一行标识代码:

a9913efe-4b57-11ed-a3b6-dac502259ad0.png

linux内核运行后期的结果如下:

a9b277a4-4b57-11ed-a3b6-dac502259ad0.gif

可见,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命令:打印系统的上一个和当前运行级别:

aa1518dc-4b57-11ed-a3b6-dac502259ad0.png

N:“N”表示自系统启动后运行级别尚未更改。从上图可见,小生的Ubuntu系统的运行级别为5。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • busybox
    +关注

    关注

    0

    文章

    21

    浏览量

    11294
  • GUI
    GUI
    +关注

    关注

    3

    文章

    659

    浏览量

    39658
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21644
  • NFS
    NFS
    +关注

    关注

    1

    文章

    53

    浏览量

    26103
  • Ubuntu系统
    +关注

    关注

    0

    文章

    91

    浏览量

    3927

原文标题:busybox源码分析笔记(二) | init程序

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内核启动过程和Bootloader(总述)

    函数,它主要完成剩余的与硬件平台相关的初始化工作,在进行一系列与内核相关的初始化后,调用第一个用户进程init 进程并等待
    发表于 08-18 17:35

    android--系统启动--init进程启动过程如何

    android--系统启动--init进程启动过程
    发表于 05-29 10:35

    【HarmonyOS HiSpark IPC DIY Camera试用连载 】鸿蒙OS内核如何启动第一个用户进程init_lite

    ,当前包括:Hi3516DV300平台和Hi3518EV300平台。 负责处理从内核加载第一个用户进程开始,到第一个应用程序启动之间的系统服务进程
    发表于 11-20 10:27

    [文章] 【HarmonyOS HiSpark IPC DIY Camera试用连载 】鸿蒙OS内核如何启动第一个用户进程init_lite

    :Hi3516DV300平台和Hi3518EV300平台。负责处理从内核加载第一个用户进程开始,到第一个应用程序启动之间的系统服务进程
    发表于 11-20 16:44

    Linux内核启动过程解析

    Uart驱动却把串口设备名写死了,如本例中linux2.6.37串口设备名为ttyO0,而不是常用的ttyS0。有了控制台内核启动过程中就可以通过串口输出信息以便开发者或用户了解系统
    发表于 10-26 17:20

    嵌入式uCLinux内核启动过程分析

    分析uCLinux的启动过程,可以加快系统启动速度、正确建立应用环境。本文要研究的就是uCLinux操作系统内核启动过程
    发表于 08-15 16:51 788次阅读

    init函数和init进程的区别

    由于执行init()函数的内核线程和init进程进程标识符都是1,它们又都叫
    发表于 01-05 10:49 1671次阅读

    Linux基础命令之Linux启动过程详解

    。 2.2.1 概述 用户开机启动Linux过程如下: (1)当用户打开PC(intel CPU)的电源时,CPU将自动进入实模式,并从地址
    发表于 10-18 14:17 2次下载
    <b class='flag-5'>Linux</b>基础命令之<b class='flag-5'>Linux</b><b class='flag-5'>启动过程</b>详解

    试述shell的启动过程详情

    进程结束后wait函数返回,从而shell收到通知并回收资源。本文主要说明shell如何启动用户进程Linux系统中可
    发表于 11-15 11:06 3748次阅读
    试述shell的<b class='flag-5'>启动过程</b>详情

    详解bootloader的执行流程与ARM Linux启动过程分析

    S3C2410 ARM处理器为例,详细分析了系统上电后 bootloader的执行流程及 ARM Linux启动过程
    的头像 发表于 12-21 09:24 1w次阅读
    详解bootloader的<b class='flag-5'>执行</b>流程与ARM <b class='flag-5'>Linux</b><b class='flag-5'>启动过程</b>分析

    走进Linux之systemd启动过程

    Linux系统的启动方式有点复杂,而且总是有需要优化的地方。传统的Linux系统启动过程主要由著名的init
    发表于 04-27 19:14 3175次阅读

    Linux系统下init进程的前世今生

    kernel_thread产生的进程,其开始在内核执行,然后通过一个系统调用,开始执行用户空间
    发表于 04-28 17:23 968次阅读

    解析基于ARM64的init用户进程究竟如何启动

    [导读] 前面的文章有提到linux启动的第一个进程init,那么该进程究竟是如何从内核
    发表于 01-26 17:05 2次下载
    解析基于ARM64的<b class='flag-5'>init</b><b class='flag-5'>用户</b><b class='flag-5'>进程</b>究竟如何<b class='flag-5'>启动</b>?

    kernel执行第一个init应用程序的实现原理

    Linux系统启动过程中通过`init_task`创建0号idle进程。然后通过`kernel_thread`创建1号init
    的头像 发表于 06-05 14:53 849次阅读
    kernel<b class='flag-5'>执行</b>第一个<b class='flag-5'>init</b>应用程序的实现原理

    kernel到android核心启动过程

    总结一个图:kernel 到android核心启动过程 kernel镜像执行跳转到start_kernel开始执行,在rest_init
    的头像 发表于 12-04 16:59 980次阅读
    kernel到android核心<b class='flag-5'>启动过程</b>