对GRUB和initramfs的小探究
竞赛时对操作系统启动过程产生了些疑问,于是问题导向地浅浅探究了下GRUB和initramfs相关机制,相关笔记先放在这里了。
内核启动流程
在传统的BIOS系统中,计算机具体的启动流程如下:
- 电源启动:当计算机的电源打开时,电源供电给计算机的硬件设备。
- BIOS自检:计算机的BIOS固件会自检硬件设备,包括RAM、处理器、硬盘等,以确保它们正常工作。
- 引导设备选择:BIOS会根据预先定义的启动顺序(通常是硬盘、光驱、USB等)选择一个启动设备。
- MBR(Master Boot Record)加载:如果选择的启动设备是硬盘,BIOS会加载该硬盘的MBR,其中包含了引导加载程序。
- GRUB加载:MBR中的引导加载程序通常是GRUB(或其他引导加载程序)。GRUB会被加载到计算机的内存中,并开始执行。
- GRUB菜单:GRUB会显示一个菜单,列出可供选择的操作系统或内核。
- 操作系统加载:用户选择操作系统后,GRUB会加载相应的操作系统或内核,并将控制权交给它。
在本次内核编译配置过程中,最主要探究的是文件系统的装载过程,也即介于6-7之间的部分。
概述
文件系统在启动流程中的发展历程可以分为以下三个部分:
-
GRUB文件系统
由 GRUB 自身通过 BIOS 提供的服务加载
-
initramfs
由GRUB加载,用于挂载真正的文件系统
-
真正的根文件系统
下面,将介绍1和2两个流程。
GRUB
GRUB(GNU GRand Unified Bootloader)是一种常用的引导加载程序,用于在计算机启动时加载操作系统。
GRUB的主要功能是在计算机启动时提供一个菜单,让用户选择要启动的操作系统或内核。它支持多个操作系统,包括各种版本的Linux、Windows、BSD等。通过GRUB,用户可以在多个操作系统之间轻松切换。
除了操作系统选择,GRUB还提供了一些高级功能,例如引导参数的设置、内存检测、系统恢复等。它还支持在启动过程中加载内核模块和初始化RAM磁盘映像(initrd或initramfs)。
GRUB具有高度可配置性,允许用户自定义引导菜单、设置默认启动项、编辑内核参数等。它还支持引导加载程序间的链式引导,可以引导其他引导加载程序,如Windows的NTLDR。
GRUB的基本作用流程为:
- BIOS加载MBR,MBR加载GRUB,开始执行GRUB程序
- GRUB程序会读取
grub.cfg
配置文件 - GRUB程序依据配置文件,进行内核的加载、根文件系统的挂载等操作,最后将主导权转交给内核
grub.cfg
内核启动时,GRUB程序会读取/boot/grub/
目录下的GRUB配置文件grub.cfg
,其中记录了所有GRUB菜单可供选择的内核选项(menuentry)及其对应的启动依赖参数。以6.4.0内核选项为例:
# menuentry标识着GRUB菜单中的一个内核选项
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-XXX' {recordfail # 记录上次启动是否失败,用于处理启动失败的情况load_video # 加载视频驱动模块,用于在启动过程中显示图形界面gfxmode $linux_gfx_mode # 设置图形模式insmod gzio # 加载gzio模块,提供对GZIP压缩和解压缩功能的支持# 如果是在Xen虚拟化平台上,则加载xzio和lzopio模块if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_gpt # 加载part_gpt模块,支持GUID分区表(GPT)insmod ext2 # 加载ext2模块,支持ext2文件系统# 设置文件系统的根分区set root='hd0,gpt3' if [ x$feature_platform_search_hint = xy ]; thensearch --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt3 --hint-efi=hd0,gpt3 --hint-baremetal=ahci0,gpt3 XXXelsesearch --no-floppy --fs-uuid --set=root XXXfilinux /boot/vmlinuz-6.4.0-rc3+ root=UUID=XXX ro text # 指定内核映像的路径和启动参数initrd /boot/initrd.img-6.4.0-rc3+ # 指定initramfs映像的路径
}
可以看到,grub.cfg
主要记录了一些该内核启动需要的依赖module,以及内核映像和initramfs映像的路径。
menuentry的代码中,有以下几个要点值得注意:
-
insmod gzio
由于加载gzio模块,提供对GZIP压缩和解压缩功能的支持。
看到这里我第一反应是觉得有点割裂,为啥这看着比较无关紧要的解压缩功能要在内核启动之前就需要有呢?于是我想起来在配置内核时,有一个选项是这样的:
在配置选项中,我们选择了对initramfs的支持,并且勾选了
Support initial ramdisk/ramfs compressed using gzip
,也即在编译时通过gzip压缩initramfs的大小以节省空间。所以说,我们在内核启动之前,持有的initramfs处于被压缩的状态。故而,我们自然需要在内核启动之前安装gzio模块,从而支持之后对initramfs的解压缩了。
-
insmod ext2
这句代码说明,GRUB的临时文件系统为ext2类型,这句代码事实上是在安装GRUB建立临时文件的必要依赖包,从而GRUB程序之后才能建立其临时文件系统、从/boot/initrd.img获取initramfs映像。
-
linux /boot/vmlinuz-6.4.0-rc3+ root=UUID=XXX ro text
指定了启动参数,也即将根文件系统以只读(
ro
)的方式挂载在root=UUID=XXX
对应的块设备上,并且默认以text
方式(也即非图形化的Shell界面)启动内核。此处的启动参数可在下一个部分介绍的
grub
文件中个性化。
grub.cfg的生成与修改
实际运用中,很多时候需要对启动参数进行一些修改。下面介绍两种修改grub.cfg
的方法。
/etc/default/grub
可以看到,grub.cfg
其实格式较为固定(也即由一系列内容也比较相似的menuentry构成)。因而,实际上我们是通过grub.d
生成grub.cfg
的(6.S081实验中事实上也涉及了这一点),而/etc/default/grub
则是GRUB程序以及grub.cfg
生成的配置文件。下面介绍下该文件主要有哪些配置选项。
# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
# info -f grub -n 'Simple configuration'# 开机时GRUB界面的持续时间,此处设置为30s
GRUB_TIMEOUT=30
GRUB_CMDLINE_LINUX=""# 不使用图形化界面
#GRUB_TERMINAL=console
# 图形化界面的大小
#GRUB_GFXMODE=640x480
# 不使用UUID
#GRUB_DISABLE_LINUX_UUID=true# 隐藏recovery mode
#GRUB_DISABLE_RECOVERY="true"
重点看下这几个参数:
-
GRUB_CMDLINE_LINUX
表示最终生成的grub.cfg中的每一个menuentry中的linux那一行需要附加什么参数。
例如说,如果设置为:
# 表示initramfs在挂载真正的根文件系统之前,需要等待120s,用于防止磁盘没准备好导致的挂载失败 GRUB_CMDLINE_LINUX="rootdelay=120"
那么,最终在menuentry中的启动参数就为:
linux /boot/vmlinuz-6.4.0-rc3+ root=UUID=XXX ro rootdelay=120 text
其他一些常见的选项:
# 直接以路径来标识块设备而非使用UUID。此为old option,建议尽量使用UUID GRUB_CMDLINE_LINUX="root=/dev/sda3" # 标明init进程(启动后第一个进程)的具体路径。此处指明为`/bin/sh` GRUB_CMDLINE_LINUX="init=/bin/sh"
-
GRUB_DEFAULT
参考 可以用来指定重启时的内核选项。如
GRUB_DEFAULT="1> 0"
表示选择第一个菜单界面的第2栏(Advanced for Ubuntu)和第二个菜单的第1个内核。
在修改完grub
文件之后,我们需要执行sudo update-grub
,来重新生成grub.cfg
文件供下次启动使用。
在GRUB界面直接修改
我们可以在GRUB界面选中所需内核,按下e键:
然后就可以对启动参数进行修改,^X退出。
值得注意的是,此修改仅对本次启动有效。如果需要长期修改,建议还是通过第一种方法去修改。
initramfs
GRUB程序会通过initrd.img
启动initramfs,从而进行真正的根文件系统挂载。
initrd.img是一个Linux系统中的初始化内存盘(initial RAM disk)的映像文件。它是一个压缩的文件系统映像,通常在引导过程中加载到内存中,并提供了一种临时的根文件系统,以便在正式的根文件系统(通常位于硬盘上)可用之前提供必要的功能和模块。
我们可以通过unmkinitramfs /boot/initrd.img-6.4.0-rc3+ /tmp/initrd/
命令解压initrd,探究里面到底有什么玩意。
├── bin -> usr/bin
├── conf
├── etc
├── init
├── lib -> usr/lib
├── lib32 -> usr/lib32
├── lib64 -> usr/lib64
├── libx32 -> usr/libx32
├── run
├── sbin -> usr/sbin
├── scripts
├── usr
└── var
init
可以看到,这实际上就是一个小型的文件系统,也即initramfs。它有自己的built-in Shell(BusyBox):
有一些较少的Shell命令(bin和sbin目录下),以及用来挂载真正的根文件系统的代码逻辑(存储在scripts目录下)。【我猜】在正常情况下,系统会执行scripts下的脚本代码挂载真正的文件系统。当挂载出现异常时,系统就会将控制权交给initramfs内置的Shell BusyBox,由用户自己探究出了什么问题。
我们接下来可以追踪下initramfs的script目录下的文件系统挂载流程。
挂载真正文件系统的主要函数为local_mount_root
:
# 仅展示主要流程代码
local_mount_root()
{# 预处理,获取参数等(也即上面grub.cfg配置的root=UUID)local_topif [ -z "${ROOT}" ]; thenpanic "No root device specified. Boot arguments must include a root= parameter."fi# 根据UUID获取对应的块设备local_device_setup "${ROOT}" "root file system"ROOT="${DEV}"# 挂载前的预处理local_premount# 挂载mount ${roflag} ${FSTYPE:+-t "${FSTYPE}"} ${ROOTFLAGS} "${ROOT}" "${rootmnt?}"
}
由于研究这个是错误驱动(乐),因而我只主要看了下local_device_setup
:
# $1=device ID to mount设备ID
# $2=optionname (for root and etc)要挂载的是什么玩意,此处应为root file system
# $3=panic if device is missing (true or false, default: true)
# Sets $DEV to the resolved device node $DEV是最终获取到的块设备
local_device_setup()
{local dev_id="$1"local name="$2"local may_panic="${3:-true}"local real_devlocal time_elapsedlocal count# 获取grub.cfg的rootdelay参数的设备等待时间。如果没有该参数,默认是30秒local slumber=30if [ "${ROOTDELAY:-0}" -gt $slumber ]; thenslumber=$ROOTDELAYfi# 等待设备case "$dev_id" inUUID=*|LABEL=*|PARTUUID=*|/dev/*)FSTYPE=$( wait-for-root "$dev_id" "$slumber" );;*)wait_for_udev 10;;esac# 等待结束了。如果条件为真,说明还是获取不到对应的设备,那就只能说明这个设备死了# 所以我们就得把问题告诉用户,让用户自己解决,并且进入BusyBox Shell# We've given up, but we'll let the user fix matters if they canwhile ! real_dev=$(resolve_device "${dev_id}") ||! get_fstype "${real_dev}" >/dev/null; doif ! $may_panic; thenecho "Gave up waiting for ${name}"return 1fiecho "Gave up waiting for ${name} device. Common problems:"echo " - Boot args (cat /proc/cmdline)"echo " - Check rootdelay= (did the system wait long enough?)"if [ "${name}" = root ]; thenecho " - Check root= (did the system wait for the right device?)"fiecho " - Missing modules (cat /proc/modules; ls /dev)"panic "ALERT! ${dev_id} does not exist. Dropping to a shell!"doneDEV="${real_dev}"
}
可以看到,这里如果进入错误状态,最终就是这样的效果2333:
相关文章:

对GRUB和initramfs的小探究
竞赛时对操作系统启动过程产生了些疑问,于是问题导向地浅浅探究了下GRUB和initramfs相关机制,相关笔记先放在这里了。 内核启动流程 在传统的BIOS系统中,计算机具体的启动流程如下: 电源启动:当计算机的电源打开时&…...

springboo单机多线程高并发防止重复消费的redis方案
springboo单机多线程高并发防止重复消费的redis方案 仅提供方案与测试。 想法:第一次收到userCode时,检查是否在redis中有,如果有,就表明已经消费了,返回抢单失败;否则,就去消费,顺…...
Java架构师内功数据库
目录 1 导学2 数据库基本概念2.1 数据库系统2.2 三级模式-两级映像2.3 数据库设计2.4 数据模型2.4.1 E-R模型2.4.2 关系模型2.5 关系代数3 规范化和并发控制3.1 函数依赖3.2 键与约束3.3 范式3.3.1 第一范式1NF3.3.2 第二范式3.3.3 第三范式3.4 模式分解3.5 并发控制3.6 封锁协…...
踩着节日的小尾巴
节日快乐...

UG\NX二次开发 设置视图中心 UF_VIEW_set_center
文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 a1794902437 订阅本专栏,非常感谢。 简介 UG\NX二次开发 设置视图中心 UF_VIEW_set_center。如果视图NULL_TAG,则使用工作视图。 效果 代码 #include &qu…...
leetcode做题笔记201. 数字范围按位与
给你两个整数 left 和 right ,表示区间 [left, right] ,返回此区间内所有数字 按位与 的结果(包含 left 、right 端点)。 示例 1: 输入:left 5, right 7 输出:4示例 2: 输入&…...

游戏盾如何有效防护DDoS
从进入计算机时代以来,DDoS攻击一直是网络世界中的一大威胁,让无数服务陷入瘫痪。这种攻击的原理非常简单:攻击者使用大量的僵尸主机或蠕虫病毒,向目标服务器发送海量请求,迅速耗尽服务器的资源,使其无法继…...
JavaScript中的类型转换
将值从一种类型转换为另一种类型,a -> ‘a’ ,称为类型转换。转换分为两种,一种显式的,一种隐式的,隐式的往往也是强制类型转换。强制类型转换总是返回标量基本类型,不会返回对象和函数。 如何区分? 类型…...

01-JVM 内存结构
JVM 内存结构 Java 虚拟机的内存空间分为 5 个部分: 程序计数器Java 虚拟机栈本地方法栈堆方法区 JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过…...

树与二叉树(考研版)
文章目录 树与二叉树树的基本概念结点、树属性的描述树的性质 二叉树的概念二叉树的性质二叉树的构建二叉树的遍历先序遍历中序遍历后序遍历层次遍历 递归算法和非递归算法的转换源代码 线索二叉树二叉树的线索化线索二叉树 找前驱/后继 树和森林树的存储 树与二叉树的应用哈夫…...
前端车牌键盘组件
父组件 // 粘贴回去后格式化一下<div class"input-plate-wrap"><div v-for"(item, index) in keyBoard.kbLenght" :key"index"><divclass"plate-item"v-if"index ! keyBoard.kbLenght - 1":class"{ ac…...

什么是脚本文件,脚本的执行,脚本格式等
1.脚本文件是什么? 脚本文件是包含一系列计算机命令的文本文件,通常用于自动化任务、自定义功能或执行特定操作。这些命令通常按照一定的编程语法和语义规则编写,以便计算机能够逐行解释和执行它们。脚本文件通常包含了一组操作,…...

react 实战- 玩转 react 全家桶(进阶)学习
一个命令是怎么运行起来的? Shell运行一个命令,实际上是运行一个脚本 环境变量 装了node以后,node的路径,就被注册到了环境变量里. 一个js的东西,可以注册? bin Webpack配置 构建 import A from A , const Arequire(A) 为什么可以这么写?为哈都行?本质上,是构建工…...
【Python】取火柴小游戏(八什博弈)
# 火柴游戏:Python编程示例 当我们想要玩一个简单而有趣的游戏,同时又想锻炼自己的编程技能时,一个经典的选择就是火柴游戏。这个游戏的规则很简单:有一堆火柴,每次可以拿走1到6根,两名玩家轮流取火柴&…...

【Redis安装】Ubuntu和Centos
此处安装的是 Redis5 在 Ubuntu 系统上 切换到 root 用户下,su 命令切换使用 apt 可以搜索 redis 相关软件包 apt search redis使用 apt 命令安装 redis apt install redis手动修改配置文件 redis.conf cd /etc/redis/ vim redis.conf修改以下两处 重启服务器 …...

【Java】ArrayList集合使用
ArrayList集合常见方法 方法名称说明public boolean add(E e)将元素插入到指定位置的arraylist中,返回值:返回boolean类型public E remove(int index)删除 arraylist里的单个元素,返回值:返回删除之前的元素public E set(int inde…...

【proteus】8086仿真/汇编:创建项目并添加汇编代码文件
1.创建好新项目 2.点击source code 弹出VSM 3. 4.注意两个都不勾选 可以看到schematic有原理图出现 5. 再次点击source code 6.project/project settings,取消勾选embed 7. add 8.输入文件名保存后: 注意:proteus不用写dos的相关语句 。...

如何给Github上的开源项目提交PR?
前言 对于一个热爱开源的程序员而言,学会给GitHub上的开源项目提交PR这是迈出开源的第一步。今天我们就来说说如何向GitHub的开源项目提交PR,当然你提交的PR可以是一个项目的需求迭代、也可以是一个Bug修复、再或者是一些内容文本翻译等等,并…...
【Java】小计 TCP UDP的区别
面向连接 TCP面向连接,需要连接,而UDP不需要建立连接 可靠性 TCP协议通过确认应答、连接管理、流量控制、拥塞控制来确保可靠性传输;UDP不保证可靠性传输。 性能 TCP传输效率慢,需要较多的资源开销,UDP传输效率快&am…...

Day 1 Vue 页面框架
现在前端框架越来越像后端了,特别是TypeScript这样的语言出现后,开发前端的体验跟后端渐渐接近了。当然,作为一个后端,直接上手前端,还是有很多坑要填的。 本次开发,前端页面框架直接选择Vue。原因很简单&…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...