深度探索Linux操作系统 —— 构建initramfs

news2024/11/19 13:31:16

在这里插入图片描述

系列文章目录


深度探索Linux操作系统 —— 编译过程分析
深度探索Linux操作系统 —— 构建工具链
深度探索Linux操作系统 —— 构建内核
深度探索Linux操作系统 —— 构建initramfs


文章目录

  • 系列文章目录
  • 前言
  • 一、为什么需要 initramfs
  • 二、initramfs原理探讨
  • 三、构建基本的initramfs
  • 四、将硬盘驱动编译为模块
    • 1、配置devtmpfs
    • 2、将硬盘控制器驱动配置为模块
    • 3、自动加载硬盘控制器驱动
      • (1)内核向用户空间发送事件
      • (2)udev加载驱动和建立设备节点


前言

    一般而言,桌面、服务器等通用系统都使用 initramfs。部分嵌入式系统中,也会使用 initramfs,甚至有的使用 initramfs 作为最终的根文件系统。那么什么是 initramfs 呢?很难用一句话将 initramfs 的作用描述清楚,或许可以将 initramfs 定位为内核通往根文件系统的桥梁。


一、为什么需要 initramfs

    鸡和蛋的问题:内核要加载这些模块或者运行这些程序才能正确识别根文件系统所在的设备,但是保存这些模块或者程序的根文件系统又存储在这些设备上。

    内核开发者们设计了 initramfs 机制。initramfs 是一个临时的文件系统,其中包含了必要的设备如硬盘、网卡、文件系统等的驱动以及加载驱动的工具及其运行环境,比如基本的 C 库,动态库的链接加载器等等。同时,那些处理根文件系统在 RAID 、网络设备上的程序也存放在 initramfs 中。由第三方程序(如 Bootloader )负责将 initramfs 从硬盘装载进内存。以驱动硬盘为例,内核就不必再从硬盘,而是从已经加载到内存的 initramfs 中获取硬盘控制器等相关驱动了,继而可以驱动硬盘,访问硬盘上的根文件系统,从而解决了前面提到的鸡和蛋的矛盾。

    在初始化的最后,内核运行 initramfs 中的 init 程序,该程序将探测硬件设备、加载驱动,挂载真正的文件系统,执行文件系统上的 /sbin/init,进而切换到真正的用户空间。真正的文件系统挂载后,initramfs 即完成了使命,其占用的内存也会被释放。

二、initramfs原理探讨

    在 2.4 以及更早版本的内核中,内核使用的是 initrdinitrd 是基于ramdisk技术的,而 ramdisk 就是一个基于内存的块设备,因此 initrd 也具有块设备的一切属性。比如 initrd 容量是固定的,一旦创建 initrd 时设定了一个大小,就不能再进行动态调整。

    ramfsramdisk 有着本质的区别,ramdisk 本质上是基于内存的一个块设备,而 ramfs 是基于缓存的一个文件系统。因此,ramfs 去除了前述块设备的一些限制。比如,ramfs 根据其中包含的文件大小可自由伸缩;增加文件时,自动分配内存;删除文件时,自动释放内存。更重要的是,ramfs 是基于已有的缓存机制,因此不必再像 ramdisk 那样需要和缓存之间进行多余的复制一环。

    从 2.6 开始,内核开发人员基于 ramfs 开发了 initramfs 替代 initrd

    当 2.6 版本的内核引导时,在挂载真正的根文件系统之前,首先将挂载一个名为 rootfs 的文件系统,并将 rootfs 的根作为虚拟文件系统目录树的总根。那么为什么要使用 rootfs 这么一个中间过程呢?原因之一还是为了解决鸡和蛋的问题。内核需要根文件系统上的驱动以及程序来驱动和挂载根文件系统,但是这些驱动和程序有可能没有编译进内核,而在根文件系统上。如果不借助第三方,内核是没有办法挂载真正的根文件系统的。而 rootfs 虽然名称为 rootfs ,但是并不是什么新的文件系统,事实上,其就是一个 ramfs,只不过换了一个名称。换句话说,rootfs 是在内存中的,内核不需要特殊的驱动就可以挂载 rootfs,所以内核使用 rootfs 作为一个过渡的桥梁。

    在挂载了 rootfs 后,内核将 Bootloader 加载到内存中的 initramfs 中打包的文件解压到 rootfs 中,而这些文件中包含了驱动以及挂载真正的根文件系统的工具,内核通过加载这些驱动、使用这些工具,实现了挂载真正的根文件系统。此后,rootfs 也完成了历史使命,被真正的根文件系统覆盖(overmount)。但是 rootfs 作为虚拟文件系统目录树的总根,并不能被卸载。但是这没有关系,前面我们已经谈到了,rootfs 基于 ramfs,删除其中的文件即可释放其占用的空间。

三、构建基本的initramfs

# 1
mkdir initramfs
cd initramfs

# 2
# /vita/initramfs/init
#!/bin/bash
echo "Hello Linux!"
exec /bin/bash

# 3
mkdir bin
cp ../sysroot/bin/bash bin/

bash 依赖

vita@baisheng:/vita/initramfs$ ldd bin/bash
	libdl.so.2 => /vita/sysroot/lib/libdl.so.2
	libgcc_s.so.1 =>/vita/cross-tool/i686-none-linux-gnu/lib/libgcc_s.so.1
	libc.so.6 => /vita/sysroot/lib/libc.so.6

vita@baisheng:/vita/initramfs$ ldd lib/libdl.so.2
	libc.so.6 => /vita/sysroot/lib/libc.so.6
	ld-linux.so.2 => /vita/sysroot/lib/ld-linux.so.2
	
vita@baisheng:/vita/initramfs$ ldd lib/libc.so.6
	ld-linux.so.2 => /vita/sysroot/lib/ld-linux.so.2
	
vita@baisheng:/vita/initramfs$ ldd lib/ld-linux.so.2

vita@baisheng:/vita/initramfs$ ldd lib/libgcc_s.so.1
	libc.so.6 => /vita/sysroot/lib/libc.so.6

    bash 依赖于libc、libdl 以及 libgcc_s.so.1,因此,我们需要在 initramfs 中安装这三个库,以及安装加载动态库的动态加载/链接器。
    根据依赖关系可见,libdl 依赖 libc 和动态链接器,libgcc 只依赖 libclibc 仅依赖动态链接器,而动态链接器不依赖其他任何库,因此,我们不再需要安装其他库到 initramfs 中。

四、将硬盘驱动编译为模块

1、配置devtmpfs

    Linux2.6.18 开始采用 udev/dev 目录使用了基于内存的文件系统 tmpfs 管理设备文件。

    2009 年初,开发人员又提出了 devtmpfs ,并在同年年底被 Linux 2.6.32 正式收录。内核引导时,devtmpfs 将所有注册的设备在 devtmpfs 中建立相应的设备文件,一旦进入用户空间,在启动 udev 前,就可以将 devtmpfs 挂载到 /dev 目录下。

    也就是说,在启动 udev 前,devtmpfs 中已经建立了初步的设备文件,一般启动程序不必再等待 udev 建立设备节点,甚至在某些嵌入式系统上,不再需要 udev 创建设备节点,因为这个基本的 /dev 已经足够,从而缩短了系统的启动时间。同 rootfs 类似,devtmpfs 也不是新设计的文件系统,如果内核配置支持 tmpfs ,那么其就是 tmpfs;否则,devtmpfs 就是 ramfs,只不过换了一个名字而已。

2、将硬盘控制器驱动配置为模块

    接下来重新编译内核和模块。内核和模块可以使用单独的命令分开编译,也可以使用一条 make 命令同时编译内核和模块。编译完成后,将模块暂时安装在 “/vita/sysroot/lib/modules” 目录下。

vita@baisheng:/vita/build/linux-3.7.4$ make bzImage
vita@baisheng:/vita/build/linux-3.7.4$ make modules
vita@baisheng:/vita/build/linux-3.7.4$ make \
		INSTALL_MOD_PATH=$SYSROOT modules_install

最终安装的硬盘控制器驱动模块包括:

vita@baisheng:/vita$ ls sysroot/lib/modules/3.7.4/kernel/drivers/ata/

ahci.ko ata_piix.ko libahci.ko

我们将其复制到 initramfs 中。

3、自动加载硬盘控制器驱动

    从 2.6 版内核开始,Linux 采用 udev 管理驱动模块的加载以及设备节点的管理。每当内核发现新的设备,便通过 NETLINK 向用户空间发送新设备事件,该事件中记录了设备的相关信息。用户空间的 udev 服务进程收到内核事件后,根据事件中携带的信息,首先判断该设备的驱动是否已经加载,如果没有,则加载驱动。驱动加载后,内核会再次向用户空间报告发现新设备事件,这时设备已经成功驱动了,并且主次设备号等信息也已经准备好了,udev 收到事件后,或者为设备建立节点,或者执行某些特定的操作。整个过程如图4-19所示。

在这里插入图片描述

(1)内核向用户空间发送事件

    PC 机上的硬盘控制器,无论是 IDE 接口的,还是 SATA 接口的,一般都是通过 PCI 总线连接到计算机上的。内核在引导时,PCI 子系统将进行初始化,枚举总线上的设备,并尝试为设备匹配驱动;然后将收集到的设备相关信息组织为 uevent 事件;接着调用 kobject_uevent ,通过 NETLINK 将组织好的 uevent 发送到用户空间,通知 udev 有新设备了。简单地讲,内核的工作就是探测并收集设备信息,将其包装到 uevent 事件中,然后发送到用户空间。

    事实上,无论是发现新的设备,还是有新的驱动载入,抑或是用户向 sysfs 中的 uevent 写入字符串,内核都将调用函数 kobject_uevent 向用户空间发送事件。

    结构体 kobj_uevent_env 用来保存收集到的设备相关信息,所以在函数 kobject_uevent_env 中,首先为 kobj_uevent_env 申请了一块内存,即变量 env 指向的内存,用来临时存放准备发送到用户空间的设备相关信息。

    然后向该内存中添加了三个默认的变量,包括 ACTIONDEVPATHSUBSYSTEM 。 其中 ACTION 指的是热插拔的动作,如 “add”,“remove”,“change” 等。DEVPATH 指的是设备在 sysfs 文件系统中注册的设备路径,比如笔者的硬盘 sdaDEVPATH 是 “/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda” 。SUBSYSTEM 一般是指设备所在的总线,比如笔者的硬盘是挂在 PCI 总线上的,因此该变量的值是 “pci”。

    pci_uevent 又向 uevent 中追加了 pci class 、 vendor id 、 device id 以及 MODALIAS 等变量,其中 MODALIAS 需要重点关注,其是由设备所在总线、vendor ID、device ID 等相关参数连接而成的一个字符串。在接下来的章节中,读者将看到,用户空间的 udev 恰恰就是根据这个变量为设备匹配驱动模块的。

    除了总线外,如果硬盘控制器所属的 class 或者 type 也需要继续向 uevent 中追加变量,则继续调用硬盘控制器所属的 class 或者 type 中的相应的函数,这里不再继续分析了。最终,内核向用户空间发送的 uevent 事件包含的大致的内容如下,其中不同变量之间使用 “\0” 进行分隔。

在这里插入图片描述

(2)udev加载驱动和建立设备节点

    udev 是用户空间动态管理设备的机制,包括加载驱动、管理设备节点等。udev 机制的核心是其服务进程 udevd。当启动过程进入用户空间阶段后,udevd 将被启动。udevd 启动后,首先读取并分析所有的规则文件,并将其缓存在内存中。一般情况下,系统默认的规则文件存在 /lib/udev/rules.d 目录下,用户自定义的规则存放在 /etc/udev/rules.d 目录下。每当动态地增加、删除或者改变某个规则文件时,udevd 将更新其缓存在内存中的规则。然后,udevd 通过 NETLINK 协议,监听并处理来自内核的 uevent 事件。每当 udevd 收到一个内核的 uevent ,udevd 均创建一个单独的子进程处理 uevent 。

    对于每个内核报告的 uevent,udevd 根据 uevent 中的变量逐个匹配规则。规则文件通常以数字开头,数字小的先进行匹配。若每个规则文件中包含若干个规则,同一规则不允许断行,每个规则至少包含一个 key-value 对,每个 key-value 对之间使用逗号分隔。可以将规则理解为由匹配条件和赋值动作组成,当所有的匹配条件都满足后,赋值动作就会发生。规则中可以加载驱动模块;规定如何给设备接点命名、建立符号连接;设备连接和断开时分别执行指定的程序等。

    前面我们看到内核在发现新设备时会将设备的一些信息通过 NETLINK 发送到用户空间,udev 接收到事件后,如果发现设备尚未被驱动,将尝试加载驱动模块。那么 udev 如何确定设备对应的驱动模块呢?一般而言,根据设备的 vender IDdevice ID 就可以标识一类设备,当然有的也需要根据 subvendor IDsubdevice ID 进一步细分。而在驱动代码中,恰恰使用这些设备信息明确声明了其可以支持的设备。以驱动 AHCI 模式的 SATA 硬盘控制器驱动为例:

   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1298246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能优化算法应用:基于粒子群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于粒子群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于粒子群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.粒子群算法4.实验参数设定5.算法结果6.参考文…

反汇编语言区分函数和运算符

在汇编语言中,函数和运算符可以通过一些特定的指令和约定来区分。 函数: 函数通常由一系列指令组成,用于执行特定的任务或操作。函数通常具有入口点和出口点,分别表示函数的开始和结束位置。函数通常包含参数传递、局部变量的分配…

黑豹程序员-EasyExcel实现导出

需求 将业务数据导出到excel中,老牌的可以选择POI,也有个新的选择EasyExcel。 有个小坑,客户要求样式比较美观,数字列要求千位符,保留2位小数。 可以用代码实现但非常繁琐,用模板就特别方便,模…

通过仿真理解完整的阵列信号噪声模型

概要 噪声对无线电设备的信号接收会造成影响,是通信、雷达、导航、遥感等工程应用领域中的关键考虑因素。通常认为阵列合成能够提升信噪比,但忽略了这一论断的前提,即不同通道引入的噪声互不相关。但实际应用中,接收的噪声不仅仅包含信道引入的不相关噪声,还包含从外界环…

智能优化算法应用:基于鲸鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于鲸鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于鲸鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鲸鱼算法4.实验参数设定5.算法结果6.参考文献7.MA…

React基础语法整理

安装: yarn create react-app reatc-lesson --template typescript yarn create 创建一个react-app的应用 项目名称 typescript 的模板react-app 官方地址 https://create-react-app.bootcss.com/docs/adding-typescriptreact 语法文档 https://zh-hans.react.dev…

【C++】输入输出流 ⑤ ( cin 输入流对象 | cin.ignore() 函数 | cin.peek() 函数 | cin.putback() 函数 )

文章目录 一、cin.ignore() 函数1、cin.ignore() 函数简介2、cin.ignore() 函数原型3、代码示例 - cin.ignore() 函数 二、cin.peek() 函数1、cin.peek() 函数简介2、代码示例 - cin.peek() 三、cin.putback() 函数1、cin.putback() 函数简介2、代码示例 - cin.putback() 一、c…

MEME成风,为何比特币生态无法复刻以太坊生态的多样玩法?

铭文市场火了之后,很多人对 BTC L2 投入了过多的期许,认为 BTC 2 层会像以太坊 layer2 一样辉煌? 然而事实是,比特币生态的「成功」可能很长时间会停滞在「资产发行」叙事阶段,要复刻以太坊的生态多样玩法&#xff0c…

thinkphp6入门(12)-- 一对一关联模型

定义一对一关联,例如,一个用户都有一个个人资料。 一、两表 1.用户表:user 2.工作经验表:work_experience user表的id关联work_experience表的user_id。 注意看,user_id1的有2条工作经验 二、数据模型 主表模型:…

低多边形3D建模动画风格纹理贴图

在线工具推荐: 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时,有几种不同的风格&#xf…

掌握PyTorch数据预处理(一):让模型表现更上一层楼!!!

引言 在PyTorch中,数据预处理是模型训练过程中不可或缺的一环。通过精心优化数据,我们能够确保模型在训练时能够更高效地学习,从而在实际应用中达到更好的性能。今天,我们将深入探讨一些常用的PyTorch数据预处理技巧,…

【交换排序 简单选择排序 堆排序 归并排序】

文章目录 交换排序简单选择排序堆排序归并排序 交换排序 冒泡排序的算法分析: 冒泡排序最好的时间复杂度是O(n)冒泡排序最好的时间复杂度是O(n平方)冒泡排序平均时间复杂度为O(n的平方)冒泡排…

1-6、编程语言排行榜

语雀原文链接 https://www.tiobe.com/tiobe-index/

基于Vue框架的电子商城购物平台小程序的设计与开发

基于JavaWebSSMVue电子商城购物平台小程序系统的设计和实现 源码获取入口KaiTi 报告/Ren务书Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 KaiTi 报告/Ren务书 一、选题的目的和意义 自从微信推出了微信小程序…

如何自动筛选高质量的指令微调数据喂给大模型?

前言 大家都知道在大模型时代,数据有多么重要,质量多高都不过分!甚至直接决定着最终的效果。 尤其做SFT,模型框架基本不用改(事实上也改不动),如何做一份符合自己场景高质量的SFT数据成了唯一…

C++中STL的容器vector

文章目录 什么是vectorvector与普通顺序表不同的点 vector的成员函数operatoroperator[]begin与end与iteratorsize()capacityresizeemptyreservepush_backpop_backinserteraseswapclear成员变量 总结 什么是vector vector:是数据结构里面的顺序表,开辟一…

【MATLAB】基于EMD分解的信号去噪算法(基础版)

代码操作 【MATLAB】基于EMD分解的信号去噪算法(基础版) 代码的主要内容 基于EMD(经验模态分解)的信号去噪算法通常可以结合相关系数、信号的熵值或者方差贡献率来完成去噪处理。这些指标可以用于确定阈值,从而对信号…

WPF仿网易云搭建笔记(2):组件化开发

文章目录 前言专栏和Gitee仓库依赖属性实战:缩小,全屏,关闭按钮依赖属性操作封装主窗口传递this本身给TitleView标题控件主要代码MainWindow.xmalMainWindow.cs依赖属性方法封装TitleView.csTitleViewModelTitleViewModel实现效果 前言 这次…

FreeRTOS的内存管理方法(超详细)

内存管理 我们知道每次创建任务、队列、互斥锁、软件定时器、信号量或事件组时,RTOS 内核都需要 RAM , RAM 可以从 RTOS API 对象创建函数内的 RTOS 堆自动动态分配, 或者由应用程序编写者提供。 如果 RTOS 对象是动态创建的,那么…

Mac虚拟机CrossOver23破解版下载和许可证下载

CrossOver Mac Mac 和 Windows 系统之间的兼容工具。使 Mac 操作系统的用户可以运行 Windows 系统的应用,从办公软件、实用工具、游戏到设计软件, 您都可以在 Mac 程序和 Windows 程序之间随意切换。 系统要求 运行macOS的基于Intel或Apple Silicon 的…