对GRUB和initramfs的小探究

news2024/11/6 3:16:10

竞赛时对操作系统启动过程产生了些疑问,于是问题导向地浅浅探究了下GRUB和initramfs相关机制,相关笔记先放在这里了。

内核启动流程

在传统的BIOS系统中,计算机具体的启动流程如下:

  1. 电源启动:当计算机的电源打开时,电源供电给计算机的硬件设备。
  2. BIOS自检:计算机的BIOS固件会自检硬件设备,包括RAM、处理器、硬盘等,以确保它们正常工作。
  3. 引导设备选择:BIOS会根据预先定义的启动顺序(通常是硬盘、光驱、USB等)选择一个启动设备。
  4. MBR(Master Boot Record)加载:如果选择的启动设备是硬盘,BIOS会加载该硬盘的MBR,其中包含了引导加载程序。
  5. GRUB加载:MBR中的引导加载程序通常是GRUB(或其他引导加载程序)。GRUB会被加载到计算机的内存中,并开始执行。
  6. GRUB菜单:GRUB会显示一个菜单,列出可供选择的操作系统或内核。
  7. 操作系统加载:用户选择操作系统后,GRUB会加载相应的操作系统或内核,并将控制权交给它。

在本次内核编译配置过程中,最主要探究的是文件系统的装载过程,也即介于6-7之间的部分。

概述

文件系统在启动流程中的发展历程可以分为以下三个部分:

  1. GRUB文件系统

    由 GRUB 自身通过 BIOS 提供的服务加载

  2. initramfs

    由GRUB加载,用于挂载真正的文件系统

  3. 真正的根文件系统

下面,将介绍1和2两个流程。

GRUB

GRUB(GNU GRand Unified Bootloader)是一种常用的引导加载程序,用于在计算机启动时加载操作系统。

GRUB的主要功能是在计算机启动时提供一个菜单,让用户选择要启动的操作系统或内核。它支持多个操作系统,包括各种版本的Linux、Windows、BSD等。通过GRUB,用户可以在多个操作系统之间轻松切换。

除了操作系统选择,GRUB还提供了一些高级功能,例如引导参数的设置、内存检测、系统恢复等。它还支持在启动过程中加载内核模块和初始化RAM磁盘映像(initrd或initramfs)。

GRUB具有高度可配置性,允许用户自定义引导菜单、设置默认启动项、编辑内核参数等。它还支持引导加载程序间的链式引导,可以引导其他引导加载程序,如Windows的NTLDR。

GRUB的基本作用流程为:

  1. BIOS加载MBR,MBR加载GRUB,开始执行GRUB程序
  2. GRUB程序会读取grub.cfg配置文件
  3. 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 ]; then
          search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt3 --hint-efi=hd0,gpt3 --hint-baremetal=ahci0,gpt3  XXX
        else
          search --no-floppy --fs-uuid --set=root XXX
        fi
        
        linux   /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的代码中,有以下几个要点值得注意:

  1. insmod gzio

    由于加载gzio模块,提供对GZIP压缩和解压缩功能的支持。

    看到这里我第一反应是觉得有点割裂,为啥这看着比较无关紧要的解压缩功能要在内核启动之前就需要有呢?于是我想起来在配置内核时,有一个选项是这样的:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    在配置选项中,我们选择了对initramfs的支持,并且勾选了Support initial ramdisk/ramfs compressed using gzip ,也即在编译时通过gzip压缩initramfs的大小以节省空间。

    所以说,我们在内核启动之前,持有的initramfs处于被压缩的状态。故而,我们自然需要在内核启动之前安装gzio模块,从而支持之后对initramfs的解压缩了。

  2. insmod ext2

    这句代码说明,GRUB的临时文件系统为ext2类型,这句代码事实上是在安装GRUB建立临时文件的必要依赖包,从而GRUB程序之后才能建立其临时文件系统、从/boot/initrd.img获取initramfs映像。

  3. 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"

重点看下这几个参数:

  1. 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"
    
  2. 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_top
	if [ -z "${ROOT}" ]; then
		panic "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_dev
	local time_elapsed
	local count

	# 获取grub.cfg的rootdelay参数的设备等待时间。如果没有该参数,默认是30秒
	local slumber=30
	if [ "${ROOTDELAY:-0}" -gt $slumber ]; then
		slumber=$ROOTDELAY
	fi

	# 等待设备
	case "$dev_id" in
	UUID=*|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 can
	while ! real_dev=$(resolve_device "${dev_id}") ||
	      ! get_fstype "${real_dev}" >/dev/null; do
		if ! $may_panic; then
			echo "Gave up waiting for ${name}"
			return 1
		fi
		echo "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 ]; then
			echo "   - Check root= (did the system wait for the right device?)"
		fi
		echo " - Missing modules (cat /proc/modules; ls /dev)"
		panic "ALERT!  ${dev_id} does not exist.  Dropping to a shell!"
	done

	DEV="${real_dev}"
}

可以看到,这里如果进入错误状态,最终就是这样的效果2333:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

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

相关文章

CPU眼里的C/C++:1.2 查看变量和函数在内存中的存储位置

写一个很简单的 c 代码,打印一些“地址”, 也就是变量、函数的“存储位置”:当程序被加载到内存后,它们具体是存在哪里,可以用精确的数值来表示,这就是内存地址。 https://godbolt.org/z/Ghh9ThY5Y #inc…

电解电容寿命与哪些因素有关?

电解电容在各类电源及电子产品中是不可替代的元器件,这些电子产品中由于应用环境的原因,使它成为最脆弱的一环,所以,电解电容的寿命也直接影响了电子产品的使用寿命。 一、电解电容失效模式与因素概述 铝电解电容器正极、负极引出…

Java实现ORM第一个api-FindAll

经过几天的业余开发,今天终于到ORM对业务api本身的实现了,首先实现第一个查询的api 老的C#定义如下 因为Java的泛型不纯,所以无法用只带泛型的方式实现api,对查询类的api做了调整,第一个参数要求传入实体对象 首先…

android——自定义控件(编辑框)、悬浮窗

一、自定义编辑框 效果图: 主要的代码为: class EditLayout JvmOverloads constructor(context: Context, attrs: AttributeSet? null, defStyleAttr: Int 0 ) : ConstraintLayout(context, attrs, defStyleAttr) {private var editTitle: Stringpr…

Android12 启动页适配

印象中,在2022年末接到了一个针对Android12启动页适配的需求,当时也使用了一些适配方案,也写了一个Demo,但是最终没有付诸适配行动;当然并不是适配失败,而是根据官方适配方案适配后太丑了… 1024纪念文章&a…

Java中的CAS简述

目录 1、CAS是什么 2、CAS的生活化例子 3、Java中的atomic包 4、unsafe类 5、CAS的缺点及解决方案 小结 1、CAS是什么 CAS(Compare and Swap)是一种并发编程中的原子操作,用于实现多线程环境下的无锁同步。它是一种乐观锁的实现方式&a…

分布式限流:Redis

目录 1:如何实现分布式限流 2:限流的几种类别 2.1:固定窗口限流 2.2:滑动窗口限流 2.3:漏桶限流 2.4:令牌桶限流 3:实现分布式限流:Redis 3.1:引入Redisson的依赖包 3.2:初始化Redisson 3.3:创建Redisson的限流类 1:如何实现分布式限流 1:把统计用户的使用频率等这些…

Springcloud介绍

1.基本介绍 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring …

欧拉图和哈密顿图

欧拉图 在连通图G中,经过G的每条边一次且仅一次的通路,称为欧拉通路若欧拉通路为回路,则称为欧拉回路含有欧拉回路的图称为欧拉图有欧拉通路则G可以一笔画出有欧拉回路则G是连通的且无奇点(欧拉图无奇点) 哈密顿图 …

2023了,是时候使用pnpm了!

2023了,是时候使用pnpm了! Excerpt 2023了,是时候使用pnpm了! 什么是pnpm pnpm代表performant npm(高性能的npm),同npm和Yarn,都属于Javascript包管理安装工具,它较npm和…

树与二叉树(考研版)

文章目录 树与二叉树树的基本概念结点、树属性的描述树的性质 二叉树的概念二叉树的性质二叉树的构建二叉树的遍历先序遍历中序遍历后序遍历层次遍历 递归算法和非递归算法的转换源代码 线索二叉树二叉树的线索化线索二叉树 找前驱/后继 树和森林树的存储 树与二叉树的应用哈夫…

交换机基础(四):MSTP负载均衡配置案例

如图所示是某个企业内部核心网络的结构图,目前企业中有20个VLAN, 编号为VLAN1~VLAN20, 为了确保内部网络的可靠性,使用 了冗余链路和MSTP 协议。为了能更好地利用网络资源和带宽,现管理员希望通过配置MSTP 的负载均衡实现网络带宽…

【proteus】8086 写一个汇编程序并调试

参考书籍:微机原理与接口技术——基于8086和Proteus仿真(第3版)p103-105,p119-122. 参考程序是p70,例4-1 在上一篇的基础上: 创建项目和汇编文件 写一个汇编程序并编译 双击8086的元件图: …

2.1 向量与线性方程组

一、行图像与列图像 线性代数的中心问题是求解线性方程组。线性的意思是这些方程的未知数是一次的,即每个未知数只会乘数字,而不会出现 x x x 与 y y y 相乘的项。下面是一个由两个未知数组成的方程组: 两个方程 两个未知数 { x − 2 y 1…

Django学习笔记——文件上传(界面还怪好看得嘞)

定义文件上传函数 #文件上页面 def uploadFileIndex(request):return render(request, "uploadFile.html")#文件上传接口 def uploadFile(request):if request.method POST and request.FILES[file]:uploaded_file request.FILES[file]fs FileSystemStorage()# 选…

螺旋矩阵[中等]

优质博文:IT-BLOG-CN 一、题目 给你一个m行n列的矩阵matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5] 示例 2&#xf…

vue如何使用冻结对象提升代码效率及其原理解析

先给大家伙整个实际工作中一定会碰到的问题 如下vue dome ,它的代码非常简单功能也1非常简单,就是一个按钮,点击后会显示有多少条数据 来看看源码, html部分就是一个按钮绑定了一个loadData事件,然后在p标签内展示了这个myData这个数据的长度 <template><div id&quo…

Txt病毒

一.txt病毒原理 利用翻转字符串的方法 混淆伪装 &#xff08;jpg 、doc、ppt 等&#xff09; &#xff08;1&#xff09;更改程序图标 &#xff08;2&#xff09;将程序重命名 readtxt.exe 鼠标放到 read 与 txt 中间 设置格式为 RLO // 这个“RLO”是一个转义字符&#xf…

交互式 Web 应用 0 基础入门

初探 Gradio&#xff1a;轻松构建交互式 Web 应用 文章目录 初探 Gradio&#xff1a;轻松构建交互式 Web 应用Why Gradio?安装 Gradio创建交互式界面1. gr.Interface2. gr.Blocks 强大的组件库输入输出组件控制组件布局组件 示例交互式数据可视化多组件同时&#xff08;嵌套&a…

Netty框架详解

一、Netty简介 Netty是一款基于Java NIO的网络编程、高性能、异步事件驱动的网络应用框架。它的设计目标是提供简单易用、高性能、可扩展的网络编程框架。 二、Netty主要特点 高并发&#xff1a;Netty使用异步的、非阻塞的I/O模型&#xff0c;通过事件驱动的方式处理网络操作…