[Linux] Linux 系统从启动到驱动加载
Linux 系统从启动到驱动加载
文章目录
- Linux 系统从启动到驱动加载
- 一、硬件上电与 BIOS/UEFI 阶段
- 1. 1 硬件上电初始化
- 1.2 BIOS/UEFI执行过程
- 1.3 Bootloader加载细节
- 二、Bootloader 阶段
- 三、Linux 内核初始化
- 3.1 架构相关初始化(setup_arch)
- 3.2 核心子系统初始化
- 3.3 虚拟文件系统(VFS)初始化
- 3.4 内核线程创建
- 四、驱动子系统初始化
- 4.1 设备模型初始化(driver_init)
- 4.2 总线子系统注册
- 4.3 早期驱动初始化(early platform drivers)
- 4.4 设备描述信息解析
- 五、驱动加载与设备枚举
- 5.1 总线驱动扫描硬件设备
- 5.2 设备与驱动匹配
- 5.3 驱动probe初始化
- 5.4 创建设备节点
- 六、用户空间初始化
- 6.1 init 进程启动
- 6.2 udev 设备管理
- 6.3 驱动模块加载
- 6.4 系统启动完成
- 七、关键数据结构与函数
- 八、典型驱动加载流程示例
- 九、调试与故障排查
- 9.1 查看内核启动日志(dmesg)
- 9.2 检查 sysfs 设备信息(/sys/devices)
- 9.3 使用 udevadm 监控设备事件
- 9.4 动态加载/卸载驱动模块(insmod/rmmod)
一、硬件上电与 BIOS/UEFI 阶段
1. 1 硬件上电初始化
当计算机电源接通后:
- 电源供应器完成自检,向主板发送Power Good信号
- CPU从复位状态解除,寄存器被初始化为默认值(如x86架构的CS=0xF000,EIP=0xFFF0)
- 主板时钟发生器开始工作,提供稳定的时钟信号
1.2 BIOS/UEFI执行过程
BIOS/UEFI固件执行的主要任务:
-
POST(Power-On Self Test):
- 检测关键硬件:CPU、内存、显卡等
- 声卡发出"滴"声表示检测通过(不同BIOS厂商的提示音编码不同)
- 对检测到的硬件生成设备列表(如通过ACPI表)
-
硬件初始化:
- 配置内存控制器和初始化RAM
- 初始化基本输入输出设备(键盘、显示器)
- 设置中断向量表(IVT)或高级中断控制器(APIC)
-
引导设备选择:
- 按照CMOS中存储的启动顺序(如:硬盘→USB→网络)
- 读取每个设备的引导扇区(MBR或GPT分区表的引导记录)
- 对于UEFI系统,会查找ESP分区中的/EFI/BOOT/BOOTx64.EFI文件
1.3 Bootloader加载细节
当BIOS/UEFI找到有效启动设备后:
- 传统BIOS会将MBR中的第一阶段引导程序(512字节)加载到内存0x7C00处
- UEFI则直接加载EFI应用程序到内存
- 对于GRUB2:
- 第一阶段(boot.img)仅负责加载core.img
- 第二阶段(core.img)包含基本文件系统驱动,用于定位/boot/grub
- 最终加载grub.cfg配置文件并显示启动菜单
示例启动顺序:
- 检测到SATA硬盘
- 读取MBR中的引导代码
- 加载GRUB的stage1 → stage1.5 → stage2
- 显示GRUB菜单等待用户选择
二、Bootloader 阶段
-
引导加载器启动:
- 系统 BIOS/UEFI 完成硬件初始化后,将控制权交给存储在 MBR 或 EFI 分区中的引导加载器(如 GRUB2)
- 常见的引导加载器包括:
- GRUB (Grand Unified Bootloader)
- Syslinux
- LILO (Linux Loader)
- systemd-boot (适用于 UEFI 系统)
-
内核加载过程:
- 引导加载器读取其配置文件(如 GRUB 的 grub.cfg)
- 加载压缩的内核镜像(通常在 /boot 目录下,命名为 vmlinuz-<版本号>)到内存
- 同时加载 initramfs(Initial RAM Filesystem)镜像
- 典型文件示例:
/boot/vmlinuz-5.4.0-91-generic /boot/initrd.img-5.4.0-91-generic
-
参数传递与内核启动:
- 引导加载器向内核传递启动参数,常见参数包括:
root=
:指定根文件系统设备(如 root=/dev/sda1)init=
:指定初始化程序路径(替代默认的 /sbin/init)quiet
:减少启动时内核信息输出splash
:显示启动画面
- 内核解压过程:
- 首先解压缩 zImage 或 bzImage 格式的内核
- 然后加载必要的驱动和初始化临时根文件系统
- 最终控制权转移到内核的入口点 start_kernel() 函数(位于 init/main.c)
- 引导加载器向内核传递启动参数,常见参数包括:
-
initramfs 的作用:
- 提供早期用户空间环境
- 包含必要的驱动程序(如磁盘控制器、文件系统驱动)
- 挂载真正的根文件系统
- 在嵌入式系统中可能直接作为最终根文件系统使用
-
特殊场景处理:
- 加密根分区:需要 initramfs 包含解密工具
- RAID/LVM:需要加载相应的模块
- 网络启动:需要包含网络驱动程序
三、Linux 内核初始化
3.1 架构相关初始化(setup_arch)
setup_arch() 是架构特定的初始化函数,主要负责:
- 解析硬件信息(如通过设备树获取CPU类型、内存布局等)
- 初始化物理内存管理(如建立memblock内存分配器)
- 设置处理器特殊功能(如开启MMU、缓存等)
- 架构相关的早期设备初始化(如SMP处理器初始化)
示例:在ARM架构中会初始化处理器异常向量表,x86架构则会检测和初始化ACPI。
3.2 核心子系统初始化
包括以下关键系统的初始化:
- 页表初始化:建立内核地址空间映射,包括:
- 内核代码段映射
- 设备I/O空间映射
- 早期内存分配区域映射
- 中断控制器初始化:
- 探测并初始化本地APIC/IOAPIC(x86)
- GIC初始化(ARM)
- 设置中断描述符表(IDT)
- 定时器子系统:
- 初始化高精度定时器(hrtimer)
- 设置系统时钟源(如HPET、TSC)
- 校准CPU频率(loops_per_jiffy)
3.3 虚拟文件系统(VFS)初始化
VFS初始化流程:
- 注册基础文件系统类型(如proc、sysfs、tmpfs)
- 初始化dentry缓存和inode缓存
- 挂载rootfs作为初始文件系统
- 创建标准文件描述符(stdin/stdout/stderr)
- 挂载实际根文件系统(通过root=内核参数指定)
3.4 内核线程创建
初始化的关键内核线程包括:
- kthreadd(内核线程守护进程,pid=2)
- 负责创建和管理其他内核线程
- 通过kthread_create()请求创建新线程
- 其他早期线程:
- ksoftirqd(软中断处理线程)
- kworker(工作队列线程)
- migration(CPU迁移线程)
- watchdog(看门狗监控线程)
这些初始化步骤完成后,内核将进入用户空间初始化阶段,启动第一个用户进程(通常是init或systemd)。
四、驱动子系统初始化
4.1 设备模型初始化(driver_init)
该阶段主要完成内核设备模型的核心数据结构初始化,包括:
- kobject 子系统初始化,建立设备层次结构基础
- 设备类和属性文件系统的创建(/sys/class/)
- 内核对象引用计数机制的建立
- 设备号分配器的初始化(devtmpfs)
4.2 总线子系统注册
分平台总线类型进行初始化:
-
platform_bus_init:
- 注册平台总线类型(platform_bus_type)
- 初始化 platform_device 和 platform_driver 的匹配机制
- 创建/sys/bus/platform/目录结构
- 典型应用场景:SoC内置设备(如GPIO控制器、时钟模块)
-
pci_init:
- PCI总线探测和枚举
- PCI设备资源分配(IO空间、内存空间)
- 建立PCI设备树结构
- PCI设备驱动匹配机制初始化
4.3 早期驱动初始化(early platform drivers)
在基本设备模型建立后立即加载的关键驱动:
- 内存控制器驱动
- 串口调试驱动(earlycon)
- 时钟源驱动
- 中断控制器驱动
这些驱动通过early_platform_init
机制注册,具有以下特性: - 在常规驱动加载前完成初始化
- 使用简化版的资源获取接口
- 通常通过命令行参数指定(如
earlycon=uart8250,mmio,0xfe001000
)
4.4 设备描述信息解析
根据系统类型采用不同配置机制:
设备树(DT)系统:
- 解析/boot/dtb文件或U-Boot传递的设备树
- 展开设备树节点为device_node结构
- 将设备树节点转换为platform_device
- 处理设备树中的中断映射、DMA范围等特殊属性
ACPI系统:
- 解析ACPI表(DSDT、SSDT等)
- 转换ACPI设备为平台设备
- 处理_PRT(中断路由)、_CRS(资源分配)等方法
- 支持ACPI电源管理扩展功能
两种系统最终都会生成统一的设备资源描述,包括:
- 内存映射区域
- 中断号
- DMA通道
- 时钟源
- 电源管理参数
五、驱动加载与设备枚举
5.1 总线驱动扫描硬件设备
总线驱动负责识别和扫描连接的硬件设备。常见方式包括:
- PCI设备枚举:通过读取PCI配置空间(Configuration Space)获取设备Vendor ID、Device ID等信息
- 设备树解析(DT node parsing):在ARM架构中解析设备树(Device Tree)的节点信息
- ACPI枚举:x86架构通过ACPI表获取设备信息
- USB总线枚举:通过USB协议发现连接的设备
示例:PCI枚举过程中会遍历所有总线号码(bus number),对每个设备/功能组合读取其配置寄存器。
5.2 设备与驱动匹配
内核通过driver_match_device()
函数进行匹配,主要依据:
- 设备树中的
compatible
属性 - ACPI设备ID(HID/UID/CID)
- PCI设备的Vendor/Device ID
- 平台设备的名称匹配
匹配优先级通常为:设备树匹配 > ACPI匹配 > ID表格匹配 > 名称匹配。
5.3 驱动probe初始化
匹配成功后调用驱动的probe()
函数进行:
- 资源分配(内存、IRQ、DMA等)
- 硬件初始化(寄存器配置、固件加载)
- 设备特定设置(时钟、电源管理)
- 子系统注册(如输入设备、网络设备等)
典型错误处理包括:检查资源可用性、逐步回滚失败操作。
5.4 创建设备节点
通过devtmpfs自动创建设备文件:
- 内核调用
device_add()
将设备注册到系统 - 根据设备类型(字符/块设备)创建
/dev
节点 - 设置正确的设备号(major/minor)
- 应用层可通过udev/mdev规则进一步配置节点属性
特殊设备可能还需要:
- sysfs属性文件(
/sys/class/...
) - debugfs接口
- 用户空间通知机制(uevent)
六、用户空间初始化
6.1 init 进程启动
内核最后阶段会启动第一个用户空间进程 init(PID=1),根据系统配置选用不同的 init 系统:
- systemd(现代主流发行版):采用并行化的服务启动方式,提供单元(unit)管理
- 读取
/etc/systemd/system/
和/usr/lib/systemd/system/
下的单元文件 - 启动基础目标(target)如
multi-user.target
或graphical.target
- 读取
- sysvinit(传统系统):串行执行
/etc/init.d/
中的启动脚本- 通过运行级别(runlevel)控制启动阶段
- 典型流程:先挂载文件系统,再启动基础服务
6.2 udev 设备管理
udev 守护进程负责动态设备管理:
- 监听内核通过 netlink 发送的 uevent
- 处理设备热插拔事件(如 USB 插入)
- 根据
/etc/udev/rules.d/
规则文件:- 创建设备节点(如
/dev/sda1
) - 设置设备权限和所有者
- 触发关联的硬件初始化脚本
- 创建设备节点(如
6.3 驱动模块加载
通过 modprobe
机制加载用户态驱动模块:
- 读取
/etc/modprobe.d/
配置文件 - 自动解决模块依赖关系
- 典型应用场景:
- 加载文件系统驱动(如 ext4、ntfs)
- 加载特殊硬件驱动(如 NVIDIA 显卡)
- 加载网络协议栈模块
6.4 系统启动完成
完成所有初始化步骤后:
- 启动登录管理器(如 gdm、lightdm)进入图形界面
- 或显示文本登录提示符(tty1-6)
- 记录启动日志到
/var/log/boot.log
- 系统进入多用户模式,所有服务正常运行
七、关键数据结构与函数
Linux设备驱动模型中的两个核心数据结构及其关系:
/*** struct device_driver - 驱动对象的基本表示* @name: 驱动名称,用于sysfs显示和模块匹配* @bus: 指向驱动所属的总线类型,如platform_bus_type* @probe: 驱动探测回调函数,当设备与驱动匹配时调用* * 示例:当注册i2c_driver时,需要设置这些字段*/
struct device_driver {const char *name;struct bus_type *bus;int (*probe)(struct device *dev);
};/*** struct bus_type - 总线类型的抽象表示* @name: 总线名称,如"platform"、"pci"等* @match: 总线匹配函数,用于判断设备和驱动是否兼容* * 总线负责管理其下的设备和驱动,典型的匹配过程:* 1. 设备注册时,总线遍历所有驱动,调用match函数* 2. 驱动注册时,总线遍历所有设备,调用match函数* 3. 匹配成功则调用驱动的probe函数*/
struct bus_type {char *name;int (*match)(struct device *dev, struct device_driver *drv);
};
工作流程说明:
- 系统初始化时会注册各种总线类型(platform/pci/i2c等)
- 驱动开发者编写驱动时:
- 定义device_driver结构体实例
- 实现probe函数进行设备初始化
- 向总线注册该驱动
- 当匹配发生时,总线层会:
- 通过match函数验证设备和驱动的兼容性
- 调用驱动的probe函数初始化设备
- 建立sysfs中的设备-驱动关联
八、典型驱动加载流程示例
以下是一个完整的 PCI 设备驱动加载流程示例,展示了从驱动注册到设备初始化的关键步骤:
// 1. 定义设备ID表,用于匹配支持的PCI设备
static const struct pci_device_id my_pci_ids[] = {{ PCI_DEVICE(0x10AB, 0x1100), .driver_data = 0 }, // 厂商ID 0x10AB,设备ID 0x1100{ PCI_DEVICE(0x10AB, 0x1200), .driver_data = 0 }, // 另一个兼容设备{ 0, } // 结束标记
};// 2. 定义驱动操作函数
static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{int retval;// 使能PCI设备retval = pci_enable_device(pdev);if (retval) {dev_err(&pdev->dev, "Failed to enable PCI device\n");return retval;}// 获取设备资源(如内存区域、中断号等)// ...具体设备初始化代码...dev_info(&pdev->dev, "Device successfully initialized\n");return 0;
}static void my_remove(struct pci_dev *pdev)
{// 释放资源,关闭设备// ...清理代码...pci_disable_device(pdev);dev_info(&pdev->dev, "Device removed\n");
}// 3. 定义PCI驱动结构体
static struct pci_driver my_driver = {.name = "my_device", // 驱动名称.id_table = my_pci_ids, // 设备匹配表.probe = my_probe, // 设备发现时的回调.remove = my_remove, // 设备移除时的回调// 可选:.suspend/.resume 等电源管理回调
};// 4. 注册PCI驱动
module_pci_driver(my_driver);// 5. 模块信息
MODULE_DEVICE_TABLE(pci, my_pci_ids);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample PCI Device Driver");
典型工作流程:
- 内核加载驱动模块时调用
module_pci_driver
宏注册驱动 - 内核遍历PCI总线,将每个设备与
id_table
进行匹配 - 找到匹配设备后,调用
probe
函数初始化设备 - 设备使用期间可能触发中断/处理请求
- 设备移除或模块卸载时调用
remove
函数清理资源
注意事项:
- 必须实现基本的probe/remove函数对
- 需要正确处理设备使能/禁用流程
- 建议添加适当的错误处理和资源管理
- 现代驱动通常还需要实现电源管理回调
九、调试与故障排查
9.1 查看内核启动日志(dmesg)
dmesg
命令是排查驱动程序问题的首选工具,它显示内核环形缓冲区中的消息。典型使用场景包括:
- 查看驱动加载时的初始化信息:
dmesg | grep <驱动模块名>
- 跟踪设备插拔事件:
dmesg -w
(实时监控) - 检查错误信息:
dmesg --level=err,warn
- 清空缓冲区:
dmesg -c
(需要root权限)
9.2 检查 sysfs 设备信息(/sys/devices)
sysfs文件系统提供了设备树的详细信息:
/sys/devices/
├── platform
│ └── <设备名>/
│ ├── driver -> ../../../bus/platform/drivers/<驱动名>
│ ├── modalias
│ └── power/
重要检查项:
- 设备是否被正确识别:
ls /sys/bus/<总线类型>/devices
- 驱动绑定状态:查看设备目录下的
driver
符号链接 - 设备参数:如
/sys/class/gpio/gpio<N>/
下的各种属性文件
9.3 使用 udevadm 监控设备事件
udev工具链的典型应用:
# 监控所有设备事件
udevadm monitor# 查看特定设备属性
udevadm info -a -p /sys/class/net/eth0# 触发设备重扫描
udevadm trigger
关键调试场景:
- 检查规则匹配:
udevadm test /sys/class/<设备类>/<设备名>
- 验证热插拔事件处理
- 调试udev规则执行过程
9.4 动态加载/卸载驱动模块(insmod/rmmod)
驱动模块管理操作流程:
# 加载模块(需.ko文件路径)
sudo insmod module.ko [参数名=参数值]# 查看已加载模块
lsmod | grep <模块名># 卸载模块(需确保无设备在使用)
sudo rmmod module# 更常用的modprobe工具
sudo modprobe module_name
sudo modprobe -r module_name
注意事项:
- 模块参数可以通过
/sys/module/<模块名>/parameters/
查看或修改 - 依赖关系处理:
modprobe
会自动解决依赖 - 查看模块信息:
modinfo <模块名>
- 常见错误:模块版本不匹配、符号未导出、资源冲突等
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)