Linux 驱动开发流程(带最小可运行代码 + 通俗类比)
很多人学 Linux 驱动都会卡在这里:
API 都看过,但完全不知道它们是怎么串起来工作的
这篇文章目标很明确:
✅ 用一条主线讲清流程
✅ 用类比帮你记住
✅ 给你一个最小可运行驱动模板
一、先记住一条主线(最重要)
用户操作 → 内核 → 设备号 → cdev → file_operations → 你的函数
二、一个类比彻底记住
🎯 类比:打电话
| 现实 | Linux |
|---|---|
| 电话号码 | 设备号 |
| 通讯录 | cdev |
| 人 | file_operations |
| 打电话 | open/read/write |
📞 过程:
open("/dev/mydev")
↓
设备号(号码)
↓
cdev(通讯录)
↓
file_operations(人)
↓
调用 read/write
三、驱动开发完整流程(工程版)
① 枚举设备(DTS / 总线)
② 注册驱动
③ 匹配(compatible)
④ probe() 执行
⑤ 注册字符设备(cdev)
⑥ 创建 /dev 节点
⑦ 用户操作 → 调用驱动
四、最小字符设备驱动(核心)
下面这个代码你可以直接用来练手👇
🔧 1. 驱动代码(mydev.c)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NAME "mydev"
static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
/* ---------------- 核心操作函数 ---------------- */
static int my_open(struct inode *inode, struct file *file)
{
printk("my_open called\n");
return 0;
}
static ssize_t my_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk("my_read called\n");
return 0;
}
static ssize_t my_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk("my_write called, size=%ld\n", size);
return size;
}
/* file_operations */
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
};
/* ---------------- 初始化 ---------------- */
static int __init my_init(void)
{
printk("mydev init\n");
/* 1️⃣ 分配设备号 */
alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
/* 2️⃣ 初始化 cdev */
cdev_init(&my_cdev, &my_fops);
/* 3️⃣ 添加 cdev */
cdev_add(&my_cdev, dev_num, 1);
/* 4️⃣ 创建设备节点 */
my_class = class_create(THIS_MODULE, "my_class");
device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
return 0;
}
/* ---------------- 退出 ---------------- */
static void __exit my_exit(void)
{
device_destroy(my_class, dev_num);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
printk("mydev exit\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
五、这个代码到底在干嘛?(一行一行串起来)
1️⃣ 分配设备号
alloc_chrdev_region(&dev_num, 0, 1, "mydev");
👉 作用:
给设备分配一个“身份证”(major + minor)
2️⃣ 定义行为(file_operations)
static struct file_operations my_fops
👉 定义:
这个设备能干什么(open/read/write)
3️⃣ cdev_init(核心)
cdev_init(&my_cdev, &my_fops);
👉 本质:
把“设备号”和“操作函数”绑定
4️⃣ cdev_add(关键)
cdev_add(&my_cdev, dev_num, 1);
👉 本质:
告诉内核:这个设备可以用了
5️⃣ 创建设备节点
device_create(...)
👉 最终生成:
/dev/mydev
六、真正的数据流(必须理解)
当你执行:
echo 123 > /dev/mydev
内核发生:
write("/dev/mydev")
↓
找到设备号
↓
找到 cdev
↓
找到 file_operations
↓
调用 my_write()
七、cdev 的本质(终极总结)
cdev = 内核里的“设备通讯录”
它做了一件事:
设备号 → file_operations
八、你必须能复述的三句话
✅ 1️⃣
file_operations = 定义设备“能干什么”
✅ 2️⃣
设备号 = 标识“是谁”
✅ 3️⃣(最重要)
cdev = 把“是谁”和“能干什么”绑定起来
九、扩展:和平台驱动的关系
字符设备只是:
“用户接口层”
完整驱动其实是:
DTS → platform_driver → probe → 注册 cdev → /dev
十、为什么你之前学不会?
因为你之前是这样记的:
背 API:cdev_add、cdev_init
但正确理解应该是:
用户调用 → 设备号 → cdev → file_operations → 函数
十一、建议练习(强烈推荐)
insmod mydev.ko
echo hello > /dev/mydev
dmesg
你会看到:
my_write called
十二、一句话总结(面试可用)
Linux 字符设备驱动的核心是通过 cdev,将设备号映射到 file_operations,从而让用户空间调用最终执行驱动代码。
结尾
如果你能讲清这三件事,你已经超过 70% 初级驱动工程师:
① 设备怎么来的(枚举)
② probe 为什么执行(匹配)
③ read/write 为什么能调用(cdev)
这才是驱动开发的“主线思维”。
&spm=1001.2101.3001.5002&articleId=159762929&d=1&t=3&u=371a3b3d120342a3902bcbb04896e264)
2386

被折叠的 条评论
为什么被折叠?



