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.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章