高级IO模型
一般对于底层硬件的中断,一般表示有数据发生,如果要去读取该中断产生的数据值,则需要通过文件io在进程上下文中进行交互(read),文件io交互的方式由分为5种模型:
- 非阻塞(O_NONBLOCK,一旦调用立刻执行,若没有数据则返回-EAGAIN错误码,提醒重试)
- 阻塞(休眠加入等待队列)
- 多路复用(select、poll、epoll)对应驱动fileoperation对象的poll方法
- 异步io
- 异步通知(fasync)信号实现
- 轮询(while循环,占CPU高)
我们应该对通过中断事件类型来选择交互模型
阻塞与非阻塞模型
驱动中需要对应用中打开文件标识(非阻塞与阻塞的标识)进行判断,file对象中记录文件的成员f_flag,通过逻辑与操作进行判断。
注意:休眠下的唤醒操作默认将唤醒指定等待队列中的所有进程。
对于休眠的状态分为以下几种:
- interruptible:休眠时可通过信号唤醒
- timeout:超时限制
- exclusive:具有排他性,唤醒该进程时不会唤醒队列中其他进程。
- locked:先获得锁才能唤醒队列。
非阻塞关键代码:
非阻塞且没有数据则返回。
if(fp->f_flags & O_NONBLOCK && 没数据)
{
return -EAGAIN;
}
阻塞休眠关键代码:
//初始化等待队列的链表头
wait_queue_head_t wq_head;
init_waitqueue_head(&wq_head);
volatile int condition;
//当前进程加入等待队列,condition 为0 则阻塞休眠, conditon一般定义为volatile类型
wait_event_interruptible(wq_head, condition);
//中断发生后,产生数据,唤醒进程
condition = 1;
wake_up_interruptible(wq_head);
多路复用模型
进程在休眠中是无法做其他操作,当进程同时对多个设备操作时,一个设备阻塞其他将无法操作,这就是阻塞状态下的缺点,他控制整个进程。
多路复用在单线程与单进程中对多个文件进行交互,类似监控,只要有一个发生则非阻塞执行。
驱动fileoperation对象的poll方法 —对应— 应用层的 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
对于应用层poll用法,读者最好在我的博客linux系统编程中学习,比较全,这里只讲述思路
对于驱动层,将阻塞休眠等待队列注册到poll中进行监控,其中等待队列分为读等待队列和写等待队列,需要注意若没有指定poll方法,应用层调用poll会默认设备驱动有事件发生。
应用层关键代码:
struct pollfd pfd[2];//监控两个设备文件
pfd[0].fd = fd1;
pfd[0].events = POLLIN;
pfd[1].fd = fd2;
pfd[1].events = POLLIN;
maxfd = fd1 > fd2 ? fd1 : fd2;
ret = poll(pfd, 2, -1);
if(!ret){
perror("poll");
exit(1);
}else{
for(i=0;i<2;i++){
if(pfd[i].revents & POLLIN){
//有数据可读
read(pfd[i].fd, &event, sizeof(key_event));
//对数据进行处理
xxxxxx
}
}
}
驱动关键代码:
因为唤醒默认会对所有等待队列中进程唤醒,因此读和写需分为两个队列。
unsigned int my_poll(struct file *fp, struct poll_table_struct *pts)
{
//返回mask值,表示是否有数据(0/POLLIN)
unsigned int mask = 0;
//poll_wait将当前读事件等待队列注册到系统
poll_wait(fp, wq_read, pts);
if(_无数据_){
mask = 0;
}
if(_有数据_){
mask |= POLLIN | POLLRDNORM;
}
return mask;
}
异步io
应用程序中对提交完io操作后立即返回,继续做其他操作,具有非阻塞型,底层收到应用层的提交后开始操作,当底层io操作完成给提交者发送信号,或调用注册的回调函数,告知请求者io操作已完成。
这里不做讲述
异步通知
类似于异步io,资源可用时只能向应用层发信号,不能直接调用应用层注册的回调函数。
应用程序不用主动去对设备文件进行读,设备文件发生事件时自己通知应用层的进程去读取。类似于中断与硬件之间的关系,这里是驱动与应用的关系。
应用层:
//信号处理方法
void catch_signal(int signo)
{
if(signo == SIGIO)
{
//读数据
read(fd, &buf, sizeof(buf));
//处理操作
}
}
//设置信号处理方法
signal(SIGIO, catch_signal);
//将当前进程设置成SIGIO的属主进程
fcntl(fd, F_SETOWN, getpid());
//将io设置成异步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);//传参给驱动中fasync方法调用
驱动添加文件io方法fasync:
struct fasync_struct *fasync;
//定义fasync接口,用于发送给应用层进程
int my_fasync(int fd, struct file *fp, int on)
{
//只需要调用一个函数记录信号该发送给谁,和进程相关联
return fasync_helper(fd, fp, on, &fasync);
}
//当有数据时通过fasync接口向指定进程发生信号
kill_fasync(&fasync, SIGIO, POLLIN);
//release方法中
my_fasync(-1, fp, 0);