Android系统启动流程3 ---linux根文件系统的构建

linux启动内核后,会构建根文件系统,之后才可以使用init进程,进而启动其他应用程序

1. 为什么需要根文件系统

  • init进程在根文件系统中,因此需要根文件系统,来向内核提供init进程
  • 根文件系统提供了根目录,linux通过根文件系统,可以对linux的文件进行管理。
  • etc目录下的文件,是对linux内核进行配置的。而这些文件是存储在根文件系统中的。
  • shell命令程序在根文件系统中(busybox在根文件系统中)

只有内核本身,是不能工作的。必须要有根文件系统(/etc目录下的配置文件,shell命令,/lib目录下的库文件)配合,才能工作

2. 构建根文件系统的步骤

构建根文件系统分为下面几步:

  1. 创建根目录,挂载rootfs文件系统(此时的系统在内存中,里面无内容),解析uboot参数,用于知道后面挂载到具体哪个路径的真正的根文件系统
  2. 解压内核里存放的initrd即initramfs(中间根文件系统)
  3. 由initramfs去启动它自己的initrc脚本,挂载块设备模块
  4. 根据root命令,挂载到磁盘上的根文件系统
  5. 启动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进程的工作

猜你喜欢

转载自blog.csdn.net/weixin_40535588/article/details/121260767