在
linux中,我们使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。cdev结构体被内核用来记录设备号,使用设备时,通常会打开设备节点,通过设备节点inode结构体、file结构体最终找到 file_operation结构体,并从 file_operation结构体获得操作设备的具体方法。设备号
每一行表示一个设备,每一行的第一个字符表示设备的类型。
c用来标识字符设备
b用来标识块设备
如
autofs是一个字符设备c, 它的主设备号是10(指向设备驱动程序),次设备号是235(指向某个具体设备)。内核中设备编号的含义
内核中,设备编号用
dev_t表示,dev_t是一个32位的数,其中,高12位表示主设备号,低20位表示次设备号。 通过移位操作,从设备编号中得到主、次设备号。同样可以通过主次设备号位运算变成dev_t 类型的设备编号。
cdev结构体
内核通过一个散列表(哈希表)来记录设备编号。 哈希表由数组和链表组成,吸收数组查找快,链表增删效率高,容易拓展等优点。
以主设备号为
cdev_map编号,使用哈希函数f(major)=major%255来计算组数下标(使用哈希函数是为了链表节点尽量平均分布在各个数组元素中,提高查询效率); 主设备号冲突,则以次设备号为比较值来排序链表节点。
- struct kobject kobj:内嵌内核对象,将设备同意加入到“linux设备驱动模型‘’中管理(如对象的引用技术、电源管理、热插拔、生命周期、与用户通信等)
- struct module *owner:字符设备驱动程序所在的内核模块对象的指针。
- const struct file_operations *ops:文件操作,是字符设备驱动中非常重要的数据结构。应用程序通过文件系统(VFS)呼叫设备驱动程序中实现的文件操作类函数过程中,ops起着桥梁纽带作用。VFS与设备文件之间的接口是file_operations结构体成员函数,这个结构体包含了对文件进行打开、关闭、读写、控制等一系列成员函数。
- struct list_head list:用于将系统中的字符设备形成链表(这是个内核链表的一个链接,可以再内核很多结构体中看到这种结构的身影)
- dev_t dev:字符设备的设备号,有主设备和次设备号构成。
- unsigned int count: 属于同一主设备号下次设备号的个数,用于表示驱动控制的同类设备的实际数量。
设备节点
设备节点(设备文件):Linux中通过
mknod命令来创建设备节点,设备节点是Linux内核对设备的抽象。设备节点创建在
/dev下,是连接内核与用户层的枢纽,设备是接到哪种接口的哪个ID 上。数据结构
序号 | 结构体 | 描述 | 备注 |
1 | file_operations | 文件操作 | 关联系统调用和驱动程序 |
2 | file | 文件描述 | 已经打开的文件描述符 |
3 | inode | 索引节点 | 存储文件元数据 |
file_operations 结构体

常见成员函数速查表
函数指针 | 对应系统调用 | 典型用途 | 返回值要点 |
open | open() | 打开设备时初始化资源,绑定private_data | 0成功;负errno失败 |
release | close() | 关闭设备时释放资源 | 0成功;负errno失败 |
read | read() | 从设备读数据到用户缓冲区,通常配合copy_to_user | (>=0)读取字节数;负errno失败 |
write | write() | 从用户缓冲区写数据到设备,通常配合copy_from_user | (>=0)写入字节数;负errno失败 |
llseek | lseek() | 修改文件位置指针,支持SEEK_SET/SEEK_CUR/SEEK_END | 新的偏移(>=0);负errno失败 |
unlocked_ioctl | ioctl() | 自定义控制命令,传递配置参数或触发动作 | 0/正值成功;负errno失败 |
poll | poll() / select() / epoll() | 查询设备是否可读/可写/异常,实现非阻塞I/O多路复用 | POLLIN|POLLRDNORM(可读)、POLLOUT|POLLWRNORM(可写)等掩码组合 |
mmap | mmap() | 将设备内存/缓冲区映射到用户空间地址,实现零拷贝访问 | 0成功;负errno失败 |
fasync | fcntl(F_SETFL, FASYNC) | 启用异步通知,设备就绪时向进程发送SIGIO信号 | 0成功;负errno失败 |
flush | close() | 文件描述符关闭前刷新待处理数据(每次close调用,区别于release仅最后一次) | 0成功;负errno失败 |
compat_ioctl | ioctl()(32位兼容) | 64位内核上处理32位用户空间的ioctl调用 | 同unlocked_ioctl |
file_operations结构体向系统提供设备驱动程序接口,结构体中每个成员对应一个系统调用。以下代码中只列出本章使用到的部分函数。
- llseek: 修改文件的当前读写位置,并返回偏移后的位置。
- 参数file:对应文件指针;
- 参数loff_t:指定偏移量的大小;
- 参数int:进行偏移位置,SEEK_SET,SEEK_CUR,SEEK_END。
- read:读取设备中数据
- 参数file:对应文件指针;
- 参数char __user*: 数据缓冲区,地址空间是用户空间的,内核模块不能直接使用,需要使用copy_to_user函数操作;
- 参数loff_t:指定偏移量的大小。
- write:向设备写入数据
- 参数file:对应文件指针;
- 参数char __user*: 数据缓冲区,地址空间是用户空间的,内核模块不能直接使用,需要使用copy_from_user函数操作;
- 参数loff_t:指定偏移量的大小。
- unlocked_ioctl: 提供设备执行相关控制命令的实现方法,它对应于应用程序的
fcntl函数以及ioctl函数。在 kernel 3.0 中已经完全删除了 struct file_operations 中的 ioctl 函数指针。
- open: 设备驱动第一个被执行的函数,一般用于硬件的初始化。
- release: 当file结构体被释放时,将会调用该函数。与open函数相反,该函数可以用于释放
- poll: 用于实现多路复用I/O机制(
poll/select/epoll)。驱动需调用poll_wait()将等待队列注册到poll表,然后返回当前设备状态掩码。 - 参数file:对应文件指针;
- 参数poll_table:由内核传入的
poll_table结构体指针,驱动调用poll_wait(file, &wq, wait)将等待队列wq注册进去; - 返回值:
POLLIN | POLLRDNORM(可读)、POLLOUT | POLLWRNORM(可写)、POLLERR(错误)等掩码组合。
- mmap: 将设备物理内存或内核缓冲区直接映射到用户空间虚拟地址,避免
copy_to_user/copy_from_user的拷贝开销。常用于framebuffer、DMA缓冲区、共享内存等场景。 - 参数file:对应文件指针;
- 参数vma:
vm_area_struct结构体,描述用户请求映射的虚拟地址范围; - 驱动通常调用
remap_pfn_range()或io_remap_pfn_range()完成物理→虚拟的页表映射。
- fasync: 实现异步通知机制。当应用程序通过
fcntl(fd, F_SETFL, flags | FASYNC)开启异步通知后,驱动在数据就绪时调用kill_fasync()向进程发送SIGIO信号。 - 参数fd:文件描述符;
- 参数filp:文件指针;
- 参数mode:开启/关闭异步通知的标志;
- 驱动需维护一个
fasync_struct链表,并在函数中调用fasync_helper()注册/注销。
- flush: 每次
close()系统调用时都会执行(即使同一文件被多次打开),区别于release仅在最后一个引用关闭时执行。适用于需要在每次关闭时刷新缓存或提交数据的场景(如NFS客户端)。
- compat_ioctl: 在64位内核上为32位用户空间应用提供ioctl兼容入口。函数签名与
unlocked_ioctl一致,驱动需处理32/64位数据结构差异(指针宽度、对齐等)。
我们提到read和write函数时,需要使用copy_to_user函数以及copy_from_user函数来进行数据访问,写入/读取成 功函数返回0,失败则会返回未被拷贝的字节数。
file结构体
每打开一个文件,内核会创建一个结构体,内核中用
file结构体来表示每个打开的文件。文件的操作函数是该结构体的成员变量
f_opf_op:存放与文件操作相关的一系列函数指针,如open、read、wirte等函数。
private_data:该指针变量只会用于设备驱动程序中,内核并不会对该成员进行操作。因此,在驱动程序中,通常用于指向描述设备的结构体。
inode结构体
包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。
一个文件描述符(file文件结构)表示一个已经打开的设备,多个文件描述符可以表示同个文件多个打开实例。但此时这些file文件全部只能指向同一个
inode结构体。dev_t i_rdev: 表示设备文件的结点,这个域实际上包含了设备号。
struct cdev *i_cdev:struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。






