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文件

执行过程

相关推荐
青草地溪水旁几秒前
Linux epoll 事件模型终极指南:深入解析 epoll_event 与事件类型
linux·epoll
..过云雨25 分钟前
04.【Linux系统编程】基础开发工具2(makefile、进度条程序实现、版本控制器Git、调试器gdb/cgdb的使用)
linux·笔记·学习
zzzsde36 分钟前
【Linux】初识Linux
linux·运维·服务器
渡我白衣44 分钟前
Linux网络:应用层协议http
linux·网络·http
pofenx1 小时前
使用nps创建隧道,进行内网穿透
linux·网络·内网穿透·nps
Ronin3051 小时前
【Linux系统】单例式线程池
linux·服务器·单例模式·线程池·线程安全·死锁
desssq1 小时前
ubuntu 18.04 泰山派编译报错
linux·运维·ubuntu
Lzc7741 小时前
Linux的多线程
linux·linux的多线程
清风笑烟语1 小时前
Ubuntu 24.04 搭建k8s 1.33.4
linux·ubuntu·kubernetes
Dovis(誓平步青云)2 小时前
《Linux 基础指令实战:新手入门的命令行操作核心教程(第一篇)》
linux·运维·服务器