Linux-开机流程与模块管理

相关概念

MBR

MBR (Master Boot Record), 存放在磁盘的第一个扇区, 有 512 Bytes 大小. 其中:

  • 前 446 Bytes 用于安装 boot loader
  • 后 64 Bytes 用于记录分区表

64 Bytes 容量仅能保存四组分区记录. 分 3 个主分区 (primary partition) 和 1 个扩展分区 (extended partition).

扩展分区的目的是使用额外的扇区来记录分区信息, 也即, 其会指向磁盘中的其他区块来做分区记录信息.

尽管有扩展分区, 上限也是 64 个.

GPT

GPT (GUID partition table), 使用逻辑区块地址 (Logical Block Address, LBA, 默认为 512 Bytes 大小) 来规划分区, 第一个 LBA 称为 LBA0.

GPT 使用磁盘最开始的 34 个 LBA 来记录分区信息, 使用磁盘最后的 33 个 LBA 来做备份.

结构如:

  • LBA0, 称为 MBR 相容区块, 其与 MBR 模式类似, 前 446 Bytes 存储 boot loader 程序, 而剩下的区域放入一个特殊标志, 来表示此磁盘为 GPT 格式
  • LBA1, 是 GPT 表头记录, 用于记录分区表本身的位置和大小, 备份用的 GPT 分区位置, 以及分区表的检验机制码. 操作系统根据这个检验码来判断 GPT 是否正确, 若有错误, 则取用备用 GPT 来恢复
  • LBA2-33, 为实际记录分区信息处, 每个 LBA 可以记录 4 个分区记录, 因此默认情况可以有 128 个分区. 每个 LBA 有 512 Bytes 大小, 每个记录用到 128 Bytes, 剩下的 64 Bytes 用来记载开始/结束的分区号码 (这里限制了一个分区的最大容量)

Boot loader

其主要用于提供:

  • 开机菜单
  • 加载内核文件
  • 转交给其他 loader

理解第三点时需要注意, 每一个分区都会保留一块开机扇区给操作体统安装 boot loader (主要是各操作系统的 boot loader 不同).

在 Linux 安装时,

开机流程

大概如下:

  1. 按下电源键后, 计算机硬件会读取 BIOS 或 UEFI BIOS 来载入硬件信息
  2. 取得各种信息后, 进行开机自检 (Power-on Self Test, POST), 之后执行硬件侦测的初始化
  3. BIOS 侦测到硬盘并其第一个扇区中前 446 Bytes 中存放的 boot loader 程序如 grub2
  4. Boot loader 中选择直接载入内核 (kernel) 以及虚拟文件系统 (Initial RAM FileSystem, initramfs) 到内存或转交给其他 loader (这里以 Linux 内核为例)
  5. Initramfs 在内存中被解压为根目录, 向 kernel 提供适当的内核模块, 让其能侦测硬件并载入驱动程序, 并卸载 initramfs 以挂载实际的根目录
  6. 硬件被成功驱动后, 内核调用 systemd 程序, 并以 default.target 的流程运行服务, systemd 会依次执行:
    • sysinit.target 来初始化系统
    • basic.target 来准备操作系统环境
    • multi-user.target 下的服务
    • multi-user.target 下的 /etc/rc.d/rc.local 文件
    • multi-user.target 下的 getty.target 服务
    • graphical.target 来启用图形界面

Initramfs 的内容

Initramfs 是在挂载实际根目录前为 kernel 提供核心模块的临时根目录. 这里以 Archlinux 的为例. Initramfs 存放在 /boot 目录下, 为 /boot/initramfs-linux.img.

可以用 lsinitrd (有些可能是 lsinitcpio) 命令直接查看 initramfs.img 文件的内容, 如:

1
[root@localhost]# lsinitrd initramfs-linux.img

也可以解压出查看:

1
2
3
4
5
[root@localhost]# mkdir /tmp/initramfs
[root@localhost]# cp /boot/initramfs-linux.img /tmp/initramfs
[root@localhost]# cd /tmp/initramfs
[root@localhost]# file initramfs-linux.img
initramfs-linux.img: Zstandard compressed data (v0.8+), Dictionary ID: None

这里可以看到, initramfs-linux.img 文件是 Zstandard 格式的压缩文件, 因此可以进行解压:

1
2
3
[root@localhost]# unzstd initramfs-linux.img -o initramfs
[root@localhost]# file initramfs
initramfs: ASCII cpio archive (SVR4 with no CRC)

再使用 cpio 解压到当前目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@localhost]# cpio -idmv < initramfs
[root@localhost]# ll
Permissions Size User Date Modified Name
lrwxrwxrwx - jie 1 Jan 1970 bin -> usr/bin
.rw-r--r-- 2.8k jie 1 Jan 1970 buildconfig
.rw-r--r-- 107 jie 1 Jan 1970 config
drwxr-xr-x - jie 1 Jan 1970 dev
drwxr-xr-x - jie 24 Mar 14:52 etc
drwxr-xr-x - jie 24 Mar 14:52 hooks
.rwxr-xr-x 3.4k jie 1 Jan 1970 init
.rw-r--r-- 15k jie 1 Jan 1970 init_functions
.rwxr-xr-x 89M jie 24 Mar 14:52 initramfs
.rwxr-xr-x 58M jie 24 Mar 14:52 initramfs-linux.img
.rw-r--r-- 2.6k jie 1 Jan 1970 keymap.bin
.rw-r--r-- 0 jie 1 Jan 1970 keymap.utf8
lrwxrwxrwx - jie 1 Jan 1970 lib -> usr/lib
lrwxrwxrwx - jie 1 Jan 1970 lib64 -> usr/lib
drwxr-xr-x - jie 1 Jan 1970 new_root
drwxr-xr-x - jie 1 Jan 1970 proc
drwxr-xr-x - jie 1 Jan 1970 run
lrwxrwxrwx - jie 1 Jan 1970 sbin -> usr/bin
drwxr-xr-x - jie 1 Jan 1970 sys
drwxr-xr-x - jie 1 Jan 1970 tmp
drwxr-xr-x - jie 24 Mar 14:52 usr
drwxr-xr-x - jie 24 Mar 14:52 var
.rw-r--r-- 4 jie 1 Jan 1970 VERSION

这样就可以看到这个临时根目录内的内容.

这个根目录也是使用 systemd 来管理, 因此可以查看其 default.target 是什么 (注意这个时候是在 initramfs 的目录下):

1
2
3
4
[root@localhost]# pwd
/tmp/initram
[root@localhost]# ll usr/lib/systemd/system/default.target
lrwxrwxrwx - root 4 Mar 01:04 /usr/lib/systemd/system/default.target -> initrd.target

(这里在 Arch 上其实没有)

可以看出其以 initrd.target 来进行启动, 可以在自己系统上查看其依赖项:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost]# systemctl list-dependencies initrd.target
initrd.target
○ ├─initrd-parse-etc.service
○ ├─systemd-battery-check.service
○ ├─systemd-bsod.service
○ ├─systemd-pcrphase-initrd.service
● ├─basic.target
● │ ├─-.mount
● │ ├─tmp.mount
● │ ├─paths.target
● │ ├─slices.target
...
...

initrd.target 启用这一系列服务, 并让系统顺利运行后, 就会写在 initramfs 小型文件系统, 并挂载实际的根目录.

systemd 使用 default.target 的开机流程

在主机硬件准备完毕后, kernel 会主动调用第一个程序 systemd, 其 PID 为 1.

systemd 的主要功能是准备软件的执行环境, 包括系统主机名, 网络, 语言, 文件系统格式的设置以及其他服务的启动. 而一个 target 则是要启用的环境, 一般是 multi-user.targetgraphical.target (一个 target 可以启用另外的 target).

systemd 默认会启用 /etc/systemd/system/default.target (某个 target 的链接文件). 可以用:

1
systemctl list-dependencies default.target

来具体分析启动的流程. 大概为:

  1. local-fs.targetswap.target, 挂载 /etc/fstab 里规范的文件系统和内存交换空间
  2. sysinit.target, 侦测硬件, 载入所需的核心模块等
  3. basic.target, 载入主要的硬件驱动和防火墙设置等
  4. multi-user.target, 一般服务以及网络服务的载入
  5. 图形界面相关服务的载入

内核与内核模块

内核用于在开机时驱动主机上的硬件设备, 而这些驱动程序大多被编译为内核模块 (由厂商提供), 在需要时由内核读取.

内核和解压缩内核所需的 RAM Disk 都放在 /boot 目录下, 在 Archlinux 下为:

  • /boot/vmlinuz-linux
  • /boot/initramfs-linux.img

内核模块一般放在:

  • /lib/modules/version/kernel/lib/modules/$(uname -r)/kernel

在内核以及模块顺利载入后, 会将相关信息记录到:

  • /proc/version, 内核版本
  • /proc/sys/kernel 目录, 内核核心功能

一般情况下, 如果有一个新硬件, 但是当前内核不支持, 此时有两种方式:

  • 重新编译内核, 并加入最新的硬件驱动程序源代码
  • 将硬件驱动程序编译为模块, 在开机时载入

内核模块以及相依性

/lib/modules/$(uname -r)/kernel 下存放的模块还分为几个目录:

  • arch, 与硬件相关
  • crypto, 与内核支持的加密技术
  • drivers, 硬件驱动程序
  • fs, 内核所支持的 filesystem
  • net, 网络相关以及防火墙模块
  • sound, 与音效相关
    (当然还有一些目录)

内核模块的相依性 (Dependencies) 指的是一个内核模块对于其他模块或操作系统组件的依赖关系. 当加载一个内核模块时, 操作系统会检查该模块所需的其他模块或组件是否已经加载, 并确保所有依赖项都满足, 如果存在未满足的依赖关系, 操作系统可能会拒绝加载该模块或导致加载失败.

而依赖性相关信息保存在 /lib/modules/$(uname -r)/modules.dep 文件中, 可以用 depmod 命令来更新和创建.

如添加了一个网卡驱动程序, 文件名为 a.ko, 想要将其加入到内核的依赖中:

1
2
cp a.ko /lib/modules/$(uname -r)/kernel/drivers/net
depmod

(注意 kernel 核心模块的扩展名一定是 .ko 结尾)

查看内核模块

查看所有已载入模块信息

1
lsmod

其会显示:

  • Module, 模块名称
  • Size, 模块的大小
  • Used by, 此模块是否被其他模块使用

查看一个模块的具体信息

使用:

1
modinfo [-adln] [module_name|filename]

常用参数为:

  • -a, 仅列出 auther
  • -d, 仅列出 description
  • -l, 仅列出 license
  • -n, 仅列出 path

如读取 drm 模块的详细信息:

1
modinfo drm

模块的载入和移除

使用 insmod 和 rmmod

insmod (“insert module”) 需要使用者自行载入完整文件名, 且其不会分析模块依赖, 语法为:

1
insmod [/full/path/module_name] [parameters]

如:

1
insmod /lib/modules/$(uname -r)/kernel/fs/fat/fat.ko

rmmod 的语法为:

1
rmmod [-fw] module_name
  • -f, “–force”

如:

1
rmmod fat

当遇到模块有依赖时, insmodrmmod 可能无法直接载入或移除模块.

使用 modprobe

modprobe 会自动搜寻 modules.dep 中的记录以解决依赖问题, 且不需要提供模块的完整路径.

载入如:

1
modprobe cifs

移除如:

1
modprobe -r cifs

模块载入的配置文件

主要有两个目录:

  • /etc/modules-load.d/*.conf, 单纯载入
  • /etc/modprobe.d/*.conf, 可加上参数

如想要添加 nf_conntrack_ftp 模块, 若不指定参数, 可以创建 /etc/modules-load.d/vbird.conf:

1
nf_conntrack_ftp

(一个模块写一行)

若想要添加参数, 则创建 /etc/modprobe.d/vbird.conf:

1
options nf_conntrack_ftp ports=555

然后:

1
2
systemctl restart systemd-modules-load.service
lsmod | grep nf_conntrack_ftp

Linux-开机流程与模块管理
http://example.com/2024/03/24/Linux-开机流程与模块管理/
作者
Jie
发布于
2024年3月24日
许可协议