上一节介绍了阻塞,这次是非阻塞IO。
一、非阻塞介绍
应用程序使用非阻塞访问设备时,当设备不可用,就会返回一个错误码,表示数据读取失败,应用程序会重新读取数据,一直循环,直达读到数据。
简单示例:
char buf;
fd = open("/dev/led", O_RDWR | O_NONBLOCK);
...
while(read(fd, &buf, 1) != 1)
printf("%c",buf);
二、轮询
应用程序需要非阻塞的方式,那么驱动程序就要提供非阻塞的处理方式:轮询。poll、epoll、select三个函数。
1.select
函数原型:
int select(int nfds, //所监视三类文件描述符,加一。
fd_set *readfds, //监视描述符集的读变化
fd_set *writefds, //监视描述符集的写变化
fd_set *exceptfds, //监视文件异常
struct timeval *timeout) //超时时间
struct timeval{long tv_sec; //秒
long tv_usec; //微秒
};
三个参数是fd_set类型的,以readfds为例,如果文件可读,则返回一个大于0的值表示可以读取,如果没有可读的,会判断是否超时。可将read设置为NULL,表示不关心读变化。
几个宏操作:
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) //一个文件是否属于某个集合
示例如下:
void main (void)
{
int 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;
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch(ret){
}
}
2.poll函数
在单个线程中,select函数能够监视的最大描述符有上限1024,poll函数没有限制,本质和select没差别。原型如下
int poll(struct pollfd *fds,
nfds_t nfds, //要监视的文件描述符数量
int timeout) //超时时间
struct pollfd{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
events监视的事件类型如下几种:
- POLLIN :有数据可以读取
- POLLPRI:紧急数据需要读取
- POLLOUT:可以写数据
- POLLERR:指定描述符发生错误
- POLLHUP:文件描述符挂起
- POLLNVAL:无效请求
- POLLRDNORM:等同于POLLIN
示例代码如下:
void main(void)
{
int ret fd;
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK);
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500);
switch(ret){
}
}
3.epoll函数
select和poll会随着监听的fd数量增加,出现效率低下的问题,而且poll函数每次要遍历所有描述符来检查,为此,epoll应运而生,epoll为处理大并发而准备的,一般用于网络编程。
创建如下:
int epoll_create(int size) //创建一个epoll句柄(int类型),size随便填入大于0的数
int epoll_ctl(int epfd, //上面的句柄
int op, //对句柄进行的操作
int fd, //文件描述符
struct epoll_event *event) //监视的类型
struct epoll_event{
uint32_t events; //epoll事件
epoll_data_t data; //用户数据
}
op可设置为
- EPOLL_CTL_ADD:向句柄(epfd)添加文件参数fd表示的描述符
- EPOLL_CTL_MOD:修改参数fd 的event事件
- EPOLL_CTL_DEL:从epfd中删除fd描述符
结构体epoll_event的events成员变量表示监视的事件 - EPOLLIN:有数据可读
- EPOLLOUT:可以写数据
- EPOLLPRI:有紧急数据要读
- EPOLLERR:文件描述符发生错误
- EPOLLHUP:文件描述符挂起
- EPOLLET:设置为边沿触发,默认水平触发
- EPOLLONESHOT:一次性监视,监视完后需要重新添加
设置完毕后,可以通过epoll_wait函数来等待事件发生
int epoll_wait(int epfd, //要等待的epoll
struct epoll_event *event, //事件结构体
int maxevents, //events数组大小
int timeout) //超时时间
4.驱动程序里的poll操作
当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序
file_operations 操作集中的 poll 函数就会执行。所以驱动程序的编写者需要提供对应的 poll 函
数, poll 函数原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
返回值状态如下:
- POLLIN :有数据可以读取
- POLLPRI:紧急数据需要读取
- POLLOUT:可以写数据
- POLLERR:指定描述符发生错误
- POLLHUP:文件描述符挂起
- POLLNVAL:无效请求
- POLLRDNORM:等同于POLLIN
需要在驱动程序里的poll函数调用poll_wait函数,poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中,
void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
参数wait_address是要添加到poll_table的等待队列头,参数p是poll函数的wait。
三、驱动程序
应用程序调用poll函数,不断检查。如果返回的事件为可读,则开始调用read函数,否则一直循环。驱动程序的poll函数:将等待队列头添加至poll_table,不会引起阻塞!下面有按键按下的标志位检测,没按下返回0,直到按下。
撷取主干程序:
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */
if(atomic_read(&dev->releasekey) == 0) /* 没有按键按下,返回-EAGAIN */
return -EAGAIN;
} else { /* 阻塞访问 */
/* 加入等待队列,等待被唤醒,也就是有按键按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按键按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;
wait_error:
return ret;
data_error:
return -EINVAL;
}
unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait); /* 将等待队列头添加到poll_table中 */
if(atomic_read(&dev->releasekey)) { /* 按键按下 */
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}
return mask;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.poll = imx6uirq_poll,
};
四、测试App
和前面代码示例相似;
int main(int argc, char *argv[])
{
int fd;
int ret = 0;
char *filename;
struct pollfd fds;
fd_set readfds;
struct timeval timeout;
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("Can't open file %s\r\n", filename);
return -1;
}
#if 0 //poll形式
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN;
while (1) {
ret = poll(&fds, 1, 500);
if (ret) { /* 数据有效 */
ret = read(fd, &data, sizeof(data));
if(ret < 0) {
/* 读取错误 */
} else {
if(data)
printf("key value = %d \r\n", data);
}
} else if (ret == 0) { /* 超时 */
/* 用户自定义超时处理 */
} else if (ret < 0) { /* 错误 */
/* 用户自定义错误处理 */
}
}
#endif
//select形式
while (1) {
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: /* 超时 */
/* 用户自定义超时处理 */
break;
case -1: /* 错误 */
/* 用户自定义错误处理 */
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) {
/* 读取错误 */
} else {
if (data)
printf("key value=%d\r\n", data);
}
}
break;
}
}
close(fd);
return ret;
}