1. 项目概述
作为一个毫无底层开发经验的小白,当我第一次听说"Linux驱动"这个词时,脑海里浮现的是各种复杂的电路图和晦涩难懂的代码。但实际接触后发现,驱动开发并非想象中那么高不可攀。这个系列记录了我从零开始学习Linux驱动的全过程,希望能给同样想入门的朋友一些参考。
Linux驱动本质上就是让硬件和操作系统对话的翻译官。比如当你按下键盘,驱动负责把这个动作翻译成系统能理解的信号。我选择从最简单的字符设备驱动入手,因为它结构清晰,适合新手理解驱动的基本框架。
2. 开发环境搭建
2.1 硬件准备
虽然最终目标是让驱动控制真实硬件,但初期学习完全可以用QEMU模拟器代替。我的配置:
- 主机:Ubuntu 20.04 LTS
- 虚拟机:QEMU 5.0
- 内核版本:5.4.0(选择长期支持版更稳定)
注意:内核版本最好与主机系统一致,避免兼容性问题。用
uname -r查看当前版本。
2.2 工具链安装
驱动开发需要一些基础工具:
bash复制sudo apt install build-essential libncurses-dev flex bison libssl-dev
- build-essential:包含gcc等编译工具
- libncurses-dev:内核菜单配置依赖
- flex/bison:语法分析器生成工具
2.3 内核源码获取
建议从官网下载对应版本:
bash复制wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.tar.xz
tar -xvf linux-5.4.tar.xz
解压后目录结构:
- drivers/:所有驱动代码
- include/:内核头文件
- arch/:体系架构相关代码
3. 第一个字符设备驱动
3.1 驱动基本结构
最简单的字符设备驱动包含三个核心部分:
- 设备号管理:主设备号标识驱动类型,次设备号区分具体设备
- 文件操作集:实现open/read/write等标准接口
- 注册与注销:向内核注册驱动模块
3.2 代码实现
创建mydriver.c文件:
c复制#include <linux/module.h>
#include <linux/fs.h>
#define DEVICE_NAME "mychardev"
static int major_num;
static int device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "Device opened\n");
return 0;
}
static struct file_operations fops = {
.open = device_open,
};
static int __init mydriver_init(void) {
major_num = register_chrdev(0, DEVICE_NAME, &fops);
printk(KERN_INFO "Registered with major number %d\n", major_num);
return 0;
}
static void __exit mydriver_exit(void) {
unregister_chrdev(major_num, DEVICE_NAME);
printk(KERN_INFO "Unregistered driver\n");
}
module_init(mydriver_init);
module_exit(mydriver_exit);
MODULE_LICENSE("GPL");
3.3 Makefile编写
配套的Makefile:
makefile复制obj-m := mydriver.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
4. 编译与测试
4.1 模块编译
bash复制make
生成mydriver.ko文件
4.2 加载驱动
bash复制sudo insmod mydriver.ko
dmesg | tail # 查看内核日志
应该能看到注册成功的消息
4.3 创建设备节点
bash复制sudo mknod /dev/mychardev c 250 0 # 250是查看dmesg得到的主设备号
4.4 测试设备
bash复制echo "test" > /dev/mychardev
cat /dev/mychardev
5. 常见问题排查
5.1 模块加载失败
- 错误:
Invalid module format - 原因:内核版本不匹配
- 解决:重新编译对应版本的内核头文件
5.2 权限问题
- 错误:
Permission denied - 解决:
bash复制sudo chmod 666 /dev/mychardev
5.3 设备号冲突
- 错误:
Device or resource busy - 解决:
bash复制cat /proc/devices # 查看已占用设备号
6. 进阶方向
6.1 添加读写功能
在fops结构中实现read/write函数:
c复制static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) {
// 实现读逻辑
}
static ssize_t device_write(struct file *filp, const char *buf, size_t len, loff_t *off) {
// 实现写逻辑
}
6.2 用户空间交互
使用ioctl实现更复杂的控制:
c复制long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
switch(cmd) {
case IOCTL_SET_MSG:
// 处理设置命令
break;
case IOCTL_GET_MSG:
// 处理获取命令
break;
}
}
6.3 中断处理
注册中断处理函数:
c复制request_irq(irq_num, handler, flags, name, dev);
7. 学习资源推荐
- 《Linux设备驱动程序》(LDD3):经典教材
- kernel.org/doc:官方文档
- Linux内核源码:drivers/char/下有大量示例
- QEMU源码:学习虚拟设备实现
驱动开发最难的不是代码本身,而是理解内核的运行机制。建议多读内核源码,从简单的驱动开始,逐步增加复杂度。每次遇到问题都要深究背后的原因,这样进步最快。
