linux启动内核后,会构建根文件系统,之后才可以使用init进程,进而启动其他应用程序
1. 为什么需要根文件系统
- init进程在根文件系统中,因此需要根文件系统,来向内核提供init进程
- 根文件系统提供了根目录,linux通过根文件系统,可以对linux的文件进行管理。
- etc目录下的文件,是对linux内核进行配置的。而这些文件是存储在根文件系统中的。
- shell命令程序在根文件系统中(busybox在根文件系统中)
只有内核本身,是不能工作的。必须要有根文件系统(/etc目录下的配置文件,shell命令,/lib目录下的库文件)配合,才能工作
2. 构建根文件系统的步骤
构建根文件系统分为下面几步:
- 创建根目录,挂载rootfs文件系统(此时的系统在内存中,里面无内容),解析uboot参数,用于知道后面挂载到具体哪个路径的真正的根文件系统
- 解压内核里存放的initrd即initramfs(中间根文件系统)
- 由initramfs去启动它自己的initrc脚本,挂载块设备模块
- 根据root命令,挂载到磁盘上的根文件系统
- 启动init进程,启动终端
2.1 创建根目录和解析uboot参数
创建根目录的过程是在start_kernel实现的
而解析uboot的参考也是在start_kernel实现
start_kernel->
vfs_caches_init()
mnt_init()
init_rootfs(); //初始化rootts
init_mount_tree();//挂载rootts
主要是init_rootfs()和init_mount_tree();
此时已经创建了根目录 ‘’ ,但是里面是空的,没有具体的文件系统,所以操作系统还无法起来
下一步就是要挂载根文件系统
我们思考一个问题,假如我们想要指令内核去挂载指令路径的块设备,比如root=/dev/mtdblock3,那么系统是如何去识别的呢
2.2 解析uboot参数
参考:https://www.cnblogs.com/lifexy/p/7366792.html
如果设置了指定的根文件系统路径,那么会在start_kernel里去解析uboot传过来的参数
asmlinkage void __init start_kernel(void)
{
...
setup_arch(&command_line); //解析uboot传入的启动参数
setup_command_line(command_line); //解析uboot传入的启动参数
....
/*查找内核参数*/
parse_early_param()
{
do_early_param(); //从__setup_start到__setup_end查找early非0的函数,后面会分析
}
/*查找内核参数*/
unknown_bootoption()
{
obsolete_checksetup(); //从__setup_start到__setup_end查找early为0的函数,后面会分析
}
...
rest_init(); //进入rest_init()
}
解析完后,会把bootags参数里的root=/dev/mtdblock3保存在saved_root_name,之后prepare_namespace(后面讲到)会去读取这个值,然后去挂载这个路径下的根文件系统
2.3 解压内核里存放的initrd即initramfs(中间根文件系统)
我们思考,一开始还没有根文件系统的时候,是不可以实现mount的,那么怎么去挂载根文件系统,如果根文件系统放在磁盘,又怎么去初始化磁盘。
这个时候就需要一个存储在ram里的initrd即initramfs来提供一个中间根文件系统,这个中间根文件系统里面提供了initrc脚本,有了这个脚本和他提供的文件系统环境,我们就可以去做一些初始化的工作,比如udev磁盘,mount到磁盘空间等等
kernel_init
do_basic_setup
rootfs_initcall(populate_rootfs)
populate_rootfs函数的作用就是将编译的initramfs文件系统解压到rootfs的根目录中,加载initrd文件。
populate_rootfs()调用unpack_to_rootfs()从内存中读取并解析initrd文件;
并使用sys_dir()、sys_open()、sys_mknod()、sys_symlink()等系统调用新建目录、常规文件、特殊文件和符号链接文件了。此时,VFS从只有根目录"/"成长为了一棵内容丰富的大树。
3. 根据root命令,挂载到磁盘上的根文件系统
static int __init kernel_init(void * unused) //进入init进程
{
prepare_namespace() //挂载根文件系统
{
... ... / /通过解析出来的命令行参数” root=/dev/mtdblock3”来挂接根文件系统 mount_root(); //开始挂载
}
init_post(); //启动应用程序
}
prepare_namespace
mount_root()//将实际文件系统挂载到rootfs的/root目录
4. 启动init进程,启动终端
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* 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) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d). Attempting defaults...\n",
execute_command, ret);
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
去判断根文件系统是不是已经可以访问ramdisk_execute_command指向的init程序了。如果可以,就执行init进程
如果根文件系统已成功挂载,并且里面有/init,那么就会去启动init进程
如果有定义execute_command就去根目录下找对应的应用程序,然后启动
如果ramdisk_execute_command和execute_command定义的应用程序都没有找到,
就到根目录下找 /sbin/init,/etc/init,/bin/init,/bin/sh 这四个应用程序进行启动
5. 构建最小根文件系统所需要的东西
总结最小根文件系统所需东西
- 终端/dev/console 设置/dev/null (如果没有设置标准输入,输出,错误——>无底洞,输出看不到)
- 设置配置文化inittab(配置文件里指定的应用程序或默认配置)
- 需要库(我们想我们自己建立的.c文件里的各种fopen,fread都是c库)
- init本身,即busybox
后面我们分析init进程的工作