根文件系统启动分析
启动根文件系统过程
UBOOT:启动内核
内核:启动应用程序
Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动
应用程序的运行是依赖文件系统的。(所以需要挂接根文件系统)
执行应用程序在“init_post”这个函数中。
static int noinline init_post(void) { free_initmem(); unlock_kernel(); mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy(); if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); (void) sys_dup(0); (void) sys_dup(0); if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } /* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); } run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); panic("No init found. Try passing init= option to kernel."); }
第一个函数free_initmem怀着好奇心看了看,发现点击进去就见到两个没有定义也查询不到声明的函数:
困扰良久之后,终于找到了问题,这两个函数是uboot中的宏定义,内核中有一些参数或者函数是调用的uboot中的。期间通过谷歌查找这个问题的时候,发现了一个不错的网站,可以查询符号被定义和引用的文件及目录,也是因为这个,发下韦老师课程添加si工程的时候一个纰漏,应该把asm-generic添加进入工程:
看看下面的引用,我们可以发现init/main.c是引用了这个符号的。
(网址点击这里)
后面的函数先不一一分析,等之后熟悉了的时候,记得回来好好分析一下linux kernel源码,对我们一定是有必要的。
上面截图的三个函数,首先是open一个控制台设备:这里以可读可写打开 /dev/console 设备
这三个文件代表标准输入,标准输出,标准错误
这也是之前linux系统编程中说到文件描述符的时候说过的,0:标准输入 1:标准输出 2:标注错误
写程序时经常用到 printf 标准输出,scanf 标准输入,err()标准错误。它们是指 3 个文件。
假设 open(dev/console) 是打开的第 1 个文件。 sys_dup(0)是指复制的意思,
复制后是第 2 个文件,则有了第 1 个文件和第 2 个文件。第 2 个文件也是指向 dev/console
另一个 sys_dup(0) 也是复制文件,则这个是第 3 个文件,也是指向 dev/console 这个文件。
意思是,所有的 printf 打印信息都会从 dev/console 打印出来。
输入时也是从 dev/console 这个文件输入的。想输入错误信息也是从 dev/console 这个文
件输出的。这个文件 dev/console 对应终端。在我们这个例子中 dev/console 对应串口 0.
对于其他设备,这个 dev/console 可能是 键盘液晶。
复制之前随笔一段:
2.用 run_init_process 启动第一个应用程序:
一般来说第一个应用程序运行后,就不会从系统返回了。
这个应用程序:
(1)要么是 UBOOT 传进来的命令行参数: 如init=linuxrc 这个样子
(2)要么是 sbin/init,要是 sbin/init 不成功,则还有 /etc/init 或 bin/init,bin/sh
查找 execute_command 符号在哪里被引用,可以借助source insight的relation视图:
如果设置了 init= 某个文件(例如 linuxrc) ,那么这个 execute_command 就等于这个文件(linuxrc).
则上面的意思是:如果execute_command 不为NULL,则会用 run_init_process 启动这个应用 程序 execute_command。
run_init_process(execute_command);若这个程序没有死掉没有返回,那么函数
run_init_process 就不会返回。我们第一个程序一般来说都是一个死循环。
若没有定义 execute_command,则往下执行:
最后打印:
panic("No init found. Try passing init= option to kernel.");
现在重新下载一下文件系统试试看呢:重新下载fs_mini_yaffs2文件系统
现在进入了文件系统。
可以看到进程为1的应用程序是init:
可见第一个应用程序: init 是内核启动的第一个应用程序。
其中的 -sh 就是当前的应用的程序,就是当前接收的这个串口。输入字符回显字符。就是
sh 这个程序接收这些东西。
busybox的引入:可以看到这些ls cp mkdir等常见命令是busybox的软连接。
所以要知道 init 进制做的哪些事,就得看 busybox 源码。
1.建立 busybox 源码 SI 工程,加入全部代码。
(1) .猜测 init 程序要做的事情。
UBOOT 的目标是启动内核,内核的目的是启动应用程序,第一个应用程序是/sbin/init(或
其他 init),但最终目标还是启动客户的“用户应用程序”。如手机程序;监控程序等。
这样显然会有一个“配置文件”,在这个文件里指定需要执行哪些不同客户的“应用程序”。
故 init 程序要读取这个:配置文件
再解析这个:配置文件
根据这个“配置文件”:执行客户的用户程序
2.分析 init.c 源码: init_main函数
2).分析 /etc/inittab 配置文件。
在 busybox 中有 inittab 说明文档。在busybox-1.7.0\busybox-1.7.0\examples目录下有inittab说明。
inittab格式:
<id>:<runlevels>:<action>:<process>
是启动不同用户的应用程序。则会有:
指定要执行的程序。
何时执行程序。
id 项: /dev/id 用作终端(终端: stdin-printf,stdout-scanf,stderr-err 注:对于console控制台来说,它的输入是我用户层的输出,所以,stdin——》printf)
会加上一个 /dev 前缀。作为“控制终端”(controlling tty)。
id 可以省略。
runlevels 项:
完全可以忽略掉。
action 项:
指示程序何时执行。
process 项:
是应用程序或脚本。
终于看到数据结构中讲的linux内核链表了。。。可以回过去看看这个思路。
/* Reboot on Ctrl-Alt-Del */ new_init_action(CTRLALTDEL, "reboot", ""); 这里id为空, runlevels 忽略。 action是“CTRLALTDEL” ;应用程序是“reboot”。 :: ctrlaltdel:reboot /* Umount all filesystems on halt/reboot */ 当关闭或重启系统时卸载所有的文件系统 new_init_action(SHUTDOWN, "umount -a -r", ""); ::shutdown:umount -a -r /* Swapoff on halt/reboot */ 当PC机有资源不够用时,会将当前内存中的某些应用程序置换到硬盘上。嵌入式中不同这项。 if (ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "swapoff -a", ""); /* Prepare to restart init when a HUP is received */ new_init_action(RESTART, "init", ""); ::restart:init /* Askfirst shell on tty1-4 */ new_init_action(ASKFIRST, bb_default_login_shell, ""); ::askfirst:-/bin/sh new_init_action(ASKFIRST, bb_default_login_shell, VC_2); tty2::askfirst:-/bin/sh new_init_action(ASKFIRST, bb_default_login_shell, VC_3); tty3::askfirst:-/bin/sh new_init_action(ASKFIRST, bb_default_login_shell, VC_4); tty4::askfirst:-/bin/sh /* sysinit */ new_init_action(SYSINIT, INIT_SCRIPT, ""); ::sysinit:/etc/init.d/rcS
5.可见, parse_ininttab 解析这个 inittab 时,先打开 /etc/inittab 。再创建很多结构放到链表中。
到这里我发现,分析代码的工作靠写博客是不行,还是得自己去分析理解别人的执行流程,所以,只听课不自己去分析,是学不好的。
最小的根文件系统需要的项:(init 进程需要)
1.打开终端: /dev/console, /dev/NULL
不设置 inittab 格式中的 id(标准输入、输出和标准错误)时,就定位到 /dev/NULL 中去,如果设置了inittab,就定位到/dev/console中去。
2.init 程序本身就是 busybox .(原因见下,busybox的配置和编译)
3.需要配置文件: /etc/inittab
4.配置文件若指定了某些应用程序或执行脚本--这些必须存在,不存在会有默认的。
5.应用程序需要的库(fopen、 fwrite 等函数需要,c库,glibc) 。
看 Makefile 中的交叉编译的指定。在 linux 中一般交叉编译工具的指定以 CROSS 开头。
交叉编译器可以在 Makefile 中直接定义。没法在配置图形项(make menuconfig)中
定义的情况下,就在 Makefile 中直接定义。直接写成:加上前缀 arm-linux-
首先是官网下载busybox,解压缩,执行make menuconfig报错,百度解决问题:
进入主界面:
最新版的 busybox 中 可以在上面的图形界面配置交叉编译器。
这里在韦老师提供的busybox1.7.0,而我用的ubuntu16.04,编译就是有错误,百度必应谷歌都无果之后,选择官网下载了一个最新的busybox1.28.2,这样make menuconfig和make都一次性成功,下载地址:https://busybox.net/downloads/
困扰了好久,最后还是找到一个解决办法,不然就只有回退ubuntu版本这个办法了,毕竟busybox不是自己写的啊。
下面以韦老师视频老版本的为例,新版本的1.28.2默认配置即可,这些常用配置早已默认给我们配置好了:
这是将 BusyBox 编译成一个静态的程序时,那么那些 C 库 也不需要了。除非用户的应用
程序指定需要库。这里不选表示用动态的。 C 库有两种, glibc 和 uclibc,如果是用 glibc 静
态编译时,会有提示一个警告信息,可能在以后使用时出问题,所以这里不用静态编译。而用动态链接。
其实老版本的我也就手动配置了一个Tab complete,其他都是使用的默认,只是这个老版本毕竟不能适应新的ubuntu了。
/*
*更正:之前的老版本busybox报错,因为我少指定了一个东西,就是交叉编译器,需要在Makefile中增加:
*
*这样之后就不报错了,也就是老版本也可以使用,新版本自然为我们做的更多,肯定更好使用才会更新嘛。
*为什么会发现?因为我发现没有谷歌百度到这个问题或者谷歌百度的问题还是没有解决,还有就是学习群和论坛都没有
*人提起这个问题,所以我想可能是我少了什么操作,果然检查一下,发下少了配置项。
*/
注意上方,linuxrc也是一个busybox的软链接文件。linuxrc你是否眼熟呢?对,它就是uboot传给内核的参数 init=/linuxrc,
所以,之前说了,内核启动的第一个应用程序init,而init是执行linuxrc,而linuxrc是busybox的链接文件,所以init程序就等价于busybox应用程序。
在 first_fs创建 dev 目录,并创建这两个 字符文件设备。
用 mknod 创建 console , c 表示字符型设备, 5 是主设备号, 1 是次设备号。
定位到“/dev/console”里面去。
这里的fs_mini目录是韦老师书上写的,而我们这里创建的是first_fs目录下的lib。不可照搬:
这里韦老师加上x(即可执行权限)其实是多余的,因为这里mkyaffs2image已经具有可执行权限了:
上面mkyaffs2image的usage有用法说明。
这样就把first_fs中的busybox制作成了我们的first_fs.yaffs2文件系统了。
NOTE:
在建立glibc库文件的时候,一定要加上 -d选项
cp *.so* /work/nfs_root/first_fs/lib -d
不加-d选项制作的yaffs2文件系统会大一倍多。
现在跟文件系统还需要改进:
5.完善根文件系统。
①.挂载虚拟的根文件系统。
在内核中,当前有哪些应用程序在跑,这些信息如何收集。是内核提供了一个虚拟的文件系统,叫 proc 文件系统。将这个内核提供的虚拟文件系统 proc 挂载一下。
所以,先是在文件系统上mkdir 然后mount挂载。
这里etc/fstab需要自己创建,按照上面的格式。etc/fstab格式可以在busybox目下搜索查看。mount -a之后,我们不用手动挂载就可以识别ps命令了:
在busybox目录下搜素mdev,有说明文档。这主要是学习韦老师的方法,他是怎么去接触学习这个。
在没有使用mdev之前,ls dev只有2个,console和null,使用了之后,就一大堆设备了。
制作jaffs2文件系统具体步骤参考韦老师的书籍,这个主要用在nor flash上。
最后说一下网络文件系统:
网络文件系统。这个文件系统放在服务器上,内核启动时识别服务器上这个目录,将这个
目录当成 根文件系统。
这样便不需要每次烧写。
NFS还是比较重要和有用的,也方便调试,可是我现在的设备不支持三网ping通(或者说很难支持),之后如果需要,回看这个一章节韦老师的视频同时参考网络教程。