1 阻塞: 在应用层调用read函数的时候,如果硬件中的数据没有准备好,此时进程会进入休眠状态,当硬件的数据准备好的时候会给驱动发送中断。驱动收到中断之后,唤醒休眠的进程。这个被唤醒的进程在driver_read读取硬件的数据,并把数据 返回到用户空间。(模型中断)
可以使用队列,把整个进程进入到队列中,使进程进入阻塞的状态,当有数据产生的时候,产生中断,唤醒在队列中的进程,从而读取数据.
队列:
wait_queue_head_t wq //定义等待队列头
init_waitqueue_head(&wq)//初始化等待队列头
wait_event(wq, condition) //不可中断的等待态
wait_event_interruptible(wq, condition)//可中断的等待态(当产生中断信号就可以唤醒)
参数:
@wq :等待对列头
@condition :如果条件为假表示可休眠,如果为真表示不休眠
返回值:成功返回0,失败返回错误码
w
ake_up(&wq)
wake_up_interruptible(&wq)
唤醒休眠
condition = 1;
分析:wake_up_interruptible()唤醒后,wait_event_interruptible(wq, condition)宏,自身再检查“condition”这个条件以决定是返回还是继续休眠,真(1)则返回,假(0)则继续睡眠,不过这个程序中若有中断程序的话,中断来了,还是会继续执行中断函数的。只有当执行wake_up_interruptible()并且condition条件成立时才会把程序从队列中唤醒。
==========================================================
user
fd = open("hello",O_RDWR); //阻塞的方式打开文件
read(fd,buf,sizeof(buf));
-----------------------------------------------------
kernel:
fops:driver_read()
{
//2.阻塞方式打开的
那进入休眠状态(等待唤醒)
3.读取数据拷贝数据
}
========================================================



2 非阻塞:在应用层调用read函数的时候,不管硬件的数据是否准备好都需要立返回到用户空间
open()函数默认的是阻塞模式,我们需要把它设置成非阻塞模式
int open(const char *pathname, int flags);
参数1:文件的路径
参数2:打开的的方式如下图

======================================================
user
fd = open("hello",O_RDWR|O_NONBLOCK); //非阻塞的方式打开文件
read(fd,buf,sizeof(buf));
-----------------------------------------------------------------
kernel:
fops:driver_read()
{
1.检查用户打开驱动的设备文件的方式
if(file->f_flags & O_NONBLOCK&!esswait)
return -EINVAL;
//非阻塞方式打开
2.读取数据,向用户空间拷贝数据
}
===========================================================
3 I/O多路复用(select/poll/epoll)

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数1 :一个结构体指针 struct pollfd *fds
struct pollfd {
int fd; //文件描述符
short events; //产生的事件如读写错误
POLLIN 读写
POLLOUT 写
POLLERR 出错
short revents; //结果描述,表示当前的fd是否可读,写初错
//用于判断出错
POLLIN 读写
POLLOUT 写
POLLERR 出错
参数2 nfds:要监视的描述符的数目
参数3 timeout 超时时间 是指定poll在返回前没有接收事件时应该等待的时间。
timeout值:
INFTIM 永远等待(为负数)
0 立即返回,不阻塞进程
>0 等待指定数目的毫秒数
返回值:
负数:表示出错
大于0 表示fd有数据
小于0 表示时间到
如果在应用中使用了poll对设备文件进行了监控,那么在驱动中必须必须实现poll
unsigned int xxx_poll(struct file *filp,struct poll_table_struct *pts)
{
//返回一个mask数值
unsigned int mask;
//调用poll_wait,将当前的等待队列注册系统中
poll_wait();
//当没有数据的时候返回一个0(通过判断队列的状态)
//当有数据的时候返回一个POLLIN
}
================================================================
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功 能:select用于监测是哪个或哪些文件描述符产生件;
参数:nfds: 监测的最大文件描述个数
readfds: 读事件集合; //读
writefds: 写事件集合; //NULL表示不关心
exceptfds: 异常事件集合(带外集合); //一般为NULL
timeout: 超时设置. NULL:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回
struct timeval {
long tv_sec; /* seconds */秒
long tv_usec; /* microseconds */微妙
};
select返回值: <0 出错
>0 表示有事件产生;
==0 表示超时时间已到
注意:当select()函数退出后,集合表示有数据的集合
当select()函数退出前,集合表示描述符的集合
void FD_CLR(int fd, fd_set *set);//把fd从集合中清除
int FD_ISSET(int fd, fd_set *set);//判断fd是否在在集合中
void FD_SET(int fd, fd_set *set);//把描述符插入到集合中
void FD_ZERO(fd_set *set);//对集合清零
if( FD_ISSET(int fd, fd_set *set))//判断fd是否在集合中
{
}
//在我们调用select时进程会一直阻塞直到以下的一种情况发生.
有文件可以读.
有文件可以写.
超时所设置的时间到

===============================================================
epoll的使用(现在常用的是epoll)
man epoll
#include <sys/epoll.h>
功能:创建epoll的实例
int epoll_create(int size);
参数:
@size :无效了
返回值:成功返回epoll实例的文件描述符,失败-1;
//功能:向epoll实例中添加文件描述符,或者从中删除文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
@ epfd :epoll实例的文件描述符
@ op :EPOLL_CTL_ADD 添加文件描述符
EPOLL_CTL_MOD 更正对文件描述符监听的事件
EPOLL_CTL_DEL 删除文件描述符
@ fd :想要添加的文件描述符
@event :epoll_event
typedef union epoll_data {
int fd;
} epoll_data_t;
struct epoll_event {
uint32_t events; //监听的事件的类型EPOLLIN读/EPOLLOUT写
epoll_data_t data; //fd
};
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
返回值:成功返回0,失败返回-1
//功能:阻塞等待文件描述符对应驱动的数据是否准备好 //死等
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
参数:
@epfd :e poll实例的文件描述符
@events :被返回的epoll_event
@maxevents :监听文件描述符的最大值
@timeout :超时(-1表示或略超时时间)
返回值:>0 :返回文件描述符的个数
=0 :超时
-1 :失败了
注意:支持管道,FIFO,套接字,POSIX消息队列,终端,设备等,但是就是不支持普通文件或目录的
==============================================================
4 异步通知
在应用层使用signal为一个信号绑定一个处理函数,应用层执行signal之后接着往下执行。当硬件中的数据准备好的时候硬件会给驱动发送中断,驱动收到中断后给应用程序发送信号,应用程序收到信号后执行信号处理函数,并在信号处理函数中调用read函数读取数据。

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
参数1 signum信号量
参数2 handler处理函数‘
功能描述:根据文件描述词来操作文件的特性。
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
描述: fcntl()针对(文件)描述符提供控制.参数fd是被参数cmd操作(如下面的描述)的描述符.
针对cmd的值,fcntl能够接受第三个参数(arg)
参数1 fd 文件描述符
参数2 cmd
复制一个现有的描述符(cmd=F_DUPFD).
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
注意:在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现在的标志值 然后按照希望修改它,
最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。
[返回值]
fcntl()的返回值
与命令有关
:
如果出错,所有命令都返回-1,如果成功则返回某个其他值。下列三个命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
F_DUPFD 返回新的文件描述符
F_GETFD 返回相应标志
F_GETFL , F_GETOWN 返回一个正的进程ID或负的进程组ID
cmd值的F_GETFL和F_SETFL :
F_GETFL取得fd的文件状态标志,如同下面的描述一样(arg被忽略),在说明open函数时,已说明了文件状态标志。不幸的是,三个存取方式标志 (O_RDONLY , O_WRONLY , 以及O_RDWR)并不各占1位。(这三种标志的值各是0 , 1和2,由于历史原因,这三种值互斥 — 一个文件只能有这三种值之一。) 因此首先
必须用屏蔽字O_ACCMODE相与取得存取方式位,然后将结果与这三种值相比较。
F_SETFL 设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND,O_NONBLOCK,O_SYNC 和 O_ASYNC。
而fcntl的文件状态标志总共有7个:O_RDONLY , O_WRONLY , O_RDWR , O_APPEND , O_NONBLOCK , O_SYNC和O_ASYNC
可更改的几个标志如下面的描述:
O_NONBLOCK 非阻塞I/O,如果read(2)调用没有可读取的数据,或者如果write(2)操作将阻塞,则read或write调用将返回-1和EAGAIN错误
O_APPEND 强制每次写(write)操作都添加在文件大的末尾,相当于open(2)的O_APPEND标志
O_DIRECT 最小化或去掉reading和writing的缓存影响。系统将企图避免缓存你的读或写的数据。如果不能够避免缓存,那么它将最小化已经被缓存了的数据造成的影响。如果这个标志用的不够好,将大大的降低性能
O_ASYNC 当I/O可用的时候,允许SIGIO信号发送到进程组,例如:当有数据可以读的时候
cmd值的F_GETOWN和F_SETOWN:
F_GETOWN 取得当前正在接收SIGIO或者SIGURG信号的进程id或进程组id,进程组id返回的是负值(arg被忽略)
F_SETOWN 设置将接收SIGIO和SIGURG信号的进程id或进程组id,进程组id通过提供负值的arg来说明(arg绝对值的一个进程组ID),否则arg将被认为是进程id
=-====================================================================
应用层:
1. signal(SIGIO,信号的处理函数);
2. fcntl(fd,F_SETFL,fcntl(fd, F_GETFL)|FASYNC);//重新设置文件状态的标志
3. fcntl(fd,F_SETOWN,getpid());//设置将接收SIGIO和SIGURG信号的进程id或进程组id
---------------------------------------------------------------------------------
驱动----发送信号
1
需要和进程进行关联--记录信号发送给谁
实现一个fasync()的接口
int key_drv_fasync (int fd, struct file *file, int on);
{
//记录信号发送给谁
return fasync_helper(fd,file,on,&key->fasync);
}
然后在特定的环境中发送信号(函数),当有数据的时候:
kill_fasync(struct fasync_struct * * fp, int sig, int band);