一、目的

上文详细介绍了CPIO格式的initrd文件,本文从源代码角度分析加载并解析initrd文件的过程。

initrd文件和linux内核一般存储在磁盘空间中,在系统启动阶段由bootload负责把磁盘上的内核和initrd加载到指定的内存空间中;然后,再由内核读取和解析initrd文件,在VFS(目前只有rootfs的根目录)中新建目录、常规文件、符号链接文件以及特殊文件;这样VFS就从根目录"/"成长为一棵枝繁叶茂的大树了。

二、函数调用过程

initrd详细的加载过程在init/initramfs.c中实现的,为了更好的理解加载过程,我们给出了关键函数的调用关系图1。这里需要注意下,由于使用roofs_initcall()宏在initcallroofs段中注册了populate_rootfs()函数,因此在执行do_initcalls()函数时会隐示调用populate_rootfs()。

图1

三、initcall简介

linux在代码段中定义了一个特殊的段initcall,该段中存放的都是函数指针;linux初始化阶段调用do_initcalls()依次执行该段的函数。关于该段的详细信息可以参见vmlinux.lds.S链接脚本。

用户可以调用以下一组宏在initcall段中注册函数指针;initcall段分为initcall0-initcall7这8个等级,initcall0段的优先级最高,initcall7段的优先级最低,优先级高的段最先被执行;initcallrootfs段优先级介于5和6之间。

  1. #define __define_initcall(fn, id) \
  2. static initcall_t __initcall_##fn##id __used \
  3. __attribute__((__section__(".initcall" #id ".init"))) = fn
  1. #define early_initcall(fn) __define_initcall(fn, early)
  2.  
  3. #define pure_initcall(fn) __define_initcall(fn, 0)
  4.  
  5. #define core_initcall(fn) __define_initcall(fn, 1)
  6. #define core_initcall_sync(fn) __define_initcall(fn, 1s)
  7. #define postcore_initcall(fn) __define_initcall(fn, 2)
  8. #define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
  9. #define arch_initcall(fn) __define_initcall(fn, 3)
  10. #define arch_initcall_sync(fn) __define_initcall(fn, 3s)
  11. #define subsys_initcall(fn) __define_initcall(fn, 4)
  12. #define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
  13. #define fs_initcall(fn) __define_initcall(fn, 5)
  14. #define fs_initcall_sync(fn) __define_initcall(fn, 5s)
  15. #define rootfs_initcall(fn) __define_initcall(fn, rootfs)
  16. #define device_initcall(fn) __define_initcall(fn, 6)
  17. #define device_initcall_sync(fn) __define_initcall(fn, 6s)
  18. #define late_initcall(fn) __define_initcall(fn, 7)
  19. #define late_initcall_sync(fn) __define_initcall(fn, 7s)

用户使用不同优先级的initcall宏可以很方便的在linux代码中注册函数指针;将这些函数指针存储在相应的initcall段中;最终,由do_initcalls()按照优先级依次执行段中的函数,具体的代码实现如下:

  1. static initcall_t *initcall_levels[] __initdata = {
  2. __initcall0_start,
  3. __initcall1_start,
  4. __initcall2_start,
  5. __initcall3_start,
  6. __initcall4_start,
  7. __initcall5_start,
  8. __initcall6_start,
  9. __initcall7_start,
  10. __initcall_end,
  11. };
  12.  
  13. int __init_or_module do_one_initcall(initcall_t fn)
  14. {
  15. int ret;
  16. ret = fn();
  17. }
  18.  
  19. static void __init do_initcall_level(int level)
  20. {
  21. initcall_t *fn;
  22. ...
  23. for (fn = initcall_levels[level]; fn < initcall_levels[level+]; fn++)
  24. do_one_initcall(*fn);
  25. }
  26.  
  27. static void __init do_initcalls(void)
  28. {
  29. int level;
  30.  
  31. for (level = ; level < ARRAY_SIZE(initcall_levels) - ; level++)
  32. do_initcall_level(level);
  33. }

回到加载initrd这个话题中,在init/initram.c的最后使用rootfs_initcall宏注册了populate_rootfs()函数;基于以上分析,我们知道这里就是加载initrd文件的入口,下面就开始分析该函数的功能。

  1. rootfs_initcall(populate_rootfs);

四、加载initrd文件

系统启动阶段,bootload将initrd加载到内存起始地址为initrd_start,结束地址为initrd_end的内存中。

populate_rootfs()调用unpack_to_rootfs()从内存中读取并解析initrd文件;根据CPIO的格式我们知道initrd文件是由很多个段组成,且段中又是由文件头、文件名和文件体组成,因此该解析程序可以使用了状态机原理处理initrd文件。

解析程序定义了以下8种状态:Start(初始状态)、Collect(获取符号链接文件信息状态)、GotHeader(获取文件头信息状态)、SkipIt(跳过该段状态)、GotName(获取文件名并新建文件状态)、CopyFile(写文件状态)、GotSymlink(新建符号链接文件状态)、Reset(终止状态)。

  1. static __initdata int (*actions[])(void) = {
  2. [Start] = do_start,
  3. [Collect] = do_collect,
  4. [GotHeader] = do_header,
  5. [SkipIt] = do_skip,
  6. [GotName] = do_name,
  7. [CopyFile] = do_copy,
  8. [GotSymlink] = do_symlink,
  9. [Reset] = do_reset,
  10. };

为了直观理解initrd文件的解析过程,下面给出状态机跳转图2。

从图中可以看出将文件分为符号链接和非符号链接两种情况处理,这是因为符号链接文件是一种特殊的文件,只有第一个符号链接文件的inode存储的是真实数据,而其他符号链接文件inode中存储的是第一个符号链接文件的路径名,因此需要把第一个符号链接文件的路径名缓存起来,缓存的数据结构是hash表,所以在处理符号链接文件时多了一些hash表的操作,因此分为了符号链接文件和非符号链接文件这两种情况来处理。

initrd文件的详细解析过程如下:

1、S0:初始状态,初始化一些全局变量;

2、S1:获取符号链接文件的文件头和文件体;

3、S2:根据CPIO格式的定义,获取文件头信息;

4、S3:跳过当前CPIO格式的段,继续处理下一个段;

5、S4:获取文件名,并在VFS中新建文件;

6、S5:将文件内容写入到新建文件中;

7、S6:新建符号链接文件;

8、S7:处理完当前CPIO格式的段,继续一个段的处理。

从图中还可以看出,由于目录文件和特殊文件没有文件内容,因此跳过了S5状态,直接进入S3状态。

图2

五、总结

通过以上分析,程序就可以成功解析initrd文件,并使用sys_dir()、sys_open()、sys_mknod()、sys_symlink()等系统调用新建目录、常规文件、特殊文件和符号链接文件了。此时,VFS从只有根目录"/"成长为了一棵内容丰富的大树。

linux文件系统初始化过程(4)---加载initrd(中)的更多相关文章

  1. linux文件系统初始化过程(5)---加载initrd(下)

    一.目的 linux把文件分为常规文件.目录文件.软链接文件.硬链接文件.特殊文件(设备文件.管道文件.socket文件等)几种类型,分别对应不同的新建函数sys_open().sys_mkdir() ...

  2. linux文件系统初始化过程(3)---加载initrd(上)

    一.目的 本文主要讲述linux3.10文件系统初始化过程的第二阶段:加载initrd. initrd是一个临时文件系统,由bootload负责加载到内存中,里面包含了基本的可执行程序和驱动程序.在l ...

  3. linux文件系统初始化过程(1)---概述

    术语表: struct task:进程 struct mnt_namespace:命名空间 struct mount:挂载点 struct vfsmount:挂载项 struct file:文件 st ...

  4. linux文件系统初始化过程(6)---执行init程序

    一.目的 内核加载完initrd文件后,为挂载磁盘文件系统做好了必要的准备工作,包括挂载了sysfs.proc文件系统,加载了磁盘驱动程序驱动程序等.接下来,内核跳转到用户空间的init程序,由ini ...

  5. linux文件系统初始化过程(2)---挂载rootfs文件系统

    一.目的 本文主要讲述linux3.10文件系统初始化过程的第一阶段:挂载rootfs文件系统. rootfs是基于内存的文件系统,所有操作都在内存中完成:也没有实际的存储设备,所以不需要设备驱动程序 ...

  6. linux文件系统 - 初始化(二)

    加载initrd(上) 一.目的 本文主要讲述linux3.10文件系统初始化过程的第二阶段:加载initrd. initrd是一个临时文件系统,由bootload负责加载到内存中,里面包含了基本的可 ...

  7. linux文件系统 - 初始化(一)

    术语表: struct task:进程 struct mnt_namespace:命名空间 struct mount:挂载点 struct vfsmount:挂载项 struct file:文件 st ...

  8. Linux命令备忘录:mount用于加载文件系统到指定的加载点

    mount命令用于加载文件系统到指定的加载点.此命令的最常用于挂载cdrom,使我们可以访问cdrom中的数据,因为你将光盘插入cdrom中,Linux并不会自动挂载,必须使用Linux mount命 ...

  9. linux文件系统 - 初始化(三)

    执行init程序 一.目的 内核加载完initrd文件后,为挂载磁盘文件系统做好了必要的准备工作,包括挂载了sysfs.proc文件系统,加载了磁盘驱动程序驱动程序等.接下来,内核跳转到用户空间的in ...

随机推荐

  1. cumprod、prod函数

    1.prod函数 prod函数用于求矩阵元素的积,其调用格式如下. (1)B=prod(A):若A为向量,则返回所有元素的积:若A为矩阵,则返回各列所有元素的积. (2)B=prod(A,dim):返 ...

  2. 【转】localStorage使用总结

    原文地址:https://www.cnblogs.com/st-leslie/p/5617130.html 一.什么是localStorage.sessionStorage 在HTML5中,新加入了一 ...

  3. 必须掌握的MySQL优化指南

    当 MySQL 单表记录数过大时,增删改查性能都会急剧下降,本文会提供一些优化参考,大家可以参考以下步骤来优化. 单表优化 除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑.部 ...

  4. 如何卸载VS 2017之前版本比如VS 2013、VS2015、 VS vNext?

    前言 大学专业为软件工程,进入大学之后才知道这个专业需要用到笔记本,我的笔记本配置为I3,内存4个G,已经有大几年了,中间坏了修了一次一直用到现在,这个笔记本还是我哥打工过年回来身上仅有的三四千块钱所 ...

  5. c#, AOP动态代理实现动态权限控制(一)

    因最近工作需要一个动态的权限配置功能,具体实现逻辑是c#的动态代理功能,废话不多说,直接干货.需求: 用户分为管理员.普通用户 不同用户拥有不同功能权限 用户的权限可配置 新增功能时,不用修改权限配置 ...

  6. PS快速祛除脸上小雀斑

    首先我们要把图片放到PS软件中,然后在PS左侧工具栏中找到污点修复画笔工具(J), 配合着污点修复画笔中的修补工具一起使用,注意:模式要选择正常,属性栏中类型要选择内容识别. 下一步我们需要在图层上添 ...

  7. Kubernetes — 深入理解容器镜像

    而正如我前面所说的,Namespace 的作用是“隔离”,它让应用进程只能看到该 Namespace 内的“世界”:而 Cgroups 的作用是“限制”,它给这个“世界”围上了一圈看不见的墙.这么一折 ...

  8. JMeter配置好环境变量后无法启动---翻车笔记

    双击jmeter.bat出现下图情况 手欠了win7中配置 path无意中多删了变量 解决方法:在计算机-属性-高级系统设置-环境变量Path中添加 %SystemRoot%/system32;%Sy ...

  9. SQL Server没有足够的内存继续执行程序 (mscorlib)的解决办法

    在Microsoft SQL Server Management Studio 中执行较大的sql脚本时,会报没有足够的内存继续执行程序(mscorlib)的错误.如下图所示 解决方法: 使用sqlc ...

  10. Python可变参数*和**

    可变参数 在Python函数中,还可以定义可变参数.顾名思义,可变参数就是传入的参数个数是可变的,可以是1个.2个到任意个,还可以是0个. 我们以数学题为例子,给定一组数字a,b,c……,请计算a2 ...