若应用程序以非阻塞的方式读取,则驱动程序也要以非阻塞的方式返回,也就是轮询的方式。poll、epoll和select函数可以用于处理轮询,应用程序可通过这些函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据
1. 驱动中的poll操作函数
应用程序调用select、poll或epoll函数来对驱动程序进行非阻塞访问时,驱动程序中file_operations操作集中的poll函数就会执行。所以驱动程序中需要提供对应的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,普通数据可读
需要在驱动程序的poll函数中调用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_table,就是file_operations中poll函数的wait参数
2. 非阻塞式I/O程序编写
在用阻塞式I/O模型降低CPU使用率一文的代码基础上进行改编,设备树文件无需修改,只需修改应用程序和驱动程序里的部分代码即可
⏩ 驱动程序poll函数处理部分
......
#define IMX6UIRQ_NAME "noblockio"
......
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_table中 */
poll_wait(filp, &dev->r_wait, wait);
/* 按键按下 */
if(atomic_read(&dev->releasekey)) {
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}
return mask;
}
/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll,
};
⏩ 驱动程序read函数处理部分
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
......
......
/* 非阻塞访问 */
if (filp->f_flags & O_NONBLOCK) {
/* 没有按键按下,返回-EAGAIN */
if(atomic_read(&dev->releasekey) == 0) {
return -EAGAIN;
}
} else { /* 阻塞访问 */
/* 加入等待队列,等待被唤醒,也就是有按键按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
}
......
......
wait_error:
return ret;
data_error:
return -EINVAL;
}
⏩ 应用程序中,使用select函数来实现非阻塞访问
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;
}
/***** 以下代码使用 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;
}
3. 编译测试
⏩ 编译驱动程序:当前目录下创建Makefile文件,并make
编译
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := noblockio.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
⏩ 编译测试程序:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp
⏩ 将驱动文件和测试文件拷贝至rootfs/lib/modules/4.1.15后加载驱动
depmod #第一次加载驱动时,需使用“depmod”命令
modprobe noblockio.ko
⏩使用./noblockioApp /dev/noblockio &
命令,以后台模式运行应用程序,此时按下按键,应用程序会打印出按键值
⏩ 使用top
命令查看noblockioApp的CPU使用率:可见使用非阻塞方式读处理后,CPU的使用率也非常低
⏩ 使用kill
命令关闭后台运行的应用程序后,卸载驱动