阻塞非阻塞
IO指的是文件的输入输出
当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式IO就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
对于非阻塞IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃, 会有超时处理
应用程序使用open打开驱动文件默认是用的是阻塞模式打开, 可以使用参数O_NONBLOCK打开文件,
等待队列
当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。
等待队列是若干个休眠进程的集合
task想等待某种事件,那么调用wait_event(等待队列,事件)就可以了。
本质上就是:将进程线程状态设置为TASK_UNINTERRUPTIBLE
状态,然后调用schedule将本线程程序调度出去。
等待队列头
在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t表示, 在文件include/linux/wait.h
中
39 struct __wait_queue_head {
40 spinlock_t lock;
41 struct list_head task_list;
42 };
43 typedef struct __wait_queue_head wait_queue_head_t;
定义好等待队列头以后需要初始化,使用init_waitqueue_head函数初始化等待队列头
void init_waitqueue_head(wait_queue_head_t *q)
也可以使用宏DECLARE_WAIT_QUEUE_HEAD来一次性完成等待队列头的定义的初始化。
等待队列项
当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面
struct __wait_queue {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
使用宏DECLARE_WAITQUEUE定义并初始化一个等待队列项,宏的内容如下
DECLARE_WAITQUEUE(name, tsk)
name就是等待队列项的名字,tsk表示这个等待队列项属于哪个任务(进程),一般设置为current,在Linux内核中current相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。
把队列项添加到等待队列头
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
q:等待队列项要加入的等待队列头。wait:要加入的等待队列项
这里要使用这种格式
__set_current_state(TASK_INTERRUPTIBLE);
schedule(); //执行__schedule()这个函数是调度的核心处理函数,当前CPU会选择到下一个合适的进程去执行了, 这是上面的函数的拆分
//唤醒以后运行
if(signal_pending(current)) //判断是不是被信号唤醒的, 是的话退出
{
ret = -ERESTARTSYS;
goto data_error;
}
data_error:
__set_current_state(TASK_RUNNING); //切换状态
remove_wait_queue(&&dev->r_wait, &wait); //移除对应
设置当前进程为可以被打断的
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t*wait)
q:要删除的等待队列项所处的等待队列头。wait:要删除的等待队列项。
唤醒
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
q就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。
进入休眠
这种用于是由头部的休眠状态
轮询
主要对应的是非阻塞方式, poll、epoll和select可以用于处理轮询, 当应用程序调用select、epoll或poll函数的时候设备驱动程序中的poll函数就会执行
以下的函数使用在应用中
select函数
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout)
nfds:最大文件描述符加一, readfds、writefds和exceptfds:这三个指针指向描述符集合,这三个参数指明了关心哪些描述符、需要满足哪些条件等等,这三个参数都是fd_set类型的,fd_set类型变量的每一个位都代表了一个文件描述符。timeout:超时时间, 使用结构体timeval表示
readfds用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么seclect就会返回一个大于0的值表示文件可以读取。如果没有文件可以读取,那么就会根据timeout参数来判断是否超时。可以将readfs设置为NULL,表示不关心任何文件的读变化。writefds和readfs类似,只是writefs用于监视这些文件是否可以进行写操作。exceptfds用于监视这些文件的异常
void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)
FD_ZERO用于将fd_set变量的所有位都清零
FD_SET用于将fd_set变量的某个位置1,也就是向fd_set添加一个文件描述符,参数fd就是要加入的文件描述符
FD_CLR用于将fd_set, 变量的某个位清零,也就是将一个文件描述符从fd_set中删除
FD_ISSET用于测试一个文件是否属于某个集合,参数fd就是要判断的文件描述符
struct timeval {
long tv_sec; /* 秒*/
long tv_usec; /* 微秒*/
};
返回值:0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作;-1,发生错误;其他值,可以进行操作的文件描述符个数
1 void main(void)
2 {
3 intret, fd;/* 要监视的文件描述符*/
4 fd_set readfds;/* 读操作文件描述符集*/
5 structtimeval timeout;/* 超时结构体*/
6
7 fd =open("dev_xxx",O_RDWR |O_NONBLOCK);/* 非阻塞式访问*/
8
9 FD_ZERO(&readfds);/* 清除readfds */
10 FD_SET(fd,&readfds);/* 将fd添加到readfds里面*/
11
12 /* 构造超时时间*/
13 timeout.tv_sec =0;
14 timeout.tv_usec =500000;/* 500ms */
15
16 ret =select(fd +1,&readfds,NULL,NULL,&timeout);
17 switch(ret){
18 case0:/* 超时*/
19 printf("timeout!\r\n");
20 break;
21 case-1:/* 错误*/
22 printf("error!\r\n");
23 break;
24 default:/* 可以读取数据*/
25 if(FD_ISSET(fd,&readfds)){/* 判断是否为fd文件描述符*/
26 /* 使用read函数读取数据*/
27 }
28 break;
29 }
30}
poll函数
select函数能够监视的文件描述符数量有最大的限制,一般为1024, 可以使用poll函数,poll函数本质上和select没有太大的差别,但是poll函数没有最大文件描述符限制
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
nfds:poll
函数要监视的文件描述符数量。timeout:超时时间,单位为ms
struct pollfd {
int fd;/* 文件描述符*/
short events; /* 请求的事件*/
short revents; /* 返回的事件*/
};
fd是要监视的文件描述符, events是要监视的事件, 返回值:返回
revents
域中不为0的pollfd
结构体个数,也就是发生事件或错误的文件描述符数量;0,超时;-1,发生错误POLLIN 有数据可以读取。 POLLPRI 有紧急的数据需要读取。 POLLOUT可以写数据。 POLLERR指定的文件描述符发生错误。 POLLHUP指定的文件描述符挂起。 POLLNVAL无效的请求。 POLLRDNORM等同于POLLIN
while(1)
{
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500); //设置时间是500ms
if(ret==0)
{//错误
}
else if(ret<0)
{//超时
}
else
{
if(fds.revents | POLLIN){//检测结果是可读
ret = read(fd, &data, sizeof(data));
if(ret<0)
{
}else
{
if(data)
{
printf("keyvalue = %#X\r\n", data);
}
}
}
}
}
epoll函数
epoll就是为处理大并发而准备的,一般常常在网络编程中使用epoll函数
pull操作函数
select或poll函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations操作集中的poll函数就会执行
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
filp:要打开的设备文件(文件描述符)。wait:结构体poll_table_struct类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait函数。返回值如下
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT可以写数据。
POLLERR指定的文件描述符发生错误。
POLLHUP指定的文件描述符挂起。
POLLNVAL无效的请求。
POLLRDNORM等同于
POLLIN,普通数据可读
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中
参数wait_address是要添加到poll_table中的等待队列头,参数p就是poll_table,就是file_operations中poll函数的wait参数
后台
static unsigned int imx6uirq_poll(struct file *filp, poll_table *wait)
{
int mask = 0;
struct imx6uirq_dev *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if(atomic_read(dev->realsekey))
{
//按键按下可读
mask = POLLIN | POLLRDNORM;
}
return mask;
}
最终调用的都是这个函数, 自己测试大概是每一次定义时间开始结束的时候调用一次, 根据返回值, 上面的函数进行分析得出结果