当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。而Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。以下有几个接口函数:
1、 等待队列头:
void init_waitqueue_head(wait_queue_head_t *q)
或 宏 DECLARE_WAIT_QUEUE_HEAD
初始化等待队列头。
2、 等待队列项:
struct wait_queue_t结构体
或 宏 DECLARE_WAITQUEUE(name, tsk)
等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
3、 将队列项添加/移除等待队列头:
id add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
和 void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可。
4、 等待唤醒:
void wake_up(wait_queue_head_t *q)
或 void wake_up_interruptible(wait_queue_head_t *q)
当设备可以使用的时候就要唤醒进入休眠态的进程,第一个可以唤醒两种状态进程,第二种只有一种。
5、 等待事件:
wait_event(wq, condition)
或wait_event_timeout(wq, condition, timeout)
或wait_event_interruptible(wq, condition)
或wait_event_interruptible_timeout(wq, condition, timeout)
设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程。
用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。当应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行。
1、 select 函数:
int select(int nfds,fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
2、poll 函数:
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
在单个线程中,select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数,poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制。
3、 epoll 函数:
int epoll_create(int size)
创建一个 epoll 句柄。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
添加要监视的文件描述符以及监视的事件。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
待事件的发生。
4、Linux驱动下的poll操作函数
当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
wait_event_interruptible函数与下面功能是等效的。
1. /* 如果没有按下就将但前线程进入休眠状态(阻塞) */
2. if(!atomic_read(&dev->release)) {
3. /* 定义一个等待队列项,param1:队列名字,param2:当前任务(进程) */
4. DECLARE_WAITQUEUE(wait, current);
5.
6. /* 进入休眠状态 */
7. add_wait_queue(&dev->r_wait,&wait); //将队列项添加到等待队列头
8. __set_current_state(TASK_INTERRUPTIBLE); //当前进程设置为可被打断状态
9. schedule(); //调度器切换,将队列中的进程进入休眠状态,
10. //它会将当前进程从runqueue队列中删除掉,
11. //使其进程不再参与调度,除非使用wake_up让其重新参与调度
12.
13. /* 判断当前进程是否有信号处理,返回值不为0的话表示有信号需要处理,
14. 这个信号不是指按键信号,指例如kill -9这些命令信号
15. */
16. if(signal_pending(current)) {
17. printk("signal_pending\r\n");
18. ret = -ERESTARTSYS;
19. goto data_error;
20. }
驱动程序修改如下:
1. static ssize_t keyirq_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
2. int ret = 0;
3. unsigned char keyvalue;
4. unsigned char release;
5. /* 提取私有属性 */
6. struct keyirq_dev *dev = filp->private_data;
7.
8. /* 5.3 判断是阻塞还是非阻塞方式读取驱动文件 */
9. if(filp->f_flags & O_NONBLOCK) {
10. /* 非阻塞 */
11. if(atomic_read(&dev->release) == 0) {
12. /* 按键值无效 */
13. return -EAGAIN;
14. }
15. } else {
16. /* 阻塞 */
17. wait_event_interruptible(dev->r_wait,atomic_read(&dev->release)); /* 这个可以被信号打断 */
18. }
19.
20. keyvalue = atomic_read(&dev->keyvalue);
21. release = atomic_read(&dev->release);
22.
23. if(release) {
24. if(keyvalue & 0X80) {
25. keyvalue &= ~0X80;
26. ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
27. } else {
28. goto data_error;
29. }
30. atomic_set(&dev->release,0); // 按下标志清零
31. } else {
32. ret = -EINVAL;
33. goto data_error;
34. }
35.
36. return 0;
37. data_error:
38. return ret;
39. }
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* argc:应用程序参数个数
* argv[]:具体打参数内容,字符串形式
* ./timerAPP
* ./timerAPP /dev/keyirq
*/
/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
int fd, ret; /* 要监视的文件描述符 */
char *filename;
unsigned char data;
/* 判断输入的元素个数 */
if(argc != 2) {
printf("ERROR USAGE!\r\n");
return -1;
}
filename = argv[1]; //获取驱动文件的路径
fd = open(filename,O_RDWR | O_NONBLOCK); //根据文件路径以读写方式打开文件,非阻塞式访问
if(fd < 0) {
printf("file %s open failed!\r\n",filename);
return -1;
}
/* 循环读取按键值 */
while(1) {
FD_ZERO(&readfds); /* 清除readfds */
FD_SET(fd, &readfds); /* 将fd添加到readfds里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1,&readfds,NULL,NULL,&timeout);
switch (ret)
{
case 0: /* 超时 */
printf("timeout!\r\n");
break;
case -1: /* 错误 */
printf("error!\r\n");
break;
default: /* 其他的可以读取数据 */
if(FD_ISSET(fd,&readfds)) {
ret = read(fd,&data,sizeof(data));/* 判断是否为fd文件描述符 */
if (ret < 0)
{
} else {
if(data) {
printf("key value = %#x\r\n",data);
}
}
}
break;
}
}
close(fd);
return 0;
}
示例代码 52.1.3.1 select 函数非阻塞读访问示例
1 void main(void)
2 {
3 int ret, fd; /* 要监视的文件描述符 */
4 fd_set readfds; /* 读操作文件描述符集 */
5 struct timeval 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 case 0: /* 超时 */
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 }
示例代码 52.1.3.2 poll 函数读非阻塞访问示例
1 void main(void)
2 {
3 int ret;
4 int fd; /* 要监视的文件描述符 */
5 struct pollfd fds;
6 7
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
8 9
/* 构造结构体 */
10 fds.fd = fd;
11 fds.events = POLLIN; /* 监视数据是否可以读取 */
12
13 ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
14 if (ret) { /* 数据有效 */
15 ......
16 /* 读取数据 */
17 ......
18 } else if (ret == 0) { /* 超时 */
19 ......
20 } else if (ret < 0) { /* 错误 */
21 ......
22 }
23 }