Lazy loaded image
Words 0Read Time 1 min
Invalid Date
 
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来计算组数下标(使用哈希函数是为了链表节点尽量平均分布在各个数组元素中,提高查询效率); 主设备号冲突,则以次设备号为比较值来排序链表节点。
notion image
  • 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 结构体
include/linux/fs.h
include/linux/fs.h

常见成员函数速查表

函数指针
对应系统调用
典型用途
返回值要点
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_op
  • f_op:存放与文件操作相关的一系列函数指针,如open、read、wirte等函数。
  • private_data:该指针变量只会用于设备驱动程序中,内核并不会对该成员进行操作。因此,在驱动程序中,通常用于指向描述设备的结构体。
inode结构体
包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。
一个文件描述符(file文件结构)表示一个已经打开的设备,多个文件描述符可以表示同个文件多个打开实例。但此时这些file文件全部只能指向同一个inode结构体。
  • dev_t i_rdev 表示设备文件的结点,这个域实际上包含了设备号。
  • struct cdev *i_cdev struct cdev是内核的一个内部结构,它是用来表示字符设备的,当inode结点指向一个字符设备文件时,此域为一个指向inode结构的指针。
 
驱动编程 - GPIO
上一篇
Data Structure and Algorithm
下一篇
用面试拷问嵌入式技术栈

Comments
Loading...