技术知识库

字符设备如何把设备节点连接到 file_operations

字符设备如何把设备节点连接到 file_operations

摘要

字符设备的数据入口是 /dev 下的设备节点,但真正处理 open()、read() 和 write() 的是驱动注册的 struct file_operations。设备节点携带主、次设备号,内核用设备号找到 cdev,再由 cdev->ops 把系统调用分发到驱动函数。class_create() 和 device_create() 负责把设备呈现给设备模型并触发节点创建,不是存放邮箱数据的地方。

问题/背景

在 martin_mailbox 实验中,用户空间执行:

printf 'test\n' > /dev/martin_mailbox
cat /dev/martin_mailbox

需要解释 /dev 节点、设备号、cdev、file_operations 和内核缓冲区分别承担什么职责。

核心机制

用户进程
  │ open/read/write("/dev/martin_mailbox")
  ▼
/dev/martin_mailbox ── 主设备号 247、次设备号 0
  │
  ▼
内核字符设备映射 ── dev_t → struct cdev
  │                         │
  │                         └─ cdev->ops
  ▼
struct file_operations
  ├─ .open  → martin_mailbox_open()
  ├─ .read  → martin_mailbox_read()
  └─ .write → martin_mailbox_write()
                           │
                           ▼
                 martin_mailbox_buf[]

设备号与 cdev

alloc_chrdev_region() 分配一段没有冲突的主、次设备号;cdev_add() 把这段设备号与一个 struct cdev 建立映射。动态方式使用 cdev_alloc() 得到 cdev 指针,并设置 ops 和 owner。

file_operations 是唯一的一组约定原型

内核中当然存在许多名为 read、open 的函数,但字符设备驱动必须遵守 struct file_operations 对应成员的函数签名。驱动可以只实现当前需要的成员;未实现的操作保持为空。

class 和 device 的职责

class_create() 创建设备类别,device_create() 在设备模型中注册一个设备并携带 dev_t。在当前 Debian systemd 环境中,设备事件配合 devtmpfs/udev 生成 /dev/martin_mailbox。

因此,写操作仍然写向 /dev/martin_mailbox。/sys/class/martin_mailbox/ 是设备模型的属性视图,不是字符数据通道。

write 的数据与返回值

用户指针不能被内核直接当作普通指针使用,因此用 copy_from_user() 把数据复制到内核缓冲区。若缓冲区为 256 字节并需要以 \0 结尾,单次最多保存 255 字节有效数据。

驱动应返回实际接收的字节数 write_len,而不是缓冲区容量,也不应把额外补入的 \0 算入有效数据长度。

read 的偏移与 EOF

*ppos 是当前文件位置。一次 read() 只复制调用者请求长度和剩余数据长度中的较小值,并把偏移向后推进。后续调用发现 *ppos >= martin_mailbox_len 时返回 0,含义是 EOF。

返回 0 不代表“本次复制成功”,而是告诉用户程序“没有更多数据”。因此即使 copy_to_user() 已复制内容,若处理函数最后仍返回 0,cat 也会把这次调用当成 EOF。

证据与来源

  • 实验内核:Running Linux Kernel 5.0,ARM64,5.0.0+。
  • 实验模块:/home/martin-zou/workspace/gcc-lab/char_mod/martin_mailbox/martin_mailbox.c。
  • 相关头文件:include/linux/cdev.h、include/linux/fs.h、include/linux/device.h、include/linux/uaccess.h。
  • 实际验证:向 /dev/martin_mailbox 写入 test\n 后,cat 输出 test。

关联

待验证

  • 第 6 章完成后阅读 VFS 到 chrdev_open()、cdev 和具体 file_operations 的调用路径。
  • 第 6 章完成后补充 device_create() 到 devtmpfs/udev 节点创建的源码与事件路径。
  • 后续实验补充并发访问、互斥和阻塞读写语义。