1. Linux内核驱动概述:驱动的作用、内核模块基础、驱动开发环境搭建

1.1 驱动的作用:连接硬件与操作系统的桥梁

在嵌入式Linux系统中,设备驱动是操作系统内核与硬件设备之间的中间层。它负责将上层应用的通用I/O请求(如读、写、控制)转换为硬件设备能够理解的特定操作序列。没有驱动,操作系统无法识别或控制任何外部硬件。

核心职责: 驱动封装了硬件寄存器的操作细节,向上提供统一的文件操作接口(file_operations),使应用程序可以像操作普通文件一样操作硬件设备。
Linux 设备驱动分层架构 用户空间应用程序 系统调用接口 (syscall) 虚拟文件系统 (VFS) 设备驱动 (file_operations) open / read / write / ioctl / mmap 硬件设备 (寄存器 / 中断 / DMA) 驱动隐藏硬件细节 提供标准文件操作接口

1.2 内核模块基础:动态加载与卸载

Linux内核支持可加载内核模块(Loadable Kernel Module, LKM)机制。模块是可以在运行时动态插入内核或从内核移除的代码片段,无需重新编译整个内核或重启系统。绝大多数设备驱动都以模块形式发布。

1.2.1 模块的生命周期

阶段 命令 内核行为
加载 insmod / modprobe 调用模块的 module_init() 函数,注册驱动到内核
运行 — 驱动响应来自VFS的系统调用,操作硬件
卸载 rmmod 调用模块的 module_exit() 函数,释放资源并注销驱动

1.2.2 最小内核模块代码示例

// hello_drv.c — 最小内核模块
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, driver world!\n");
    return 0;   // 0 表示加载成功
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, driver world!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Expert");
MODULE_DESCRIPTION("A minimal kernel module example");
关键点: __init 和 __exit 宏将函数放入特殊内存段,加载/卸载完成后内核会释放这些内存以节省空间。MODULE_LICENSE 必须声明,否则内核会标记模块为“污染”。

1.3 驱动开发环境搭建

嵌入式驱动开发通常采用交叉编译模式:在性能强大的宿主机(x86 PC)上编写和编译代码,生成的目标文件在目标板(ARM/RISC-V/MIPS)上运行。搭建环境需要准备以下三个核心组件。

1.3.1 交叉编译工具链

工具链包含交叉编译器(如 arm-linux-gnueabihf-gcc)、链接器、汇编器以及目标板所需的C库(glibc或uClibc)。

组件 典型名称 作用
C编译器 arm-linux-gnueabihf-gcc 将C源码编译为目标板机器码
汇编器 arm-linux-gnueabihf-as 将汇编代码转为目标文件
链接器 arm-linux-gnueabihf-ld 链接多个目标文件与库
二进制工具 objdump / readelf 分析目标文件的段、符号表

1.3.2 内核源码树

编译内核模块必须依赖目标板对应的内核源码树(Kernel Source Tree)。源码树需要预先配置(make menuconfig)并至少编译一次,以生成 Module.symvers 和 include/generated/ 等头文件。

# 典型的内核源码树目录结构(精简)
linux-6.1.y/
├── arch/          # 体系结构相关代码 (arm64, riscv, x86 ...)
├── drivers/       # 设备驱动存放目录 (char, net, i2c, spi ...)
├── include/       # 内核头文件 (linux/, asm-generic/, uapi/ ...)
├── scripts/       # 编译脚本 (module.lds, Makefile.build ...)
├── Makefile       # 顶层 Makefile
├── Module.symvers # 符号版本信息(编译模块时自动引用)
└── .config        # 内核配置选项

1.3.3 模块编译的 Makefile 模板

# Makefile — 用于编译 hello_drv.ko
obj-m := hello_drv.o

# KDIR 指向目标板的内核源码树路径
KDIR  := /home/user/linux-6.1.y

# CROSS_COMPILE 指定交叉编译工具链前缀
CROSS_COMPILE := arm-linux-gnueabihf-

all:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) ARCH=arm clean
常见陷阱: 宿主机内核版本与目标板内核版本必须一致或兼容,否则加载模块时会报 Invalid module format 或 version magic 不匹配错误。始终使用目标板运行的内核源码树进行编译。

1.4 驱动开发流程总览

嵌入式驱动开发完整流程 ① 环境搭建 ② 编写驱动 ③ 交叉编译 ④ 部署测试 ⑤ 调试优化 ⑥ 提交合入 迭代循环:调试发现问题 → 修改驱动源码 → 重新交叉编译 → 部署测试,直至稳定 最终合入内核主线或产品 BSP 仓库

1.5 本章小结

  • 驱动的作用:封装硬件操作,向上提供标准文件接口,是内核与硬件的中间层。
  • 内核模块:支持动态加载/卸载,使用 module_init 和 module_exit 管理生命周期。
  • 交叉编译工具链:包含编译器、链接器、二进制工具,前缀标识目标架构(如 arm-linux-gnueabihf-)。
  • 内核源码树:编译模块必须依赖目标板对应的源码树,版本必须严格匹配。
  • 开发流程:环境搭建 → 编写 → 编译 → 部署 → 调试 → 合入,形成闭环迭代。
下一阶段准备: 在第二章中,我们将深入设备ID匹配机制,学习如何让驱动正确识别硬件设备,并处理多设备兼容性问题。