16.poll机制

目录

POLL机制

同步阻塞IO和异步阻塞IO

同步阻塞IO

异步阻塞IO

文件I/O事件类型

poll()函数

头文件

函数定义

实验环节

app.c文件

执行过程

poll函数底层机制

SYSCALL_DEFINE3函数

do_sys_poll()函数

do_poll()函数

poll函数指针

poll驱动模板

相关结构体框图

实验环节:poll实验

实验设计

dts_led.c文件

app.c文件

Makefile文件

执行过程


POLL机制

poll机制的底层实现原理:基于等待队列来实现。

poll()函数,poll()函数底层函数接口有一个对应的函数指针。当调用poll()函数的时候,会找到对应的file_operations的成员变量poll,最终会调用poll成员变量指向的函数指针。

同步阻塞IO和异步阻塞IO

同步阻塞IO

应用层调用一个 read / write 对一个文件进行读写操作时, read / write可能使当前的进程或线程进入一个休眠态(进程 / 线程阻塞在一个文件的读写操作上),陷入休眠态的进程 / 线程只能通过对应的文件设备驱动唤醒自己。

异步阻塞IO

在应用层调用poll()函数时,poll()函数会对多个文件进行轮询操作,去查看每一个文件是否有特定的事件。若所有文件都没有发生特定的事件,那么poll()函数会阻塞当前进程 / 线程。

poll()函数文件操作涉及多个文件的轮询,所以poll()函数引起的进程 / 线程休眠,可以会被多个设备驱动唤醒。

多个文件对应多个设备驱动。阻塞在多个文件的轮询操作上(poll),可以被多个设备驱动唤醒。

文件I/O事件类型

事件类型:可读、可写、异常...

poll()函数的目的是查询每个文件的I/O事件,没查到就会引起休眠。这些事件是由设备驱动产生,产生后会唤醒由poll()函数导致的进程 / 线程休眠,poll()函数也能把发生的事件返回给用户空间。

poll()函数

头文件
#include <poll.h>
函数定义
/*
 * 监视多个文件描述符的指定事件(注意:不是所有事件)
 * 事件发生时(设备驱动唤醒poll函数导致休眠的进程/线程),把发生的具体事件通知给用户程序
 * fds:数组类型详见下
 * nfds:pollfd数组的元素个数,要监控的文件描述符数量
 * timeout:超时时间(ms),不希望进程线程一直休眠在poll函数里
 */
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
 * 返回值:
 *    成功:发生事件的文件数量,超时返回0
 *    失败:-1
 */

struct pollfd
{
	int fd;        // 要监视的文件描述符
  	short events;  // 指定要监视的请求事件类型,通过设置一系列的宏来描述  	
 	short revents; // 返回的事件类型,内核设置具体的返回事件,记录实际发生的事件
};

events监视的事件:

        POLLIN:系统内核通知应用层指定数据已经准备好,读数据不会被阻塞

        POLLPRI:有紧急的数据需要被读取

        POLLOUT:系统内核通知应用层IO缓冲区已经准备好,写数据不会被阻塞

        POLLERR:指定的文件描述符发生错误,必须先解决错误才能对文件做其他的处理

        POLLNVAL:无效的请求

        ...

实验环节

app.c文件

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h> 

int main(int argc, char *argv[])
{
        struct pollfd fds = {0};
		fds.fd = 0;	//标准输入
		fds.events = POLLIN;
		
		int ret = poll(&fds, 1, 5000);	//监视1个数组,5s超时
		if(ret == -1){
			printf("poll error!\n");
		}else if(ret){
			printf("data is ready!\n");
		}else if(ret == 0){
			printf("time out!\n");
		}

        return 0;
}

执行过程

gcc app.c -o App

sudo ./App(回车等待5s)

sudo ./App &

cat

2

poll函数底层机制

SYSCALL_DEFINE3函数

// 此宏是如何一步一步推导到sys_poll见下
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,int, timeout_msecs)
{
	struct timespec64 end_time, *to = NULL;
	int ret;

	if (timeout_msecs >= 0) {
		to = &end_time;
		poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
			NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
	}
	// 关键
	ret = do_sys_poll(ufds, nfds, to);
	...
	return ret;
}

do_sys_poll()函数

该函数保存在内核/fs/select.c文件中。

函数执行过程:

复制用户空间pollfd数组到内核空间

        分配静态数组内存(一个poll_list结构体)

        动态分配内存(一组poll_list结构体)

调用do_poll()函数

返回修改后的pollfd数组到用户空间(主要是返回修改后的revents值)

static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec64 *end_time)
{
	struct poll_wqueues table;
 	int err = -EFAULT, fdcount, len, size;
 	// 此宏定义详见下
 	long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
	struct poll_list *const head = (struct poll_list *)stack_pps;
 	struct poll_list *walk = head;
 	// 用户空间调用poll函数的第二个参数,数组长度
 	unsigned long todo = nfds;
 	...
 	// 获取静态分配的数组大小,取最小值
 	// 判断可以存放多少个用户空间传进来的pollfd结构体
 	len = min_t(unsigned int, nfds, N_STACK_PPS);
	for (;;) {
		walk->next = NULL;
		walk->len = len;
		if (!len)
			break;

		if (copy_from_user(walk->entries, ufds + nfds-todo,
					sizeof(struct pollfd) * walk->len))
			goto out_fds;

		todo -= walk->len;
		if (!todo)
			break;
		// 计算剩下的文件描述符所需空间大小,最大为一个页
		len = min(todo, POLLFD_PER_PAGE);
		size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
		walk = walk->next = kmalloc(size, GFP_KERNEL);
		if (!walk) {
			err = -ENOMEM;
			goto out_fds;
		}
	}
 	// 
 	poll_initwait(&table);
 	// 详见下,fdcount为所有发生事件的文件数量
 	// 为了返回修改后的pollfd数组到用户空间
	fdcount = do_poll(head, &table, end_time);
	poll_freewait(&table);
	
	// 在此遍历poll_list(链表)
	for (walk = head; walk; walk = walk->next) {
		struct pollfd *fds = walk->entries;
		int j;
		// 遍历每个poll_list元素的pollfd数组
		for (j = 0; j < walk->len; j++, ufds++)
			// 返回事件值给用户空间
			if (__put_user(fds[j].revents, &ufds->revents))
				goto out_fds;
  	}
	...
}

do_poll()函数

该函数保存在内核/fs/select.c文件中。

函数里面的三重循环:

        第一重:确保线程 / 进程被唤醒后,继续执行一次循环体内容

        第二重:遍历一组poll_list

        第三重:遍历每一组poll_list的一组pollfd

static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
		   struct timespec64 *end_time)
{
	poll_table* pt = &wait->pt;
	ktime_t expire, *to = NULL;
	int timed_out = 0, count = 0;
	u64 slack = 0;
	__poll_t busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
	unsigned long busy_start = 0;
	...
	for (;;) {
		struct poll_list *walk;
		bool can_busy_loop = false;
		// 二重:遍历 poll_list 结构体(链表)
		for (walk = list; walk != NULL; walk = walk->next) {
			struct pollfd * pfd, * pfd_end;

			pfd = walk->entries;
			pfd_end = pfd + walk->len;
			// 三重:遍历 pollfd 结构体(数组)
			for (; pfd != pfd_end; pfd++) {
				if (do_pollfd(pfd, pt, &can_busy_loop,
					      busy_flag)) {
					 // 返回值不为0,count++,表示有返回事件的文件数量
					 // 返回值不为0,文件没有发生任何事件
					 // 返回值为0,发生了某(些)事件
					count++;
					pt->_qproc = NULL;
					/* found something, stop busy polling */
					busy_flag = 0;
					can_busy_loop = false;
				}
			}
		}
		pt->_qproc = NULL;
		if (!count) {
			count = wait->error;
			// 检查当前进程或者线程是否有信号处理
			if (signal_pending(current))
				count = -EINTR;
		}
		// 下面的break会跳出最外层循环
		if (count || timed_out)
			break;
		...
		// 真正使当前进程或者线程休眠的函数
		// 若timeout为1,下一次最外层循环将会从上面的break跳出
		// 此函数是阻塞的,当等待的事件发生时,会从此处继续向下执行
		if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
			timed_out = 1;
	}
	return count;
}

poll函数指针

// 参数2的数据类型其实是 poll_table 结构体的别名
/*
 * filp:要打开的设备文件
 * wait:结构图poll_table_struct类型指针
 */
__poll_t (*poll) (struct file *filp, struct poll_table_struct *wait);
//返回值:文件可用事件类型
poll驱动模板
static __poll_t xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	// 详见下,注意第二个参数就是等待队列头
	poll_wait(filp, &yyy, wait);

	// 判断驱动程序里面发生了哪些条件,这些条件对应哪些文件的事件,设置好事件后,return回去
	if(...)
	{
		mask |= POLLOUT | ...;
	}
	return mask;
}

// 参数2:等待队列头
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
	if (p && p->_qproc && wait_address)
		// 其实就 上面初始化为 __pollwait 函数
		// 参数2指定等待队列头
		p->_qproc(filp, wait_address, p);
}

相关结构体框图

实验环节:poll实验

实验设计

App应用程序调用poll()函数检测/dev/rgb_led设备文件的可写事件

        无可写事件,进程一直休眠(poll)

        有可写事件,写入字符“0”,点亮rgb红灯

file_operations->poll

        监视write_data全局变量值(0-无事件发送,1-返回可写事件)

        指定唤醒App进程的等待队列头(poll_wait)

file_operations->write

        判断write_data全局变量值(0-点亮rgb红灯,1-唤醒poll函数引起休眠的App进程)

dts_led.c文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
 
#include <linux/string.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/poll.h>

#include <asm/mach/map.h>
#include <asm/io.h>
 
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
 
#define DEV_NAME        "rgb_led"
#define DEV_CNT         (1)
  
int rgb_led_red;
int rgb_led_green;
int rgb_led_blue;
 
wait_queue_head_t wait_queue;
unsigned int write_data = 0;
 
static dev_t led_devno;
static struct cdev led_chrdev;
struct class *class_led;
struct device *device;
struct device_node *rgb_led_device_node;
 
static int led_chrdev_open(struct inode *inode, struct file *filp)
{
        printk("open form driver\n");
        return 0;
}
 
static ssize_t led_chrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
        int ret, error;
        unsigned char receive_data[10];         //用于保存接收到的数据
 
        if(cnt > 10)    cnt = 10;
 
        error = copy_from_user(receive_data, buf, cnt);
        if(error < 0)   return -1;
 
        ret = kstrtoint(receive_data, 16, &write_data);
        if(ret)         return -1;
 
        if(write_data){
                wake_up(&wait_queue);
                return cnt;
        }else{
                gpio_set_value(rgb_led_red, 0);
        }
 
        return cnt;
}
 
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
        printk(KERN_ALERT "finished!!!\n");
 
        return 0;
}

__poll_t led_chrdev_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	
	poll_wait(filp, &wait_queue, wait);
 
	if(write_data)
	{
		mask |= POLLOUT;
	}
	return mask;
}

static struct file_operations led_chrdev_fops = {
        .owner = THIS_MODULE,
        .open = led_chrdev_open,
        .write = led_chrdev_write,
        .release = led_chrdev_release,
		.poll = led_chrdev_poll,
};
 
static int led_probe(struct platform_device *pdv)
{
        int ret = -1;   //保存错误状态码
        unsigned int register_data = 0;
 
        printk(KERN_ALERT "match successed!\n");
 
        /* 获取rgb_led的设备树节点 */
        rgb_led_device_node = of_find_node_by_path("/rgb_led");
        if(rgb_led_device_node == NULL){
                printk(KERN_ERR "get rgb_led failed!\n");
                return -1;
        }
 
        /* 获取red led GPIO 引脚号 */
        rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
        if(rgb_led_red < 0){
                printk(KERN_ERR "rgb_led_red failed!\n");
                return -1;
        }
 
        /* 获取green led GPIO 引脚号 */
        rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);
        if(rgb_led_green < 0){
                printk(KERN_ERR "rgb_led_green failed!\n");
                return -1;
        }
 
                /* 获取blue led GPIO 引脚号 */
        rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);
        if(rgb_led_blue < 0){
                printk(KERN_ERR "rgb_led_blue failed!\n");
                return -1;
        }
 
        /* 设置GPIO为输出模式,并默认高电平 */
        gpio_direction_output(rgb_led_red, 1);
        gpio_direction_output(rgb_led_green, 1);
        gpio_direction_output(rgb_led_blue, 1);
 
        /* 第一步
         * 采用动态分配的方式获取设备编号,次设备号为0
         * 设备名称为rgb-leds,可通过命令cat /proc/devices查看
         * DEV_CNT为1,当前只申请一个设备编号
         */
        ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
		if(ret < 0){
                printk("fail to alloc led_devno\n");
                goto alloc_err;
        }
 
        /* 第二步
         * 关联字符设备结构体cdev与文件操作结构体file_operations
         */
        led_chrdev.owner = THIS_MODULE;
        cdev_init(&led_chrdev, &led_chrdev_fops);
 
        /* 第三步
         * 添加设备到cdev_map哈希表中
         */
        ret = cdev_add(&led_chrdev, led_devno, DEV_CNT);
        if(ret < 0){
                printk("fail to add cdev\n");
                goto add_err;
        }
 
        /* 第四步:创建类 */
        class_led = class_create(THIS_MODULE, DEV_NAME);
 
        /* 第五步:创建设备 */
        device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
 
        return 0;
 
alloc_err:
        return -1;
add_err:
        //添加设备失败时,需要注销设备号
        unregister_chrdev_region(led_devno, DEV_CNT);
        printk("error!\n");
}
 
static const struct of_device_id rgb_led[] = {
        {.compatible = "fire,rgb_led"},
        {/* sentinel */}
};
 
/* 定义平台设备结构体 */
struct platform_driver led_platform_driver = {
        .probe = led_probe,
        .driver = {
                .name = "rgb-leds-platform",
                .owner = THIS_MODULE,
                .of_match_table = rgb_led,
        }
};
 
static int __init led_platform_driver_init(void)
{
        int DriverState;
 
        init_waitqueue_head(&wait_queue);
        DriverState = platform_driver_register(&led_platform_driver);
        printk(KERN_ALERT "DriverState is %d\n", DriverState);
 
        return 0;
}
 
static void __exit led_platform_driver_exit(void){
        /* 销毁设备 */
        device_destroy(class_led, led_devno);
        /* 删除设备号 */
        cdev_del(&led_chrdev);
        /* 取消注册字符设备 */
        unregister_chrdev_region(led_devno, DEV_CNT);
        /* 销毁类 */
        class_destroy(class_led);
 
                platform_driver_unregister(&led_platform_driver);
 
        printk(KERN_ALERT "led_platform_driver exit\n");
}
 
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("couvrir");
MODULE_DESCRIPTION("led module");
MODULE_ALIAS("led module");

app.c文件

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>

int main(int argc, char *argv[])
{
		struct pollfd fds = {0};
		int error;

        if(argc != 2){
                printf("commend error!\n");
                return -1;
        }
 
        int fd = open("/dev/rgb_led", O_RDWR);
        if(fd < 0){
                printf("open file:/dev/rgb_led failed!!!\n");
                return -1;
        } 

		fds.fd = fd;
		fds.events = POLLOUT;

		if(poll(&fds, 1, -1) < 0)	//一直休眠
				printf("poll error!\n");

		if(fds.revents & POLLOUT){
				error = write(fd, argv[1], sizeof(argv[1]));
				if(error < 0){
						printf("write file error!\n");
						close(fd);
				}
		}

        
 
        error = close(fd);
        if(error < 0){
                printf("close file error!\n");
        }
 
        return 0;
}

Makefile文件

照旧

执行过程

虚拟机:

执行makemake copy。生成.ko文件。

开发板(在挂载目录下执行):

sudo insmod dts_led.ko

sudo ./App 0 &(设备文件没有可写事件产生,poll函数使当前App进程休眠)

sudo sh -c "echo 1 > /dev/rgb_led"(设备文件产生可写事件,App应用程序写入字符‘0’,rgb红灯被点亮)

sudo rmmod dts_led.ko

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值