19-IO多路复用 poll

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知

1. poll简介

 阻塞型IO相对于非阻塞型IO来说,最大的有点是资源不可用是进程主动放弃CPU让其他的进程运行,而不用不停的轮询,提高系统的效率,但是缺点也是比较明显的就是进程阻塞之后不能做其他的事情,这在一个进程要同时对多个设备进行操作时非常不便。解决这个问题的方法有很多,比如多进程、多线程和I/O多路复用,I/O复用有select、poll和Linux所持有的epoll三种方式,select、poll和epoll可以用于处理轮询,应用程序通过 select、epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用 select、epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。本节继续说明poll的用法。
 在单个线程中,select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数,poll 函数本质上和 select 没有太大的差别,但是poll 函数没有最大文件描述符限制。

2. poll函数接口

2.1 应用层poll接口

 应用层poll函数的原型如下:

原    型: int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功    能: 
@param1: 要监视的文件描述符集合以及要监视的事件为一个数组,数组元素都是结构体 pollfd类型的,pollfd 结构体如下所示:
		  struct pollfd {
		 	  int   fd;		  // 文件描述符,如果fd无效的话那么events监视事件也就无效,并且revents返回0
			  short events;	  // 请求的事件
			  short revents;  // 返回的事件,由Linux 内核设置具体的返回事件
		  };
		 可监视的事件类型如下所示:
	 	   POLLIN 			  // 有数据可以读取。
	 	   POLLPRI	 		  // 有紧急的数据需要读取。
	 	   POLLOUT  		  // 可以写数据。
	 	   POLLERR  		  // 指定的文件描述符发生错误。
	 	   POLLHUP  		  // 指定的文件描述符挂起。
	 	   POLLNVAL 		  // 无效的请求。
	 	   POLLRDNORM   	  // 等同于 POLLIN
@param2: poll 函数要监视的文件描述符数量
@param3: 超时时间,单位为ms
@return: 返回revents域中不为0的pollfd结构体个数,也就是发生事件或错误的文件描述符数量;
         0表示超时;
         -1发生错误并设置errno为错误类型

2.2 驱动中poll接口

 在驱动程序的 poll 函数中调用 poll_wait 函数,poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中,poll_wait 函数原型如下:

原    型: void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
功    能: 将应用程序添加到poll_table中
@param1: 打开的设备文件
@param2: 要添加到poll_table中的等待队列头
@param3: poll_table指针,就是file_operations 中 poll 函数的 wait 参数
@return: 无返回值

3. 示例代码

3.1 demo.c

 在 vser_poll 函数中,调用 poll_wait 函数将应用程序添加到 poll_table 中,如果监听的设备(虚拟串口设备)发生事件,FIFO不为空的时候返回POLLIN表示设备可读,然后应用程序中会依据返回的值进行判断,执行对应的操作。

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/poll.h>

#define VSER_CHRDEV_ANME ("vser_chrdev")
#define VSER_CLASS_ANME  ("vser_cls")
#define VSER_DEVICE_ANME ("vser_dev_28")
#define KFIFO_SIZE (16)
#define FLAG (0)

struct vser_dev{
	dev_t dev_no;
	int major;
	int minor;
	struct cdev cdev;
	struct class *cls;
	struct device *dev;
	wait_queue_head_t rwqh;		// 定义读的等待队列头 
	wait_queue_head_t wwqh;		// 定义写的等待队列头
};
struct vser_dev test_vser_dev;

DEFINE_KFIFO(vser_fifo, char, KFIFO_SIZE);	// 声明定义一个虚拟串口

static int vser_open(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	filp->private_data = &test_vser_dev;
	
	return 0;
}

static int vser_release(struct inode *indoe, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num, ret;
	struct vser_dev *test_vser_dev = filp->private_data;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if ( kfifo_is_empty(&vser_fifo) )		// kfifo为空返回真
	{
		printk("kfifo_is_empty.\n");
		if ( filp->f_flags & O_NONBLOCK )	// 如果非阻塞方式打开直接返回
		{
			printk("O_NONBLOCK.\n");
			ret = -EAGAIN;	// try again
		}
		
		// condition 条件不成立的时候进程休眠,即kfifo为空时进程休眠
		if ( wait_event_interruptible(test_vser_dev->rwqh, !kfifo_is_empty(&vser_fifo)) < 0 )
		{
			printk("line num = %d.\n", __LINE__);
			ret = -ERESTARTSYS;
		}

		goto err0;
	}

	printk("kfifo is not empty.\n");
	if (size > KFIFO_SIZE)
	{
		size = KFIFO_SIZE;	// 判断拷贝内容的大小
	}
		
	ret = kfifo_to_user(&vser_fifo, userbuf, size, &copied_num);	// kfifo不为空将数据拷贝到用户空间
	if (ret < 0)
	{
		printk("kfifo_to_user failed.\n");
		ret = -EFAULT;	// Bad Address
		goto err0;
	}
	printk("%s copied_num = %d.\n", __FUNCTION__, copied_num);

	if ( !kfifo_is_full(&vser_fifo) )					// kfifo不为满
	{
		printk("line num = %d.\n", __LINE__);
		wake_up_interruptible(&test_vser_dev->wwqh);	// 唤醒写的等待队列头
	}

	return copied_num;

err0:
	return ret;
}

static ssize_t vser_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	unsigned int copied_num = 0;
	unsigned int ret = 0;	
	struct vser_dev *test_vser_dev = filp->private_data;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if ( kfifo_is_full(&vser_fifo) )		// kfifo为满返回真
	{
		printk("kfifo_is_full.\n");
		if ( filp->f_flags & O_NONBLOCK )	// 判断是否以非阻塞方式打开
		{
		    printk("%s -- O_NONBLOCK.\n", __FUNCTION__);
			ret = -EAGAIN;
			goto err0;
		}

		if (wait_event_interruptible(test_vser_dev->wwqh, !kfifo_is_full(&vser_fifo)) < 0)
		{
			printk("line num = %d.\n", __LINE__);
			ret = -ERESTARTSYS;
		}
		goto err0;
	}

	if (size > KFIFO_SIZE)
	{
		size = KFIFO_SIZE;
	}
		
	ret = kfifo_from_user(&vser_fifo, userbuf, size, &copied_num);	// kfifo不为满,则将用户空间数据拷贝到内核空间
	if (ret == -EFAULT)
	{
		printk("kfifo_from_user failed.\n");
		goto err0;
	}
	printk("%s -- copied_num = %d.\n", __FUNCTION__, copied_num);

	if ( !kfifo_is_empty(&vser_fifo) )
	{
		printk("line num = %d.\n", __LINE__);
		wake_up_interruptible(&test_vser_dev->rwqh);		// 唤醒读的等待队列
	}

	return copied_num;

err0:
	return ret;
}

unsigned int vser_poll(struct file *filp, struct poll_table_struct *wait)
{
	struct vser_dev *test_vser_dev = filp->private_data;
	unsigned int mask = 0;
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	poll_wait(filp, &test_vser_dev->rwqh, wait);	
	poll_wait(filp, &test_vser_dev->wwqh, wait);

	if ( !kfifo_is_empty(&vser_fifo) )
	{
		printk("line num = %d.\n", __LINE__);
		mask |= (POLLIN | POLLRDNORM);
	}

	return mask;
}

struct file_operations vser_fops = 
{
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
	.poll = vser_poll,
};

static int __init vser_init(void)
{
	int ret;
	
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	if (test_vser_dev.major)
	{
		test_vser_dev.dev_no = MKDEV(test_vser_dev.major, 0);
		ret = register_chrdev_region(test_vser_dev.dev_no, 1, VSER_CHRDEV_ANME);
		if (ret < 0)
		{
			printk("register_chrdev_region failed.\n");
			goto register_chrdev_region_err;
		}
	}
	else
	{
		ret = alloc_chrdev_region(&test_vser_dev.dev_no, 0, 1, VSER_CHRDEV_ANME);
		if (ret < 0)
		{
			printk("alloc_chrdev_region failed.\n");
			goto alloc_chrdev_region_err;
		}
	}

	cdev_init(&test_vser_dev.cdev, &vser_fops);
	ret = cdev_add(&test_vser_dev.cdev, test_vser_dev.dev_no, 1);
	if (ret < 0)
	{
		printk("cdev_add failed.\n");
		goto cdev_add_err;
	}

	test_vser_dev.cls = class_create(THIS_MODULE, VSER_CLASS_ANME);
	if ( IS_ERR(test_vser_dev.cls) )
	{
		printk("class_create failed.\n");
		ret = PTR_ERR(test_vser_dev.cls);
		goto class_create_err;
	}
	
	test_vser_dev.dev = device_create(test_vser_dev.cls, NULL, test_vser_dev.dev_no, NULL, VSER_DEVICE_ANME);
	if ( IS_ERR(test_vser_dev.dev) )
	{
		printk("device_create failed.\n");
		ret = PTR_ERR(test_vser_dev.dev);
		goto device_create_err;
	}

	init_waitqueue_head(&test_vser_dev.rwqh);	// 初始化读的等待队列头
	init_waitqueue_head(&test_vser_dev.wwqh);	// 初始化写的等待队列头
	
	return 0;

device_create_err:
	class_destroy(test_vser_dev.cls);
class_create_err:
	cdev_del(&test_vser_dev.cdev);
cdev_add_err:
	unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
alloc_chrdev_region_err:
register_chrdev_region_err:
	return ret;
}

static void __exit vser_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	device_destroy(test_vser_dev.cls, test_vser_dev.dev_no);
	class_destroy(test_vser_dev.cls);
	cdev_del(&test_vser_dev.cdev);
	unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
}

module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");

3.2 test.c

 test.c 是测试程序,在19行定义了 pollfd 结构体的数组,并将其请求的事件定义为POLLIN(代码25和35行),返回的时间初始化为0(代码26行和36行)。
 在代码40行调用了poll函数,其中参数一要比fd的最大值大1,监听两个设备,超时时间设置为10s。在代码的44行和61行分别判断是那个设备产生的时间然后进行对应的操作

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>

#define TIMEOUT (1000*10)	// 超时时间 10s
#define DEVICE_NUM (2)

int main(int argc, char *argv[])
{
	int ret;				// 要监视的文件描述符
	char r_buf[16];	
	struct pollfd fds[DEVICE_NUM];	// 定义pollfd结构体,要监视的文件描述集的集合

	// 打开自己定义的设备
	fds[0].fd = open("/dev/vser_dev_28", O_RDWR | O_NONBLOCK);	
	if (fds[0].fd < 0)
	{
		perror("open");
		return -1;
	}
	fds[0].events = POLLIN;		// 监视设备是否可以读取
	fds[0].revents = 0;

	// 打开触摸屏设备
	fds[1].fd = open("/dev/input/touchscreen0", O_RDWR | O_NONBLOCK);
	if (fds[1].fd < 0)
	{
		perror("open");
		return -1;
	}
	fds[1].events = POLLIN;		// 监视设备是否可读
	fds[1].revents = 0;

	while(1)
	{
		ret = poll(fds, DEVICE_NUM, TIMEOUT);	// 超时时间10s
		printf("ret = %d\n", ret);
		if (ret > 0)
		{
			if (fds[0].revents & POLLIN)
			{
				ret = read(fds[0].fd, r_buf, sizeof(r_buf));
				if (ret > 0)			// 读到内容
				{
					printf("r_buf = %s\n", r_buf);
				}
				else if (ret == 0)		// 读到文件末尾
				{	
					printf("read end of file\n");
					printf("r_buf = %s\n", r_buf);
				}
				else					// 读取失败
				{
					perror("read");
				}
			}
			if (fds[1].revents & POLLIN)
			{
				printf("touch screen\n");
			}
		}
		else if (ret == 0)		// 超时
		{
			printf("timeout\n");
		}
		else					// 出错
		{
			perror("poll");
		}
	}

	close(fds[0].fd);
	close(fds[1].fd);

	return 0;
}

3.3 Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)

EXEC = app
OBJS = test.o
CC   = arm-linux-gnueabihf-gcc

$(EXEC):$(OBJS)
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	$(CC) $^ -o $@
.o:.c
	$(CC) -c $<

install:
	sudo cp *.ko  /tftpboot
	sudo cp app  /tftpboot
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

3.4 测试结果

root@am335x-evm:~# insmod demo.ko 	// 加载模块
[  139.768133] vser_init -- 184.
root@am335x-evm:~# ./app &			// 后台运行应用程序
[1] 873
root@am335x-evm:~# [  143.445605] vser_open -- 34.
[  143.449182] vser_poll -- 157.	// 应用层的poll调用驱动层的vser_poll函数
[  153.459194] vser_poll -- 157.
ret = 0								// 在10s内没有向FIFO中写入任何数据,返回poll超时
timeout
[  153.464956] vser_poll -- 157.
									// 敲回车键,显示出命令行,往FIFO中写入数据
root@am335x-evm:~# echo "123" > /dev/vser_dev_28	// 往FIFO中写入数据
[  160.976397] vser_open -- 34.		
[  160.979752] vser_write -- 107.	// 调用驱动中的write函数
[  160.982988] vser_write -- copied_num = 4.
[  160.992330] line num = 142.
[  160.998717] vser_poll -- 157.
[  161.001746] line num = 164.		// FIFO不为空,返回POLLIN
ret = 1
[  161.004885] vser_read -- 53.		// 监视的fds[0].revents返回POLLIN,调用read函数
root@am335x-evm:~# [  161.020793] vser_release -- 43.
[  161.035843] kfifo is not empty.
[  161.041620] vser_read copied_num = 4.
[  161.045340] line num = 91.		// 将内核空间数据拷贝到用户空间
r_buf = 123							// 打印读取到的数据
[  161.056083] vser_poll -- 157.
[  171.066131] vser_poll -- 157.
ret = 0								// 超时
[  171.069562] vser_poll -- 157.
timeout
[  181.079602] vser_poll -- 157.
ret = 0
[  181.082982] vser_poll -- 157.
timeout

root@am335x-evm:~# echo "789" > /dev/vser_dev_28
[  190.720722] vser_open -- 34.
[  190.724107] vser_write -- 107.
[  190.733238] vser_write -- copied_num = 4.
[  190.744285] line num = 142.
[  190.749471] vser_poll -- 157.
[  190.752500] line num = 164.
ret = 1
[  190.755543] vser_read -- 53.
root@am335x-evm:~# [  190.771853] vser_release -- 43.
[  190.786381] kfifo is not empty.
[  190.794032] vser_read copied_num = 4.
[  190.801033] line num = 91.
r_buf = 789
[  190.804055] vser_poll -- 157.

root@am335x-evm:~# ps -aux			// 查看后台运行的进程
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.7  1.5   5232  3932 ?        Ss   20:20   0:05 /sbin/init
root         2  0.0  0.0      0     0 ?        S    20:20   0:00 [kthreadd]
... ...
root       873  0.1  0.3   1340   876 ttyS0    S    20:22   0:00 ./app
root       883  0.5  0.4   1812  1192 ?        Ss   20:23   0:00 /sbin/agetty -8 -L ttyS
root       884  0.0  0.5   2636  1276 ttyS0    R+   20:23   0:00 ps -aux
root@am335x-evm:~# kill -9 873		// 杀死./app进程
[  206.857174] vser_poll -- 157.
root@am335x-evm:~# [  206.886797] vser_release -- 43.
[1]+  Killed                  ./app	
root@am335x-evm:~# rmmod demo.ko 	// 卸载模块
[  210.412966] vser_exit -- 248.
发布了57 篇原创文章 · 获赞 64 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_36310253/article/details/103142121