kernel启动流程-start_kernel的执行_8.cpio initrd解包

1. 前言

本专题文章承接之前《kernel启动流程_head.S的执行》专题文章,我们知道在head.S执行过程中保存了bootloader传递的启动参数、启动模式以及FDT地址等,创建了内核空间的页表,最后为init进程初始化好了堆栈,并跳转到start_kernel执行。
《kernel启动流程-start_kernel的执行_7.arch_call_rest_init》中提到kernel_init->do_basic_setup->do_initcalls会遍历执行所有的init函数,这其中会执行populate_rootfs函数,populate_rootfs会将initrd释放到rootfs的“/”目录,本文重点介绍start_kernel的cpio initrd解包的主要流程.

kernel版本:5.10
平台:arm64

2. rootfs挂载

rest_init
    \--kernel_thread(kernel_init, NULL, CLONE_FS);
			|--kernel_init
			|	  |--kernel_init_freeable
			|	  |      |--do_basic_setup
			|	  |      |      |--populate_rootfs 
			|     |      |             |   //情形1,通过释放根文件系统到kernel rootfs根目录/
			|     |      |             |--unpack_to_rootfs(__initramfs_start, __initramfs_size);
			|     |      |             |--if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE)) goto done
			|     |      |             |   //情形2,通过dts传递的内存地址释放根文件系统到kernel rootfs根目录/
			|     |      |             |--unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
			|     |      |  //ramdisk_execute_command默认为/init, init_eaccess返回非0,因此会通过prepare_namespace执行挂载
			|     |      |  //因此对于initramfs的用户,需要通过类似"rdinit=linuxrc"的方式指定,并通过init_access检查通过才会
			|     |      |  //对于ramdisk用户,不需要通过类似"rdinit=linuxrc"的方式指定
			|     |      |--if (init_eaccess(ramdisk_execute_command) != 0)
			|     |             ramdisk_execute_command = NULL
			|     |             //情形3, 挂载rootfs文件系统到kernel rootfs根目录/
			|     |             prepare_namespace()
			\--if (ramdisk_execute_command)
			        run_init_process(ramdisk_execute_command)	              

用户制作的根文件系统该如何与内核对接呢?主要包含三种方式:

  1. 情况1. 用户制作的根文件系统通过initramfs的方式与内核镜像打包在一起,这种情况下的rootfs内核会进行cpio压缩;
    此时主要会通过rest_init->kernel_init->populate_rootfs将用户根文件系统释放到内核的rootfs中。

  2. 情况2. 用户制作的根文件系统通过cpio的方式单独处理,不与内核镜像打包在一起;
    仍然会通过rest_init->kernel_init->populate_rootfs路径,只不过这里会判断一下initrd_start是否被初始化过,它主要来源于dts的chosen节点解析的linux,initrd-start,如果initrd_start变量不为空,那么代表用户将根文件系统存放到内存的某个区域,

  3. 情况3. 用户制作的根文件系统以某种文件系统格式化,形成文件系统镜像,内核在启动时,进行挂载
    prepare_namespace,主要针对格式化rootfs为某种文件系统的情况,主要通过挂载用户制作的rootfs根文件系统镜像
    注:这里主要通过init_eaccess检查是否能访问到某个文件(如linuxrc),如果能访问到则表示已经通过populate_rootfs 释放到内核根目录/,则不会执行prepare_namespace,否则将执行prepare_namespace挂载用户根文件系统

3.populate_rootfs

此处我们主要以与内核打包在一起的initramfs为例进行说明
populate_rootfs的声明位于init/initramfs.c:

rootfs_initcall(populate_rootfs);

将initrd释放到rootfs,实际是将initrd下的目录和文件及链接在rootfs的根目录/下再创建一遍,之后把cpio initrd的文件内容拷贝过去。那这个动作是在什么时候发生的呢?就是在populatge_rootfs中

populate_rootfs
    |--unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start)
           |--while (!message && len)
                  decompress(buf, len, NULL, flush_buffer, NULL,...)//only once
                  write_buffer(buf, len)	

由于本文采用cpio initrd,调用unpack_to_rootfs对cpio initrd进行解包。unpack_to_rootfs最重要的是会调用write_buffer函数,它通过一个大循环对cpio initrd中打包进去的每一个文件进行处理。while循环首次会将cpio initrd的压缩文件进行解压缩。之后将通过write_buffer对解压缩后的cpio initrd进行处理。
write_buffer函数维护了一个状态机,不同的状态具有不同的处理函数
在这里插入图片描述
对每一个文件的处理将依次经过如下几种状态(以普通文件为例):

  • Start:文件处理开始
  • Collect:通过读取cpio initrd中每个文件的cpio头来收集文件信息
  • GotHeader:对每个文件的cpio头进行解析,并保存到全局变量
  • GotName:通过上步获取的cpio头信息获取文件访问属性,对文件进行不同处理,如对于普通文件则在当前进程的当前目录(current为init_task,current->fs->pwd为rootfs的/目录)下创建此文件
  • CopyFile:将initrd中文件的内容拷贝到新创建的文件
  • Reset:重置,以准备下一个文件的处理

经过如上的步骤就完成了cpio initrd释放到rootfs的“/”目录,之后就可以通过/init来访问为init程序,并为之创建进程,也就是1号进程了。

4.GotName的处理

下面以GotName为例,以cpio initrd下的init文件为例说明释放过程, GotName对应do_name处理函数:

static int __init do_name(void) 
{
    ......
    clean_path(collected, mode);                                                                                                                                                 
    if (S_ISREG(mode)) {//以常规文件为例                                                                                                                                                        
         int ml = maybe_link();                                                                                                                                               
         if (ml >= 0) {                                                                                                                                                       
                 int openflags = O_WRONLY|O_CREAT;                                                                                                                            
                 if (ml != 1)                                                                                                                                                 
                         openflags |= O_TRUNC;                                                                                                                                
                 wfile = filp_open(collected, openflags, mode);                                                                                                               
                 if (IS_ERR(wfile))                                                                                                                                           
                         return 0;                                                                                                                                            
                 wfile_pos = 0;                                                                                                                                               
                                                                                                                                                                              
                 vfs_fchown(wfile, uid, gid);                                                                                                                                 
                 vfs_fchmod(wfile, mode);                                                                                                                                     
                 if (body_len)                                                                                                                                                
                         vfs_truncate(&wfile->f_path, body_len);                                                                                                              
                 state = CopyFile;                                                                                                                                            
         }                                                                                                                                                                    
   }
   ......
}

由于init是一个普通文件因此会调用sys_open(collected, openflags, mode),对于init也就是:

sys_open("init", O_WRONLY|O_CREAT, mode)

sys_open系统调用如下:

sys_open
    \--do_sys_open
           \--do_filp_open
                  \--path_openat
                         |--path_init
                         |--link_path_walk
                         \--do_last->
                                \--lookup_open->
                                       |--lookup_dcache//创建dentry
                                       \--vfs_create//创建inode

lookup_dcache 为init创建dentry;vfs_create调用根inode->i_op即ramfs_file_inode_operations->create为init文件创建inode

参考文档

1. cpio格式的介绍

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值