一、阻塞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; }