Linux Driver Char Device 笔记
1.打開設備
- 设备节点
本质上和文件节点相同,为操作驱动的基本单位
- 文件节点
只用来做文件操作
打开设备文件的步骤:
chrdev_open完成的主要工作是:首先根据设备号找到添加在内核中代表字符设备的cdev (cdev是放在cdev_map散列表中的,驱动加载时会构造相应的cdev并添加到这个散列表中,并且在构造这个cdev时还实现了一个操作方法集合,由cdev的ops成员指向它),找到对应的cdev对象后,用cdev关联的操作方法集合替代之前构造的file结构中的操作方法集合,然后调用cdev所关联的操作方法集合中的打开函数,完成设备真正的打开操作,这也标志着do_filp_open函数基本结束。
使用的时候是先用inode找到对应的cdev,然后cdev对象包含对应的操作集file_operations。
并且在以下情况时,驱动程序会根据入口函数 static int cdev_open ( struct inode* inodp, struct file* filp ) 将 和具体设备 相关的 驱动数据 与 file 结构建立关联:
- 打开代表设备的inode
- 初始化要作为file来操作的设备
这样 file 结构就包含对驱动操作的 操作集 及 与具体设备挂钩的 驱动私有数据 了,这样就能对 file 进行操作进而 控制相关设备 了。
打开一个 cdev 的步骤:
- 打开对应 inode
- 通过 indoe 找到对应 cdev
- 将与 cdev 相关的 设备数据 及 驱动操作集 与 要作为文件操作的代表驱动的 file 结构 相挂钩
2.IOCTL
ioctl 命令编码规则
31 30
----------(2 Byte)
00 00 - 命令不带参数
00 01 - 向驱动传递参数,写入
01 00 - 从驱动获取数据,读出
01 01 - 既写入又读出
29 28 27 ... 16
------------------------(14 Byte)
参数所占内存大小
15 14 13 ... 8
----------------------(8 Byte)
每个驱动全局唯一幻数
7 6 5 ... 0
----------------(8 Byte)
命令码
#define _IOC(dir, type, nr, size)
dir - 写入读出方向
type - 命令码
nr - 驱动全局唯一幻数
size - 参数所占内存大小
阻塞隊列聲明及操作方式
- DECLARE_WAIT_QUEUE_HEAD( name )
静态定义等待队列头
- init_waitqueue_head( q )
初始化等待队列头
- wait_event( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:N/A
排他入队:N/A
拿内部锁:N/A
屏蔽中断:N/A
- wait_event_timeout( q, condition, timeout )
入队条件:Y
超时判断:Y
信号唤醒:N/A
排他入队:N/A
拿内部锁:N/A
屏蔽中断:N/A
- wake_up( q )
唤醒队列:Y
信号唤醒:N/A
拿内部锁:N/A
- wait_event_interruptible( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:Y
排他入队:N/A
拿内部锁:N/A
屏蔽中断:N/A
- wait_event_interruptible_timeout( q, condition, timeout )
入队条件:Y
超时判断:Y
信号唤醒:Y
排他入队:N/A
拿内部锁:N/A
屏蔽中断:N/A
- wait_event_interruptible_exclusive( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:Y
排他入队:Y
拿内部锁:N/A
屏蔽中断:N/A
- wake_up_interruptible( q )
唤醒队列:Y
信号唤醒:Y
拿内部锁:N/A
- wait_event_interruptible_locked( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:Y
排他入队:N/A
拿内部锁:Y
屏蔽中断:N/A
- wait_event_interruptible_locked_irq( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:Y
排他入队:N/A
拿内部锁:Y
屏蔽中断:Y
- wait_event_interruptible_exclusive_locked( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:Y
排他入队:Y
拿内部锁:Y
屏蔽中断:N/A
- wait_event_interruptible_exclusive_locked_irq( q, condition )
入队条件:Y
超时判断:N/A
信号唤醒:Y
排他入队:Y
拿内部锁:Y
屏蔽中断:Y
- wake_up_locked( q )
唤醒队列:Y
信号唤醒:N/A
拿内部锁:Y
I/O多路复用
int poll( struct pollfd * fds, nfds_t nfds, int timeout )
struct pollfd
{
/* file descriptor */
int fd;
/* requested events */
short events;
/* returned events */
short revents;
};
- poll ( )
- sys_poll ( )
- do_sys_poll ( )
- 构造 poll_list
- 记录监听文件个数
- poll_initwait ( )
- 构造 poll_wqueues
- 初始化成员
- pt 指针指向 poll_table
- do_poll ( )
- 遍历 poll_list 里的 struct pollfd 结构
- 初始化和每一个 struct pollfd 结构对应的 poll_wqueues 里的 poll_table_entry
- 指定 poll_table_entry 节点被唤醒后调用的函数为 poll_wake ( )
- 根据 struct pollfd 结构的文件描述符找到对应的 file 结构体
- 调用 file 结构体的 poll ( ) 接口来调用驱动中的 poll ( ) 接口函数
- poll ( ) 接口函数调用 poll_wait ( ) 辅助函数
- poll_wait ( )
- 调用 poll_wqueues 里,pt 指针指向的 poll_table 的 __poll_wait ( )
- 将构造好的 poll_table_entry 加入驱动的等待队列
- poll ( )
- 驱动 poll ( ) 接口函数检查资源是否可用
- 返回状态位,如果有相应事件发生,跳到 8. poll ( ) ,否则跳到 9. poll_schedule_timeout ( )
- poll ( )
- 关心的事件发生,将事件记录在 struct pollfd 结构的 revents 中
- 将 struct pollfd 结构复制到用户层,poll ( ) 调用返回
- poll_schedule_timeout ( )
- 没有相应事件发生,将 poll ( ) 系统休眠
- xxx_isr ( ) 中断发生
- 对应驱动的资源可用,唤醒驱动中的等待队列
- poll_table_entry 出队,调用 poll_wake ( )
- poll ( ) 系统被唤醒
- 回到 5. do_poll ( )
异步I/O
static ssize_t vser_aio_read (
struct kiocb * ,
const struct iovec * ,
unsigned long ,
loff_t
)
异步通知
- 注册信号处理函数
- sigaction ( SIGIO, &act, &oldact )
- 打开设备文件,设置设备文件属主
- fcntl ( fd, F_SETOWN, getpid () )
- 设备文件资源可用时,驱动向应用层发送信号
- fcntl ( fd, F_SETSIG, SIGIO )
- 设置文件 FASYNC 标志 ( 使能异步通知机制 )
- flag = fcntl ( fd, F_GETFL )
- fcntl ( fd, F_SETFL, flag | FASYNC )
驱动
- 构造 struct fasync_struct 链表头( 指针 )
- struct vser_dev {
- unsigned int baud;
- ...
- struct fasync_struct * fapp;
- }
- 实现 fasync 接口函数,调用 fasync_helper 函数来构造 struct fasync_struct 节点,并加入链表
- static int vser_fasync ( int fd, struct file * flip, int on )
- {
- return fasync_helper ( fd, flip, on, &vsdev.fapp );
- }
- 资源可用时,调用 kill_fasync 发送信号,设置资源可以类型( 可读,可写 )
- static ssize_t vser_read ( struct file * flip, char ___user * buf, size_t count, loff_t * pos )
- {
- ...
- if ( ! kfifo_is_full ( & vsfifo ) ) {
- wake_up_interruptible ( & vsdev.wwqh );
- kill_fasync ( & vsdev.fapp, SIGIO, POLL_OUT );
- }
- ...
- }
- 文件最后一次关闭,在 release 接口中,显式调用驱动实现的 fasync 接口,将节点从链表中删除
- static int vser_release ( struct inode * inode, struct file * flip )
- {
- vser_fasync ( -1, flip, 0 ); /* -1 is useless. */
- return 0;
- }
mmap
int remap_pfn_range (
struct vm_area_struct * vma,
unsigned long addr,
unsigned long pfn,
unsigned long size,
pgprot_t prot
)
将连续的物理地址空间映射到由 vm_area_struct 代表的虚拟空间
vma - 代表虚拟空间的结构指针( 虚拟空间的 起始地址,结束地址,访问权限... )
addr - 虚拟空间的起始地址( 不指定的话,内核将自动指定 )
pfn - 物理地址对应的页框号( 物理地址除以页大小的值 )
phy_addr >> PAGE_SHIFT
size - 需要被映射的空间( 物理地址 )的大小
prot - 此映射的保护标志位
定位操作
loff_t ( * llseek ) ( struct file *, loff_t, int );
file - 文件的 file 结构
loff_t - 偏移量
int - 位置
评论
发表评论