1_5.4.2_根文件系统_构建根文件系统之init进程分析_P

想知道这个这个文件系统里面有哪些东西,我们需要跟着init进程继续分析,看这个进程需要哪些东西。

在根文件系统里面有很多命令,比如cp,ls等等,这些命令也就相对一个应用程序,这些命令可能有成千上百种。

对于这么多的命令,在嵌入式领域中,我们使用busybox,它是ls,cp,cd这些命令的组合,我们编译busybox的时候得到一个应用程序叫busybox,我们执行的ls,cp这些指令,实际上执行的都是这些指令到busybox的链接,比如在shell中输入ls,其实就是执行了busybox ls。

可以看到,ls和copy都是到busybox的链接。
在这里插入图片描述
使用ls和busybox ls是一样的。
在这里插入图片描述
事实上,在嵌入式系统,这个/sbin/init也是到busybox的链接。
在这里插入图片描述
在这里插入图片描述
所以说,我们想要确定这个init进程做了哪些事情,就需要找到busybox的源码,来分析一下。

找到配套的busybox,解压并用SI创建一个工程来分析一下。
在这里插入图片描述
在SI中导入busybox源码,可以看到文件中有一个cp.c,cp.c中有一个cp_main函数,可以推测,使用cp命令就是调用了cp_main函数。
在这里插入图片描述
在这里插入图片描述
同样,也有一个init.c文件,init.c文件中有一个init_main函数。
在这里插入图片描述
在这里插入图片描述我们需要分一下这个init_main。

先猜测一下,init程序会做什么事情呢?

答:回顾一下,u-boot的目的是启动内核,内核的目的是启动应用程序

内核怎么启动应用程序呢?

答:内核首先启动了init进程,它位于/sbin/init或其他,由这个进程来启动其他的应用程序。

不同的客户要启动的应用程序各不相同,我们怎么知道要启动哪些应用程序呢?

答:应该还要有一个配置文件,这个配置文件决定了后续还要启动哪些应用程序。

所以,init进程至少要做以下三件事:

  1. 读取配置文件;
  2. 解析配置文件;
  3. 执行用户程序。

阅读源码看下是不是这样。

打开init.c,查看init_main函数,前面是设置信号,先不管。
在这里插入图片描述
继续向下看,初始化控制台(之前打开了控制台,这里初始化一下)等等。
在这里插入图片描述
接着向下看,我们启动内核时没有传入任何参数,所以argc小于1,执行parse_inittab,从名字就可以看出来,这个是解析init table。
在这里插入图片描述打开parse_inittab,可以看到首先是打开一个文件/etc/inittab,显然这个就是配置文件,在linux系统中,配置文件一般都放在etc目录下。打开配置文件后面就是解析了。
在这里插入图片描述
在这里插入图片描述
解析之前,我们先看一下/etc/inittab,打开/etc/inittab,可以看到inittab的格式。
在这里插入图片描述
这些配置文件的目的是启动不同的应用程序,根据这个目的,我们可以猜测一下配置文件里面肯定会有:

  1. 指定执行哪些应用程序;
  2. 指定何时执行这些应用程序。

根据文档说明可以知道,id项会被加上/dev/,也就是/dev/id,用作控制终端(也就是我们的标准输入,标准输出,标准错误,stdin,stdout,stderr : printf,scanf,err)。
在这里插入图片描述
然后是runlevels,这里说可以完全忽略掉。
在这里插入图片描述
然后是action,指示何时执行process,指示执行的应用程序或脚本。红框是action的取值范围
在这里插入图片描述
继续分析源码,可以看到如果没有这个文件,那么就执行默认项,否则解析配置文件。
在这里插入图片描述下面是对配置文件的一些解析,如果是#开头的就忽略掉。
在这里插入图片描述然后id是加上/dev/前缀,解析完最终要调用new_init_action。显然,要了解这些配置项怎么起作用,需要来解析一下这个new_init_action。
在这里插入图片描述以默认配置(即fopen INITTAB失败时)的new_init_action为例,来解析一下。

new_init_action(ASKFIRST, bb_default_login_shell, VC_2);

在这里插入图片描述在源码里面搜索一下bb_default_login_shell,可以看到,就是"-/bin/sh",VC_2就是"/dev/tty2"。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以

new_init_action(ASKFIRST, "-/bin/sh", "/dev/tty2");

就等于

new_init_action(ASKFIRST, "-/bin/sh", "/dev/tty2");

从函数定义可以看出,action(执行时机)是ASKFIRST,command(应用程序或脚本)是-/bin/sh,id(终端)是/dev/tty2。
在这里插入图片描述
接着分析new_init_action,可以看到有一个init_action_list,可以猜测这是一个初始化动作的链表。
在这里插入图片描述查看init_action_list的定义,可以看到init_action_list是一个指向init_action结构体的指针。
在这里插入图片描述
可以猜测,这个new_init_action函数的作用:

  1. 创建一个init_action结构,并填充;
  2. 把这个结构放入init_action_list链表。
    在这里插入图片描述
    现在,假设没有配置文件,根据默认的配置来反推出默认的配置文件,看看它是什么内容。
    在这里插入图片描述其中,773行可以省略,这是因为在PC上,如果系统资源不足,就会把某些不用的进程释放到硬盘中去,将内存空间空出来,但是在嵌入式系统中一般不会这样做,所以if不会进去。
/* Swapoff on halt/reboot */
if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");

最终,反推出来的默认的配置文件如下:
在这里插入图片描述在这里插入图片描述
那么,构建了这样一个链表,之后要怎么使用呢?继续往下看。

构建链表之后就是执行,先执行SYSINIT这一类的动作,然后是WAIT,然后是ONCE。
在这里插入图片描述
再接下来是一个死循环。
在这里插入图片描述
前面说过,action决定了执行时机,它的取值范围有八个,但是inittab中没有说明这些值代表什么意思,我们结合代码分析一下。
在这里插入图片描述
首先分析一下run_action函数,可以看到,上面说的8个action在这里都有对应的执行程序。
在这里插入图片描述
首先看一下SYSINIT,它调用了两个函数waitfor和delete_init_action,这两个函数的作用分别是:

  1. 执行应用程序,并等待它执行完毕(waitfor);
  2. 在链表中删除执行完的init程序(delete_init_action)。
    在这里插入图片描述waitfor的源码如下,可以看到先执行run(a),该函数会创建process子进程,然后在while循环中waitpid,等待a执行完毕。
    在这里插入图片描述delete_init_action则是在链表中删除执行完的init程序。
    在这里插入图片描述
    再看一下run函数,函数前面执行了很多系统调用,最后是一个BB_EXECVP,它也是一个系统调用,用来执行子进程。
    在这里插入图片描述
    在这里插入图片描述
    所以,SYSINIT的功能就是系统初始化,在系统最开始的时候执行,并且执行的时候,系统会等待它执行完毕,执行完毕后就会从链表删除。

然后是WAIT,它的作用和SYSINIT类似,只是它是在SYSINIT之后执行,先执行SYSINIT然后再执行WAIT。

然后是ONCE,可以看到它和SYSINIT也是类似的,只是系统不会等待它执行完毕,同样是执行一次后从链表删除。
在这里插入图片描述

然后是RESPAWN和ASKFIRST,只有在pid等于0时才执行。

系统一开始的时候,pid都是0,只有开始执行该进程后,系统才会给它分配一个非0的pid。
在这里插入图片描述
可以看到,在init_main的while循环中,会不断的执行RESPAWN和ASKFIRST,然后在它们执行完毕后再将它们的pid清零,这样它们才可以再次执行。
在这里插入图片描述
那么,RESPAWN和ASKFIRST有什么区别呢?

这就要从run函数来分析了,在run函数中,当action包含ASKFIRST时,在执行该process前,会先在标准输出输出一条询问信息,此时只有输入回车才会继续向下运行,
在这里插入图片描述
这就是RESPAWN和ASKFIRST的差别,ASKFIRST从名字也可以看出来,在执行前先询问一下。

八个action,我们分析了SYSINIT,WAIT,ONCE,RESPAWN,ASKFIRST五个,还有restart,ctrlaltdel,shutdown是做什么用的呢?

在init_main函数的开头,我们看到有一些信号量,这些信号量调用的函数里面,就会用到这些action。
在这里插入图片描述ctrlaltdel_signal就会来执行ctrlaltdel这一类的函数,restart和shutdown也是类似的。
在这里插入图片描述

回顾一下,init_main函数至少需要这三项:

  1. 打开终端/dev/console;(还需要一个/dev/null)
  2. 需要配置文件/etc/inittab;
  3. 配置文件里指定的应用程序;

需要注意的是,如果在配置文件中不设置id的话,那么该进程的标准输入,标准输出,标准错误,就都定义到/dev/null里去。

假设我们写一个简单的应用程序,这些函数都不是它自己实现的,而是C库实现的,所以还需要一个库文件。

int main(void)
{
	printf("hello, world!\n");	//C库
	fopen();					//C库
	fwrite();					//C库
	fclose();					//C库
}

另外,init程序本身也是busybox,所以一个最小的根文件系统,需要以下五种组成:

  1. /dev/console和/dev/null;
  2. init应用程序 ==> busybox;
  3. 配置文件/etc/inittab;
  4. 配置文件指定的应用程序;
  5. C库;

构建这五种东西,就可以构建出一个根文件系统了,下一节我们就来创建它。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值