当前位置: 首页 > news >正文

驱动开发硬核特训 · Day 16:字符设备驱动模型与实战注册流程


🎥 视频教程请关注 B 站:“嵌入式 Jerry”


一、为什么要学习字符设备驱动?

在 Linux 驱动开发中,字符设备(Character Device)驱动 是最基础也是最常见的一类驱动类型。很多设备(如 LED、按键、串口、传感器)都可以以字符设备方式呈现,并通过 read()write()ioctl() 与用户空间通信。

理解字符设备驱动,不仅是掌握 Linux 驱动开发的起点,也为日后深入 platform、总线设备驱动、misc、input 等模型打下坚实基础。


在这里插入图片描述

二、字符设备驱动的核心模型

字符设备驱动的注册模型由内核 include/linux/cdev.h 文件中的结构与接口决定:

2.1 struct cdev 是关键结构

struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;...
};

它是内核维护的代表“一个字符设备”的核心结构体。

2.2 file_operations 是接口描述

这是用户空间调用 open/read/write/ioctl 等系统调用时,最终调用的驱动函数集。

static const struct file_operations my_fops = {.owner = THIS_MODULE,.open = my_open,.read = my_read,.write = my_write,.release = my_release,
};

2.3 主设备号 & 次设备号

  • 主设备号用于标识驱动程序
  • 次设备号用于区分同一类驱动下的不同设备实例

三、字符设备注册的五个步骤

学习字符设备注册的流程,可以总结为五步:

步骤 1:分配设备号

dev_t devno;
alloc_chrdev_region(&devno, 0, 1, "mychardev");
  • devno 是设备号(类型为 dev_t),用 MAJOR(devno)MINOR(devno) 分别提取主次设备号。
  • alloc_chrdev_region() 会自动分配主设备号。

步骤 2:初始化 cdev 结构

struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;

步骤 3:添加 cdev 到系统中

cdev_add(&my_cdev, devno, 1);

这一步使得字符设备被内核正式管理。

步骤 4:创建设备节点

配合 udev 或手动创建设备节点:

mknod /dev/mydev c <主设备号> <次设备号>

更推荐在内核中通过 class_create + device_create 来自动创建节点。

步骤 5:卸载时释放

device_destroy(my_class, devno);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(devno, 1);

四、完整实战代码:LED 字符设备驱动

我们用一个模拟的 LED 驱动为例,实现一个字符设备:

4.1 驱动核心代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>#define DEV_NAME "myled"static dev_t devno;
static struct cdev my_cdev;
static struct class *my_class;static int led_value = 0;static ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{return copy_to_user(buf, &led_value, sizeof(led_value)) ? -EFAULT : sizeof(led_value);
}static ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{return copy_from_user(&led_value, buf, sizeof(led_value)) ? -EFAULT : sizeof(led_value);
}static int led_open(struct inode *inode, struct file *file)
{pr_info("LED device opened\n");return 0;
}static int led_release(struct inode *inode, struct file *file)
{pr_info("LED device closed\n");return 0;
}static struct file_operations led_fops = {.owner = THIS_MODULE,.read = led_read,.write = led_write,.open = led_open,.release = led_release,
};static int __init led_init(void)
{alloc_chrdev_region(&devno, 0, 1, DEV_NAME);cdev_init(&my_cdev, &led_fops);cdev_add(&my_cdev, devno, 1);my_class = class_create(THIS_MODULE, "myled_class");device_create(my_class, NULL, devno, NULL, DEV_NAME);pr_info("LED driver loaded: major=%d\n", MAJOR(devno));return 0;
}static void __exit led_exit(void)
{device_destroy(my_class, devno);class_destroy(my_class);cdev_del(&my_cdev);unregister_chrdev_region(devno, 1);pr_info("LED driver unloaded\n");
}module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jerry");
MODULE_DESCRIPTION("Simple LED Char Device Driver");

五、测试方法与验证

编译并加载

make
insmod myled.ko

查看设备节点

ls /dev/myled

读写测试

echo 1 > /dev/myled
cat /dev/myled

六、文件系统中的呈现:/proc 与 /sys

字符设备驱动在加载后,会自动出现在以下路径中:

路径含义
/proc/devices显示当前注册的主设备号及驱动名
/sys/class/myled_class/myled/设备节点属性
/dev/myled用户空间访问入口

七、理论总结:字符设备模型的特点

  • 与平台设备无绑定关系,适合用于最简设备建模。
  • 所有用户空间 I/O 都通过 file_operations 映射函数实现。
  • 可作为更复杂模型(如 misc、input、platform)的基础。

八、常见问题答疑

Q1:字符设备必须创建设备节点吗?

A:必须。否则无法从用户空间访问。可使用 mknoddevice_create 自动生成。

Q2:file_operations 中哪些函数必须实现?

A:至少实现 openreleasereadwrite。也可以只实现 ioctl,视业务而定。

Q3:主设备号可以固定吗?

A:可以使用 register_chrdev() 指定固定主设备号,但建议使用动态分配。


九、结语与展望

字符设备模型虽是最基础的驱动类型,但它揭示了驱动与用户空间之间的交互机制,是理解 Linux 驱动架构的入口。后续我们将继续向上探索:

  • misc 模型
  • platform 总线匹配机制
  • device tree 配合 platform 驱动

敬请期待《驱动开发硬核特训 · Day 17》!


📺 视频教程请关注 B 站:“嵌入式 Jerry”

如有问题,欢迎评论区留言交流,也欢迎点赞收藏,让我们一起成为 Linux 驱动高手!


相关文章:

  • 基于STC89C52RC和8X8点阵屏、独立按键的匹配消除类小游戏
  • unity3d实现物体闪烁
  • Discuz论坛网站忘记管理员密码进不去管理中心怎么办?怎么改管理员密码?
  • 45.[前端开发-JavaScript高级]Day10-迭代器-生成器
  • Git创建空分支并推送到远程仓库
  • 市场分析 3 mysql (槽)
  • YOLO11改进,尺度动态损失函数Scale-based Dynamic Loss,减少标签不准确对损失函数稳定性的影响
  • 【网络安全】OWASP 十大漏洞
  • 蓝桥杯2024省A.成绩统计
  • 组件是怎样写的(1):虚拟列表-VirtualList
  • Activity之间交互
  • spark与hadoop的区别
  • Flutter 状态管理 Riverpod
  • 【Linux】多线程任务模块
  • 【Linux篇】轻松搭建命名管道通信:客户端与服务器的互动无缝连接
  • 卷积神经网络--手写数字识别
  • day33和day34图像处理OpenCV
  • 教育行业网络安全:守护学校终端安全,筑牢教育行业网络安全防线!
  • FastGPT Docker Compose本地部署与硅基流动免费AI接口集成指南
  • 【计算机网络】第五章 局域网技术
  • GDP增长4.1%,一季度广东经济数据出炉
  • 湖南平江发生人员溺亡事件,已造成4人死亡
  • 蔚来第三品牌萤火虫上市:对标宝马MINI,预期贡献10%销量
  • 中汽协:杜绝虚假宣传与过度营销,确保用户清晰区别驾驶辅助与自动驾驶
  • 4月LPR保持不变:1年期3.1%,5年期以上3.6%
  • “科技+萌点”机器人马拉松刷屏!宇树回应“半马摔倒”