字符设备注册:从cdev_add 到chrdev_open 的 VFS路径

/dev/xxx 打不开、mknod 后读写进不了驱动:问题常在 cdev 与 VFS 的衔接 ,而不是 read 实现本身。本文沿 cdev_init/cdev_addregister_chrdev_regionchrdev_openfile_operations 走通一次打开路径,便于对照主次设备号与 fops 排障。

源码锚点

路径 作用
include/linux/cdev.h struct cdevcdev_init/cdev_add/cdev_del
fs/char_dev.c 字符设备号区域、chrdev_opencdev_add
include/linux/fs.h struct file_operationsstruct inode/file
fs/devices.c / fs/inode.c(随版本) 设备 inode 与打开路径协作
Documentation/admin-guide/devices.txt 设备号约定(参考)

核心结构:

c 复制代码
/* include/linux/cdev.h */
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	dev_t dev;
	unsigned int count;
};

/* include/linux/fs.h --- 用户可见操作集 */
struct file_operations {
	struct module *owner;
	ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
	long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
	int (*open)(struct inode *, struct file *);
	int (*release)(struct inode *, struct file *);
	/* ... */
};

注册骨架:

c 复制代码
/* 典型驱动写法(概念) */
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE;
alloc_chrdev_region(&dev, 0, count, "mydev");  /* 或 register_chrdev_region */
cdev_add(&my_cdev, dev, count);

调用链

text 复制代码
驱动初始化
  → alloc_chrdev_region / register_chrdev_region   /* 占用主/次设备号 */
  → cdev_init(cdev, fops)
  → cdev_add(cdev, dev, count)                     /* 挂到内核 cdev 映射 */

用户态 open("/dev/mydev")
  → do_sys_open / vfs_open
      → chrdev_open(inode, file)                   /* fs/char_dev.c */
          → 按 inode->i_rdev 找到 cdev
          → file->f_op = cdev->ops
          → 若存在 fops->open → 调用驱动 open
  → 之后 read/write/ioctl 走 file->f_op

设备节点创建(二选一):

text 复制代码
userspace: mknod /dev/mydev c <major> <minor>
  或 udev/devtmpfs:驱动侧 device_create(class, ...) 自动生成节点

重点知识

1. 设备号是查找键,cdev 是操作载体

VFS 打开字符设备时用 i_rdev(主+次)找到 cdev,再切换 file->f_op。只 mknod 而不 cdev_add,或 major/minor 与 cdev_add 不一致,会得到 ENXIO 或进错驱动。

2. register_chrdevcdev_* 别混用错生命周期

老接口 register_chrdev(major, name, fops) 内部仍落到字符设备层;新代码推荐 alloc_chrdev_region + cdev_init + cdev_add,卸载时对称 cdev_del + unregister_chrdev_region。模块 owner 必须设对,否则 fops 还在用时模块可被卸掉。

3. open 里挂私有数据,勿依赖全局单例

常见写法:file->private_data = my_struct。多开、多 minor 时用 iminor(inode) 区分实例。read/write 必须正确处理 __user 指针(copy_to_user/copy_from_user)与返回值(ssize_t,部分成功要返回已传字节数)。

4. 配置与观测

bash 复制代码
cat /proc/devices | grep -i mydev          # 主设备号是否注册
ls -l /dev/mydev                           # 主次是否与驱动一致
# 动态分配时看 dmesg / 驱动打印的 major
# 类设备
ls /sys/class/<your_class>/
# 打开失败
strace -e openat,open cat /dev/mydev
dmesg | tail

权限:节点默认模式与 udev 规则决定是否需 root;EACCES 先查权限再查驱动。

5. 设计意图与踩坑

  • 意图 :字符设备 = 字节流式访问抽象,用 fops 把系统调用落到驱动。
  • cdev_add 成功但未 device_create,节点不存在;手动 mknod 又写错次设备号。
  • read 返回 -EFAULT 却不区分「无数据可读」与「用户缓冲非法」。
  • :阻塞 read 未实现 poll/fasync,用户态无法用 select/epoll 等待。
  • ioctl 命令码与用户头文件不同步,导致参数解释错乱。

Checklist

  • 能指出 cdev_addchrdev_open 所在文件,并画出 open 到 fops->open 的链
  • /proc/devices 中的 major 与 /dev 节点、cdev_add 三者一致
  • 模块 owner、加载/卸载与 cdev_del/unregister_chrdev_region 成对
  • open 设置 private_data;多 minor 用次设备号索引
  • 用户缓冲区一律 copy_*_userstrace 能看到 open/read 进入驱动路径
  • 需要事件通知时实现 poll,避免用户态空转
相关推荐
life16918 分钟前
python requests采集同花顺F10、龙虎榜数据
python·同花顺·龙虎榜
angushine27 分钟前
qwen3-tts使用实例
python·tts
陈年老古董1 小时前
OpenCV实战:文档扫描与图像风格迁移
人工智能·python·opencv·计算机视觉
benchmark_cc1 小时前
数据 API 稳定性为什么会影响量化策略?从数据获取到信号执行的完整分析
开发语言·python·数据分析·量化·股票数据·quantdash·量化数据源
STLearner1 小时前
KDD 2026 | (2月轮)时空数据(Spatial-Temporal)论文总结时空(交通)预测,轨迹数据挖掘(表示,生成)
论文阅读·人工智能·python·深度学习·学习·机器学习·数据挖掘
axinawang1 小时前
ddddocr--识别验证码
python
2601_962180331 小时前
python——Django 框架
开发语言·python·django
长江后浪博客1 小时前
Conda环境下测试Intel NPU:Python版本如何选择?
开发语言·python·conda·openvino·intel npu
陈年老古董2 小时前
PyTorch 实现 MNIST 手写数字识别学习笔记
笔记·python·深度学习·学习