一、阻塞I/O
应用程序的阻塞访问方式
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR);
ret = read(fd, &data, sizeof(data));
阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,这样可以将CPU资源让出来,直到等待的条件被满足。而设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成。
示例:阻塞方式读取鼠标
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[100];
int fd, ret;
fd = open("/dev/input/event3", O_RDONLY)
if (-1 == fd) {
perror("open error");
exit(-1);
}
memset(buf, 0, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if (0 > ret) {
perror("read error");
close(fd);
exit(-1);
}
printf("suceess to read<%d> bytes data\n", ret);
close(fd);
exit(0);
}
二、非阻塞I/O
应用程序的非阻塞访问方式
int fd;
int data - 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK);
ret = read(fd, &data, sizeof(data));
示例:非阻塞方式读取鼠标
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[100];
int fd, ret;
fd = open("/dev/input/event3", O_RDONLY|O_NONBLOCK)
if (-1 == fd) {
perror("open error");
exit(-1);
}
memset(buf, 0, sizeof(buf));
for(;;) {
ret = read(fd, buf, sizeof(buf));
if (0 < ret) {
printf("suceess to read<%d> bytes data\n", ret);
close(fd);
exit(0);
}
}
}
三、驱动程序对阻塞进程的唤醒
等待队列wait queue实现了阻塞进程的唤醒工作。
等待队列要用到的数据结构:
1、使用等待队列,首先创建和初始化一个等待队列头,队列头结构体定义如下:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
2、每一个要访问设备的进程是一个队列项,队列项结构体:
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;
等待队列API函数:
1、初始化等待队列头:
void init_waitqueue_head(wait_queue_head_t *q);
使用宏 DECLARE_WAIT_QUEUE_HEAD直接一次性完成定义和初始化,如下:
DECLARE_WAIT_QUEUE_HEAD(q);
2、初始化等待队列项:
使用宏DECLARE_WAITQUEUE可定义和初始化一个等待队列项:
DECLARE_WAITQUEUE(wait, current);
current是当前进程。
3、将队列项添加到等待队列头:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
4、将队列项移除等待队列头:
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
5、唤醒等待队列头
唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程
void wake_up(wait_queue_head_t *q);
唤醒处于TASK_INTERRUPTIBLE状态的进程:
void wake_up_interruptible(wait_queue_head_t *q);
6、等待事件
等待以wq为等待队列头的等待队列被唤醒,将进程设置为TASK_INTERRUPTIBLE可被信号打断:
wait_event_interruptible(wq,condition)
增加了超时时间,以jiffers为单位,如果返回0表示超时时间到且conditon为假 :
wait_event_interruptible(wq,condition,timeout)
这些宏在内核头文件include/linux/wait.h中。
示例:设备驱动中使用等待队列
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
struct xxx_dev *dev = (struct xxx_dev *)filp->private_data;
ret = wait_event_interruptible(dev->r_wait, condition);
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&dev->r_wait, &wait);
avail = device_readable(...);
if (avail < 0) {
_set_current_state(TASK_INTERRUPTIBLE);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out;
}
__set_current_state(TASK_RUNNING);
remove_wait_queue(&dev->r_wait, &wait);
}
device_read(...);
out:
remove_wait_queue(&dev->r_wait, &wait);
__set_current_state(TASK_RUNNING);
}
四、非阻塞I/O应用程序轮询
应用程序通过select、epoll、poll函数来查询设备是否可以操作。
1、select
int select(int nfds,
fd_set *readfds,
fd_set *writedfds,
fd_set *exceptfds,
struct timeval *timeout);
nfds:三类文件描述符集合里最大值加一,文件描述符从 0 开始编号,select 需要检查的范围是 [0, nfds),即 0 到 nfds - 1。
fd_set类型变量的每一位对应一个fd:
readfds时监视描述符集的读变化;
writefds监视文件是否可写;
exceptfds监视文件的异常。
上面的三个参数如果设置为NULL就表示不关心这些文件的变化。
fd_set变量的操作宏,定义在 sys/select.h:
FD_ZERO(fdsetp)
FD_SET(fd, fdsetp)
FD_CLR(fd, fdsetp)
FD_ISSET(fd, fdsetp)
timeout设置超时时间,如果时NULL表示无期限等待。
struct timeval {
long tv_sec;
long tv_usce;
};
返回值0表示超时发生,但没有任何文件描述符可操作;返回值-1表示有错误发生;返回其他值表示可进行操作的文件描述符个数。
示例:应用程序使用select函数非阻塞访问读
void main(void)
{
int ret, fd;
fd_set readfds;
struct timeval timeout;
fd = open("dev_xxx", O_RDWR | O_NONBLOCK);
FD_ZERO(&readfds);
FD_SET(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)) {
/* read data */
}
break;
}
}
2、poll
单个线程中有,select函数能够监视的文件描述符数最大限制为1024,这时就可以使用poll函数,poll函数没有最大文件描述符限制。
int poll(struct pollfd *fds,
nfds_t nfds,
int timeout)
nfds是要监视的文件描述符数量;
timeout单位是ms;
fds是要监视的文件描述符集合以及要监视的事件。
struct pollfd {
int fd;
short events;
short revents;
};
fd是要监视的文件描述符;
events是要监视的事件:
revents是返回参数,即返回的事件。
poll函数的返回值
返回-1表示有错误发生,并且会设置errno;返回0表示该调用在任意一个文件描述符成为就绪态之前就超时了;返回一个正整数表示有一个或多个文件描述符处于就绪态了,返回值表示fds数组中返回的revents变量不为0的struct pollfd对象的数量。
示例:应用程序使用poll函数非阻塞访问读
void main(void)
{
int ret;
int fd;
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK);
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500);
if (ret) {
[...]
/* read data */
[...]
} else if (ret == 0) {
/* timer out */
} else if (ret < 0) {
/* error */
}
}
五、驱动程序对select和poll非阻塞访问的行为
设备驱动程序提供非阻塞处理方式也就是轮询。当应用程序调用select或poll函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations操作集中的poll函数就会执行。
unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait)
filp是要打开的设备文件(文件描述符);
wait是由应用程序传递进来的,一般将此函数传递给poll_wait函数。
void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
参数wait_address是要添加到poll_table中的等待队列头;
参数p是file_operations中poll函数的wait参数。
示例:设备驱动poll()函数
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
struct xxx_dev *dev = (struct xxx_dev *)filp->private_data;
if (filp->f_flags & O_NONBLOCK) {
avail = device_readable(...);
if (avail < 0) {
ret = -EAGAIN;
} else {
ret = wait_event_interruptible(dev->r_wait, condition);
if (ret) {
retrun ret;
}
}
}
device_read(...);
ret 0;
}
unsigned int xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = (struct xxx_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
avail = device_readable(...);
if (avail)
mask = POLLIN | POLLRDNORM;
return mask;
}