第4阶段——制作根文件系统之分析init进程(2)
本节目标:
(1) 了解busybox(init进程和命令都放在busybox中)
(2) 创建SI工程,分析busybox源码来知道init进程做了哪些事情
(3) 分析busybox中init进程 init_main()
(3.1)熟悉init进程的inittab配置文件(位于/etc/inittab)
(3.2)熟悉inittab配置文件中不同action的子进程区别
(3.3)了解init进程如何读取分析inittab,以及运行inittab文件中的各个子进程
(4) 了解制作一个最小的根文件系统的需求
1.busybox简介
内核启动成功后,建立init进程并执行了第一个应用程序后,我们就可以输入ls、cp、vi等命令了
这些命令其实都是一个应用程序,命令都放在了/bin目录中,如下图所示:
不过它们的链接地址都是放在了busybox里.比如:执行ls命令,其实就是执行 busybox ls,
如下图所示,我们在/bin目录中输入busybox ls,和ls命令一摸一样:
同样,我们在/bin目录中输入ls - l 列出详细信息,如下图所示:
发现所有命令都是放在busybox中,linux是借助busybox来实现这些命令
除了命令外,init进程同样也是放在busybox中,如下图:
所以命令和init进程都位于busybox,制作根文件系统必须要busybox
2. 接下来创建SI工程,分析busybox源码来知道init进程做了哪些事情
busybox源码位于资料光盘中/system中,添加所有文件,并同步文件.
可以发现:
其中ls命令就位于ls.c文件中,cp命令就位于cp.c文件中,同样的init进程就位于init.c文件中
执行这些命令或者进程,最终调用它们自己的文件中xx_main()函数。
所以分析init进程就分析init.c文件中的init_main()函数
3分析busybox中init进程 init_main()
init进程:除了启动第一个应用程序(/linuxrc或者/sbin/init等),还要启动用户的应用程序(例如启动摄像,视频等),那么就需要:
(1)读取配置文件(一般放在linux中/etc目录下, /etc/inittab)
(2)解析配置文件
(3)最后执行用户的应用程序(里面的各个子进程)
其中配置文件说明在busybox-1.7.0/examples/inittab中,通过inittab分析得出:
inittab配置文件格式如下:
Format for each entry: <id>:<runlevels>:<action>:<process>
参数如下:
id: id 会等于/dev/id, 用做终端(标准输入、标准输出以及标准错误) ,这个可以不需要设置,因为/etc/console已经设为标准输入输出了,如不设置就等于dev/null,则从控制台输入输出。
runlevels:可以被忽略
action: 运行时机,指应用程序何时(action)行动,它的参数有(参数必须小写):
sysinit(用来初始化时启动),
respawn(每当相应的进程终止运行时,该进程就会重新启动),
askfirst(每次启动进程之前等待用户按下enter键),
wait(告诉init必须等到相应的进程执行完成之后才能继续执行),
once(仅执行相应的进程一次,而且不会等待它执行完成),
restart(当重新读取分析inittab配置文件时,会执行相应进程),
ctrlaltdel(当按下Ctrl+Alt+Delete组合键时,会执行相应进程),
andshutdown(该进程用于系统关机时执行)
process:应用程序或者脚本, 就是要启动的进程(如果有“-”字符,说明这个程序被称为”交互的”)。
init_main()流程图如下:
3.1先分析init_main()前部分如何读取解析配置文件
init_main()部分代码如下:
- int init_main(int argc, char **argv)
- {
- ... ...
- console_init(); //初始化控制台,在init_post()中只是打开了控制台设备
- ... ...
- if (argc > //在init_post()中执行的”_init_process("/sbin/init");”,所以argc=1, argv=/sbin/init
- && (!strcmp(argv[], "single") || !strcmp(argv[], "-s") || LONE_CHAR(argv[], '')))
- {... ...} //此处省略,因为不执行
- else {
- parse_inittab(); //argc==1,执行else,读取解析init 表(解析配置文件)
- }
- .... ... //运行应用程序
- }
通过函数名称可以猜测出,上面代码中parse_inittab()就是实现解析init表的
3.1.1接下来分析parse_inittab();函数是怎么读取解析init表:
由于argc=1,所以会进入到parse_inittab()中
该函数代码如下:
- #define INITTAB "/etc/inittab" //定义INITTAB=/etc/inittab
- static void parse_inittab(void)
- {
- file = fopen(INITTAB, "r"); //找到INITTAB定义,显然是打开 /etc/inittab 配置文件
- /* 如果/etc/inittab无法打开,则调用new_init_action进行一些默认的操作配置 */
- if (file == NULL) {
- new_init_action(CTRLALTDEL, "reboot", "");
- new_init_action(SHUTDOWN, "umount -a -r", "");
- if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");
- new_init_action(RESTART, "init", "");
- new_init_action(ASKFIRST, bb_default_login_shell, "");
- 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);
- /* sysinit */
- new_init_action(SYSINIT, INIT_SCRIPT, "");
- return ;
- }
- /* while一直循环解析file文件将里面的内容一行一行读出来,然后调用new_init_action进行操作*/
- while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) {
- /* Skip leading spaces */
- for (id = buf; *id == ' ' || *id == '\t'; id++);
- /* Skip the line if it's a comment */
- if (*id == '#' || *id == '\n')
- continue;
- ... ...
- new_init_action(a->action, command, id); //读完后调用new_init_action
- }
}
显然parse_inittab()函数任务就是将配置文件内容读出来,然后调用new_init_action解析配置文件.
如果,上面函数中file == NULL,会配置出什么来?默认的配置文件里内容又是什么?
(1)首先我们分析new_init_action()函数
我们以上面的new_init_action(ASKFIRST, bb_default_login_shell, VC_2);为例来分析:
其中该函数定义为:
- new_init_action(int action, const char *command, const char *cons)
- {... ...}
首先搜索这3个实参 ASKFIRST, bb_default_login_shell, VC_2:
其中ASKFIRST=0X04;
bb_default_login_shell[]="-/bin/sh"
VC_2= "/dev/tty2"
其中参数定义:
0X04(action): 等于配置文件的action(运行时机,指应用程序何时(action)行动)
"-/bin/sh"(*command): 等于配置文件的process(应用程序)
"/dev/tty2"( *cons) :等于配置文件的id (终端,这里使用的tty2终端)
接下来分析new_init_action(0x04,"-/bin/sh","/dev/tty2")函数:
- # define bb_dev_null "/dev/null" //定义bb_dev_null等于"/dev/null"
- static struct init_action *init_action_list = NULL; //定义init_action型结构体链表
- static void new_init_action(int action, const char *command, const char *cons) //函数开始
- {
- /*
- 先介绍init_action结构体,定义如下:
- struct init_action {
- struct init_action *next; //指向下一个init_action结构体,用于链表
- int action; //执行时机,用于何时执行
- pid_t pid; //process id(进程号)
- char command[INIT_BUFFS_SIZE]; //应用程序或者脚本, 就是要启动的进程。
- char terminal[CONSOLE_NAME_SIZE]; //终端
- };
- */
- /*定义init_action型指针, *new_action:指新的结构体*/
- struct init_action *new_action, *a, *last;
- /*判断cons是否 "/"开头 */
- if (strcmp(cons, bb_dev_null) == && (action & ASKFIRST))
- return;
- /* a和last都等于init_action_list 链表,a始终指向下一个结构体,查找是否有相同的command和termin*/
- for (a = last = init_action_list; a; a = a->next)
- {
- /*找到有相同的command和termin,则只更新action执行时机参数,并return*/
- if ((strcmp(a->command, command) == )&& (strcmp(a->terminal, cons) == ))
- {a->action = action;
- return;}
- /*更新last,等于上一个init_action 结构体*/
- last = a;
- }
- new_action = xzalloc(sizeof(struct init_action)); //为new_action分配内存,使它成为静态变量,不释放
- if (last) { //last!=NULL,说明init_action_list当前有内容,将链表下一个节点等于new_action
- last->next = new_action;
}- else { //last==NULL,说明init_action_list里面还没有内容,直接将链表等于new_action
- init_action_list = new_action;
}- strcpy(new_action->command, command); //更新当前链表里的command
- new_action->action = action; //更新链表里的action
- strcpy(new_action->terminal, cons); //更新链表里的command
- messageD(L_LOG | L_CONSOLE, "command='%s' action=%d tty='%s'\n",
- new_action->command, new_action->action, new_action->terminal);
- }
所以new_init_action()解析配置文件,就是将配置文件中的配置格式放在init_action链表中.
(2)然后通过new_init_action()函数反推出parse_inittab()函数中file==NULL情况下的默认配置文件:
其中配置文件格式: <id>:<runlevels>:<action>:<process>
id: id 会等于/dev/id, 用做终端,可以忽略使用从控制台输入输出。
runlevels:可以被忽略
action: 运行时机,指应用程序何时(action)行动,它有sysinit, respawn, askfirst, wait, once,restart, ctrlaltdel, andshutdown.这些值可选择。
process:应用程序或者脚本, 就是要启动的进程。
(2.1) 然后逐步反推代码:
- if (file == NULL) {
- /*ID为空, runlevels忽略, action= ctrlaltdel, process= reboot */
- new_init_action(CTRLALTDEL, "reboot", "");
- /*ID为空, runlevels忽略, action= shutdown, process= umount -a -r */
- new_init_action(SHUTDOWN, "umount -a -r", "");
- /* ENABLE_SWAPONOFF 未定义,不分析*/
if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", "");- /*ID为空, runlevels忽略, action= restart, process= init */
- new_init_action(RESTART, "init", "");
- /*ID为空, runlevels忽略, action= askfirst, process= -/bin/sh */
- /* 其中bb_default_login_shell ="-/bin/sh" */
- new_init_action(ASKFIRST, bb_default_login_shell, "");
- /*ID=/dev/tty2, runlevels忽略, action= askfirst, process=-/bin/sh */
- new_init_action(ASKFIRST, bb_default_login_shell, VC_2); //VC_2= "/dev/tty2"
- /* ID=/dev/tty3, runlevels忽略, action= askfirst, process=-/bin/sh */
- new_init_action(ASKFIRST, bb_default_login_shell, VC_3); // VC_3= "/dev/tty3"
- /* ID=/dev/tty4,runlevels忽略, action= askfirst, process=-/bin/sh */
- new_init_action(ASKFIRST, bb_default_login_shell, VC_4); // VC_4= "/dev/tty3"
- /* sysinit */
- /*ID为空, runlevels忽略, action= sysinit, process= etc/init.d/rcS */
- new_init_action(SYSINIT, INIT_SCRIPT, ""); //INIT_SCRIPT="etc/init.d/rcS"
- return ;
- }
(2.2)根据配置文件格式<id>:<runlevels>:<action>:<process>,得出最终默认的配置文件内容如下:
- :: ctrlaltdel:reboot //当按下Ctrl+Alt+Delete组合键时,会执行reboot
- :: shutdown:umount -a -r // 告诉init,在系统关机的时候执行umount命令卸载所有文件系统,失败则以读模式安装
- :: restart:init //init重启时,指定执行init进程
- :: askfirst: -/bin/sh //启动-/bin/sh之前不显示,等待用户按enter键
- /dev/tty2:: askfirst:-/bin/sh //启动tty2的-/bin/sh之前在终端tty2上显示信息,并等待用户按enter键
- /dev/tty3:: askfirst:-/bin/sh //启动tty3的-/bin/sh之前在终端tty3上显示信息,并等待用户按enter键
- /dev/tty4:: askfirst:-/bin/sh //启动tty4的-/bin/sh之前在终端tty4上显示信息,并等待用户按enter键
- :: askfirst:etc/init.d/rcS //启动etc/init.d/rcS之前不显示,并等待用户按enter键
从上面发现init进程里分了很多个子进程,每个子进程都需要3样:
id(可以为空),action(运行时机,必须小写),process(指定要运行的应用程序位置)
parse_inittab()函数到这里就分析完毕,它主要就是将配置文件读出来解析,然后放在链表init_action_list中
3.2 接下来继续分析int_main()后面如何运行应用程序的,简写代码如下:
- int init_main(int argc, char **argv)
- {
- ... ...
- console_init(); //初始化控制台,在init_post()中只是打开了控制台设备
- ... ...
- if (argc > //在init_post()中执行的”_init_process("/sbin/init");”,所以argc=1, argv=/sbin/init
- && (!strcmp(argv[], "single") || !strcmp(argv[], "-s") || LONE_CHAR(argv[], '')))
- {... ...} //此处省略,因为不执行
- else {
- parse_inittab(); //读取解析init 表(解析配置文件)
- }
- ....
- /* First run the sysinit command */
- run_actions(SYSINIT); /*首先运行系统初始化的链表节点(SYSINIT:等待运行结束为止)*/
- /* Next run anything that wants to block */
- run_actions(WAIT); //运行 action = WAIT的链表节点(WAIT:等待运行结束为止)
- /* Next run anything to be run only once */
- run_actions(ONCE); //运行 action = ONCE的链表节点(ONCE:不会等待运行)
- while () {
- run_actions(RESPAWN); //运行 action = RESPAWN的链表节点(pid==0时才能运行)
- run_actions(ASKFIRST); //运行action = ASKFIRST的链表节点(pid==0时才能运行,且还需要等待回车)
- sleep(); //让CPU等待会儿
- wpid = wait(NULL); //等待上面两个的子进程退出
- while (wpid > ) //退出后设置pid=0,然后while重新运行RESPAWN&& ASKFIRST
- { a->pid = ;}
- }
- }
从上面得出run_actions()函数就是用来链表节点里的应用程序.
且 ASKFIRST和 RESPAWN会在while中一直运行.
3.3分析上面run_actions ()函数是怎么运行链表节点的,代码如下:
- static void run_actions(int action) //执行时机参数
- {
- struct init_action *a, *tmp;
- for (a = init_action_list; a; a = tmp) //从链表init_action_list中循环查找
- {tmp = a->next; //指向下一个链表的节点
- if (a->action == action) //找到相同名称的action节点了
- {
if (a->terminal[] && access(a->terminal, R_OK | W_OK))- { delete_init_action(a);} //已经使用过该应用程序,从链表中删除
- /* SYSINIT|WAIT|CTRLALTDEL|SHUTDOWN|RESTART这些的应用程序都需要等待执行完毕 */
- else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART))
- { waitfor(a, ); //(0:ID号为空) 执行a节点的应用程序,然后等待它执行完毕
- delete_init_action(a); //然后从链表init_action_list中删除(delete)
- }
- else if (a->action & ONCE) //action(运行时机)=ONCE时,不需要等待执行完毕
- {run(a); //创建子进程后即删除该节点
- delete_init_action(a);
}- //action(运行时机)= RESPAWN | ASKFIRST时,也不需要等待执行完毕
- else if (a->action & (RESPAWN | ASKFIRST))
- { if (a->pid == ) {a->pid = run(a);} } //a->pid==0才run(a)创建子进程
- }
- }
- }
通过上面代码分析出执行waitfor()时,需要等待应用程序执行完毕,
执行run()时,不需要等待.
3.4先分析上面waitfor(a, 0)函数是怎么实现执行应用程序然后等待的?
waitfor代码如下:
- static int waitfor(const struct init_action *a, pid_t pid) //*a:链表中的一个节点
- {
- int runpid;
- int status, wpid;
- /*run(a):创建<process>子进程(运行应用程序)*/
- runpid = (NULL == a)? pid : run(a); //当a==NULL,runpid=pid=0,否则runpid=run(a).
- while () {
- wpid = waitpid(runpid, &status, ); //等待应用程序执行完毕
- if (wpid == runpid)
- break;
- if (wpid == - && errno == ECHILD) {
- /* we missed its termination */
- break;
- }
- /* FIXME other errors should maybe trigger an error, but allow
- * the program to continue */
- }
- return wpid;
- }
最终waitfor还是调用的run(a),所以这些所有节点都会调用run(a)来创建<process>子进程(运行应用程序).然后在while中循环运行action=(RESPAWN| ASKFIRST)的节点
3.2.3 , 除了没分析run(a)以外,RESPAWN和ASKFIRST还是没懂什么不同.
RESPAWN和ASKFIRST到底有什么不同,就需要分析run(a)了
代码如下:
- static pid_t run(const struct init_action *a) //*a:链表中的一个节点
- {
- .. ...
- if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART))
- {... ...} //只分析RESPAWN和ASKFIRST有什么不同,所以此处省略
- if (a->action & ASKFIRST) //action==ASKFIRST的时候
- {
- /*打印\nPlease press Enter to activate this console.(请按回车键启动控制台.)*/
- tatic const char press_enter[] ALIGN1 ="\nPlease press Enter to activate this console. ";
- char c;
- ... ...
- while (read(, &c, ) == && c != '\n'); //一直等待用户回车
- }
- BB_EXECVP(cmdpath, cmd); //创建子进程
- }
从上面分析出,当执行action=RESPAWN时,只创建子进程,而action=ASKFIRST时,需要一直等待用户回车才创建子进程
4.通过前面的分析,制作一个最小的根文件系统至少需要:
(1)/dev/console(终端控制台, 提供标准输入、标准输出以及标准错误)
- /dev/null (为空的话就是/dev/null, 所有写到这个文件中的数据都会被丢弃掉。)
(2) init进程的程序(也就是busybox,因为init程序位于busybox中)
(3)/etc/inittab(用于init进程读取配置, 然后执行inittab里的指定应用程序)
(4)应用程序(被inittab配置文件调用的应用程序)
(5)C库(被应用程序调用的C库函数,比如:printf,strcmp,fopen等)
init进程分析完毕,接下来开始通过上面的需要来制作一个最小文件系统.
第4阶段——制作根文件系统之分析init进程(2)的更多相关文章
- 第4阶段——制作根文件系统之分析init_post()如何启动第1个程序(1)
本章学习如何启动第一个应用程序 1.在前面的分析中我们了解到,在init进程中内核挂接到根文件系统之后,会开始启动第一个应用程序: kernel_init函数代码如下: static int __in ...
- 制作根文件系统之Busybox init进程的启动过程分析
先来介绍一下什么是Busybox:它是将众多的UNIX命令集合进一个很小的可执行程序中. 在制作根文件系统之内核如何启动init进程中遗留了一个问题是/linuxrc是内核启动的第一个应用程序,那么它 ...
- 第4阶段——制作根文件系统之编译配置安装busybox(3)
在上一节分析出制作一个最小的根文件系统至少需要: (1)/dev/console(终端控制台, 提供标准输入.标准输出以及标准错误) /dev/null (为空的话就是/dev/null, 所有写到 ...
- Linux学习 :Uboot, Kernel, 根文件系统初步分析
1.U-Boot启动内核的过程可以分为两个阶段: 1)第一阶段的功能 硬件设备初始化 加载U-Boot第二阶段代码到RAM空间 设置好栈 跳转到第二阶段代码入口 2)第二阶段的功能 初始化本阶段使用的 ...
- 使用Busybox-1.2.0制作根文件系统
使用Busybox-1.2.0制作根文件系统 cross-3.3.2 make-3.8.1 STEP 1: 创建根文件系统目录,主要包括以下目录/bin,/etc,/dev,/mnt,/sbin,/u ...
- 利用busybox制作根文件系统
实际项目中可以使用Buildroot制作根文件系统 1.busybox源码下载及配置 https://busybox.net/downloads/ 1.1.修改Makefile (1) ARCH = ...
- 制作根文件系统之内核如何启动init进程
start_kernel其实也是内核的一个进程,它占用了进程号0,start_kernel的内容简写如下: asmlinkage void __init start_kernel(void) //内核 ...
- mini6410基于linux2.6.36内核通过NFS启动根文件系统总结(四制作根文件系统及通过NFS挂载文件系统)
http://blog.csdn.net/yinjiabin/article/details/7489563 根文件系统一般包括: 1)基本的文件系统结构,包含一些必须的目录,比如:/dev,/pro ...
- mkyaffs2image制作根文件系统、使用NFS挂载虚拟机目录(2)
1.制作根文件系统及nfs烧写 1.1 先解压文件系统,/wok/nfs_root 目录下是已经构造好的各种文件系统:① fs_mini.tar.bz2 是最小的根文件系统,里面的设备节点是事先建立好 ...
随机推荐
- 七、vue中v-for有时候对页面不会重新渲染,数组变化后如何到渲染页面
v-for不能进行双向数据绑定,页面渲染完成后,再次更改v-for遍历的数据,js里面打印的数据看到数据值已经更改,但是页面的数据就是没有渲染,这是为什么呢? vue中v-for和angularj ...
- Abp(.NetCore)开发与发布过程3-部署Ubuntu站点
以下是笔者在 Ubuntu 16.0-64bit 环境下 发布 ABP(.NetCore)的全过程.特此记录,希望对大家有所帮助. 准备的工具 1.PuTTY(ssh,如果不想每次都用阿里云的远程登录 ...
- 【数学】HPU--1037 一个简单的数学题
1037: 一个简单的数学题 [数学] 时间限制: 1 Sec 内存限制: 128 MB提交: 259 解决: 41 统计 题目描述 小明想要知道$a^b$的值,但是这个值会非常的大. 所以退而求其次 ...
- ASP.NET Core 源码学习之 Logging[4]:FileProvider
前面几章介绍了 ASP.NET Core Logging 系统的配置和使用,而对于 Provider ,微软也提供了 Console, Debug, EventSource, TraceSource ...
- cve-2017-8464 复现 快捷方式远程代码执行
cve-2017-8464 2017年6月13日,微软官方发布编号为CVE-2017-8464的漏洞公告,官方介绍Windows系统在解析快捷方式时存在远程执行任意代码的高危漏洞,黑客可以通过U盘.网 ...
- 微软认知服务 Luis
学习认知服务 Luis(Language understand intellgence service) 简述: 开发者可以通过Luis开发可以理解人类语言的只能应用,学习人类语言分析语义. 利用这个 ...
- 从Google Play下载应用并不安全,上千款监视软件伪装其中
如果你认为在官方应用市场里下载app就觉得安全的话,小编可以负责任的回答你:"too young too simple,sometimes native" 今年4月,BankBot ...
- android学习ViewFlipper的使用
android系统自带的多页面管理控件,它可以实现子页面的自动切换 1,为ViewFlipper添加View 静态导入:在layout布局文件中直接导入 动态导入:通过addview方法进行导入 2, ...
- Oracle进程与系统进程
--Oracle进程与系统进程 --------------------------2013/11/25 这里讨论Linux/Unix环境下,oracle v$process与操作系统对应的关系. 系 ...
- WebApi 的CRUD 的方法的应用
一.最近一直在忙于开发公司的新的项目和搭建公司的框架,也好久没有写博客了.对于RaidDevelopmentFramework 我有着自己的见解在应用到实际的框架中确实挺好用的,但是还是存在一部分的问 ...