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()函数

头文件

cpp 复制代码
#include <poll.h>

函数定义

cpp 复制代码
/*
 * 监视多个文件描述符的指定事件(注意:不是所有事件)
 * 事件发生时(设备驱动唤醒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文件

cpp 复制代码
#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函数

cpp 复制代码
// 此宏是如何一步一步推导到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值)

cpp 复制代码
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

cpp 复制代码
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函数指针

cpp 复制代码
// 参数2的数据类型其实是 poll_table 结构体的别名
/*
 * filp:要打开的设备文件
 * wait:结构图poll_table_struct类型指针
 */
__poll_t (*poll) (struct file *filp, struct poll_table_struct *wait);
//返回值:文件可用事件类型

poll驱动模板

cpp 复制代码
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)

有可写事件,写入字符"1",点亮rgb红灯

file_operations->poll

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

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

file_operations->write

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

dts_led.c文件

App.c文件

cpp 复制代码
#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};
		
        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){
				int 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文件

执行过程

相关推荐
广而不精zhu小白2 小时前
CentOS Stream 9 挂载Windows共享FTP文件夹
linux·windows·centos
一休哥助手2 小时前
全面解析 Linux 系统监控与性能优化
linux·运维·性能优化
二进制杯莫停2 小时前
掌控网络流量的利器:tcconfig
linux
watl03 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维
赵大仁3 小时前
在 CentOS 7 上安装 Node.js 20 并升级 GCC、make 和 glibc
linux·运维·服务器·ide·ubuntu·centos·计算机基础
vvw&3 小时前
Docker Build 命令详解:在 Ubuntu 上构建 Docker 镜像教程
linux·运维·服务器·ubuntu·docker·容器·开源
冷曦_sole4 小时前
linux-21 目录管理(一)mkdir命令,创建空目录
linux·运维·服务器
最后一个bug4 小时前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
dessler4 小时前
Docker-Dockerfile讲解(二)
linux·运维·docker
卫生纸不够用4 小时前
子Shell及Shell嵌套模式
linux·bash