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 安装时,
开机流程
大概如下:
- 按下电源键后, 计算机硬件会读取 BIOS 或 UEFI BIOS 来载入硬件信息
- 取得各种信息后, 进行开机自检 (Power-on Self Test, POST), 之后执行硬件侦测的初始化
- BIOS 侦测到硬盘并其第一个扇区中前 446 Bytes 中存放的 boot loader 程序如 grub2
- Boot loader 中选择直接载入内核 (kernel) 以及虚拟文件系统 (Initial RAM FileSystem, initramfs) 到内存或转交给其他 loader (这里以 Linux 内核为例)
- Initramfs 在内存中被解压为根目录, 向 kernel 提供适当的内核模块, 让其能侦测硬件并载入驱动程序, 并卸载 initramfs 以挂载实际的根目录
- 硬件被成功驱动后, 内核调用
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 |
|
也可以解压出查看:
1 |
|
这里可以看到, initramfs-linux.img
文件是 Zstandard
格式的压缩文件, 因此可以进行解压:
1 |
|
再使用 cpio
解压到当前目录:
1 |
|
这样就可以看到这个临时根目录内的内容.
这个根目录也是使用 systemd
来管理, 因此可以查看其 default.target
是什么 (注意这个时候是在 initramfs 的目录下):
1 |
|
(这里在 Arch 上其实没有)
可以看出其以 initrd.target
来进行启动, 可以在自己系统上查看其依赖项:
1 |
|
在 initrd.target
启用这一系列服务, 并让系统顺利运行后, 就会写在 initramfs 小型文件系统, 并挂载实际的根目录.
systemd 使用 default.target 的开机流程
在主机硬件准备完毕后, kernel 会主动调用第一个程序 systemd
, 其 PID 为 1.
systemd
的主要功能是准备软件的执行环境, 包括系统主机名, 网络, 语言, 文件系统格式的设置以及其他服务的启动. 而一个 target
则是要启用的环境, 一般是 multi-user.target
和 graphical.target
(一个 target
可以启用另外的 target
).
systemd
默认会启用 /etc/systemd/system/default.target
(某个 target
的链接文件). 可以用:
1 |
|
来具体分析启动的流程. 大概为:
local-fs.target
和swap.target
, 挂载/etc/fstab
里规范的文件系统和内存交换空间sysinit.target
, 侦测硬件, 载入所需的核心模块等basic.target
, 载入主要的硬件驱动和防火墙设置等multi-user.target
, 一般服务以及网络服务的载入- 图形界面相关服务的载入
内核与内核模块
内核用于在开机时驱动主机上的硬件设备, 而这些驱动程序大多被编译为内核模块 (由厂商提供), 在需要时由内核读取.
内核和解压缩内核所需的 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
, 内核所支持的 filesystemnet
, 网络相关以及防火墙模块sound
, 与音效相关
(当然还有一些目录)
内核模块的相依性 (Dependencies) 指的是一个内核模块对于其他模块或操作系统组件的依赖关系. 当加载一个内核模块时, 操作系统会检查该模块所需的其他模块或组件是否已经加载, 并确保所有依赖项都满足, 如果存在未满足的依赖关系, 操作系统可能会拒绝加载该模块或导致加载失败.
而依赖性相关信息保存在 /lib/modules/$(uname -r)/modules.dep
文件中, 可以用 depmod
命令来更新和创建.
如添加了一个网卡驱动程序, 文件名为 a.ko
, 想要将其加入到内核的依赖中:
1 |
|
(注意 kernel 核心模块的扩展名一定是 .ko
结尾)
查看内核模块
查看所有已载入模块信息
1 |
|
其会显示:
- Module, 模块名称
- Size, 模块的大小
- Used by, 此模块是否被其他模块使用
查看一个模块的具体信息
使用:
1 |
|
常用参数为:
-a
, 仅列出 auther-d
, 仅列出 description-l
, 仅列出 license-n
, 仅列出 path
如读取 drm
模块的详细信息:
1 |
|
模块的载入和移除
使用 insmod 和 rmmod
insmod
(“insert module”) 需要使用者自行载入完整文件名, 且其不会分析模块依赖, 语法为:
1 |
|
如:
1 |
|
rmmod
的语法为:
1 |
|
-f
, “–force”
如:
1 |
|
当遇到模块有依赖时, insmod
和 rmmod
可能无法直接载入或移除模块.
使用 modprobe
modprobe
会自动搜寻 modules.dep
中的记录以解决依赖问题, 且不需要提供模块的完整路径.
载入如:
1 |
|
移除如:
1 |
|
模块载入的配置文件
主要有两个目录:
/etc/modules-load.d/*.conf
, 单纯载入/etc/modprobe.d/*.conf
, 可加上参数
如想要添加 nf_conntrack_ftp
模块, 若不指定参数, 可以创建 /etc/modules-load.d/vbird.conf
:
1 |
|
(一个模块写一行)
若想要添加参数, 则创建 /etc/modprobe.d/vbird.conf
:
1 |
|
然后:
1 |
|