网站首页 > 技术文章 正文
本序列涉及的 Linux 源码都是基于 linux-4.14.143 。
1. 文件抽象 与 poll 操作
1.1 文件抽象
在 Linux 内核里,文件是一个抽象,设备是个文件,网络套接字也是个文件。
文件抽象必须支持的能力定义在 file_operations 结构体里。
在 Linux 里,一个打开的文件对应一个文件描述符 file descriptor/FD,FD 其实是一个整数,内核把进程打开的文件维护在一个数组里,FD 对应的是数组的下标。
文件抽象的能力定义:
// 源码位置:include/linux/fs.h struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); // 对于 select/poll/epoll 最重要的实现基础 // 非阻塞的轮询文件状态的函数 unsigned int (*poll) (struct file *, struct poll_table_struct *); // 省略其他函数指针 } __randomize_layout; // 源码位置:include/linux/poll.h typedef struct poll_table_struct { // 文件的 file_operations.poll 实现一定会调用的队列处理函数 poll_queue_proc _qproc; // poll 操作敢兴趣的事件 unsigned long _key; } poll_table; // poll 队列处理函数 typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
1.2 文件 poll 操作
poll 函数的原型:
unsigned int (*poll) (struct file *, poll_table *); /** * 如果 poll_table 有回调函数,则回调它。 * * @filp 要监听的目标文件 * @wait_address 要监听事件的等待队列头 * @p select/poll/epoll 调用里传入里的等待节点 */ static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) { if (p && p->_qproc && wait_address) p->_qproc(filp, wait_address, p); }
文件抽象 poll 函数的具体实现必须完成两件事(这两点算是规范了):
1. 在 poll 函数敢兴趣的等待队列上调用 poll_wait 函数,以接收到唤醒;具体的实现必须把 poll_table 类型的参数作为透明对象来使用,不需要知道它的具体结构。
2. 返回比特掩码,表示当前可立即执行而不会阻塞的操作。
下面是某个驱动的 poll 实现示例,来自:https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch05s03.html:
unsigned int scull_p_poll(struct file *filp, poll_table *wait) { Scull_Pipe *dev = filp->private_data; unsigned int mask = 0; /* * The buffer is circular; it is considered full * if "wp" is right behind "rp". "left" is 0 if the * buffer is empty, and it is "1" if it is completely full. */ int left = (dev->rp + dev->buffersize - dev->wp) % dev->buffersize; // 在不同的等待队列上调用 poll_wait 函数 poll_wait(filp, &dev->inq, wait); poll_wait(filp, &dev->outq, wait); /* readable */ if (dev->rp != dev->wp) mask |= POLLIN | POLLRDNORM; /* writable */ if (left != 1) mask |= POLLOUT | POLLWRNORM; return mask; }
2. poll 的等待与唤醒
poll 函数接收的 poll_table 只有一个队列处理函数 _qproc 和感兴趣的事件属性 _key。
文件抽象的具体实现在构建时会初始化一个或多个 wait_queue_head_t 类型的事件等待队列 。
poll 等待的过程:
- poll 函数被调用时,其实现肯定会调用 poll_wait,进而调用到 _qproc 函数。
- _qproc 负责构建包含 wait_queue_entry 结构体的等待节点(比如 select 操作是 poll_table_entry 结构体),并把 wait_queue_entry 添加到要监听文件的等待队列 wait_address 上(wait_queue_entry 结构体指定了事件发生时的唤醒函数,比如 select 操作里指定的是 pollwake 函数)。
- poll 函数返回文件当前可立即执行而不阻塞的操作表示码。
事件发生时的唤醒过程:
- 当事件发生时,文件的具体实现遍历等待队列,调用其唤醒函数,由唤醒函数进行具体的唤醒操作,唤醒函数的类型为 typedef int (*wait_queue_func_t)(struct wait_queue_entry *wq_entry, unsigned mode, int flags, void *key)。
- 具体的唤醒函数实现根据 wait_queue_entry 找到 _qproc 函数里构建的等待节点,利用其数据判断是否需要唤醒,是则唤醒等待进程。
一个小困惑:
唤醒函数是如何根据 wait_queue_entry 找到真实的等待节点呢??
这是借助内核的一个宏 container_of 实现的,container_of 是指针的一个灵活应用,作用是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。
猜你喜欢
- 2024-11-03 linux 内核poll/select/epoll实现剖析(经典)-下
- 2024-11-03 Linux I/O复用中select poll epoll模型的介绍及其优缺点的比较
- 2024-11-03 IO多路复用之select、poll、epoll之间的区别总结
- 2024-11-03 网络编程——C++实现socket通信(TCP)高并发之select模式
- 2024-11-03 Java面试八股文Netty网络编程,select
- 2024-11-03 「Linux网络编程」TCP并发服务器的实现(IO多路复用select)
- 2024-11-03 linux 内核poll/select/epoll实现剖析(经典)-上
- 2024-11-03 I/O复用 - select&poll(i/o复用不会阻塞用户进程)
- 2024-11-03 深入学习IO多路复用 select/poll/epoll 实现原理
- 2024-11-03 linux并发服务器模型三、Select(linux高并发服务器)
- 最近发表
-
- Windows平台搭建C/C++开发环境-Eclipse入门
- 16、开发工具eclipse的安装和使用
- 50个常见 Java 错误以及如何避免它们
- Java程序员常用的快捷键:30个Eclipse键盘快捷键分享
- eclipse运行时报错端口被占用处理
- 能让 Windows 10 运行 Android 应用的 "Project Astoria" 是这么回事
- 看了必收藏的Eclipse下载安装与配置教程(图文详解)
- Win10上跑安卓:微软VS Android模拟器独立版下载
- Java EE更名Jakarta EE 无法提供向前兼容性
- java中一半是天使一半是魔鬼的Unsafe类详解
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)