Linux poll 和 select 机制

news2025/1/18 20:22:20

poll select 介绍

使用非阻塞 I/O 的应用程序常常使用 poll, select, 和 epoll 系统调用. poll,
select 和 epoll 本质上有相同的功能: 每个允许一个进程来决定它是否可读或者写一个
或多个文件而不阻塞. 这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来
读或写. 因此, 它们常常用在必须使用多输入输出流的应用程序, 而不必粘连在它们任何
一个上. 相同的功能常常由多个函数提供, 因为 2 个是由不同的团队在几乎相同时间完
成的: select 在 BSD Unix 中引入, 而 poll 是 System V 的解决方案. epoll 调用
[23]23
添加在 2.5.45, 作为使查询函数扩展到几千个文件描述符的方法.
支持任何一个这些调用都需要来自设备驱动的支持. 这个支持(对所有 3 个调用)由驱动
的 poll 方法调用. 这个方法由下列的原型:
unsigned int (*poll) (struct file *filp, poll_table *wait);
这个驱动方法被调用, 无论何时用户空间程序进行一个 poll, select, 或者 epoll 系统
调用, 涉及一个和驱动相关的文件描述符. 这个设备方法负责这 2 步:
•  1. 在一个或多个可指示查询状态变化的等待队列上调用 poll_wait. 如果没有文
件描述符可用作 I/O, 内核使这个进程在等待队列上等待所有的传递给系统调用的
文件描述符.
•  2. 返回一个位掩码, 描述可能不必阻塞就立刻进行的操作.
这 2 个操作常常是直接的, 并且趋向与各个驱动看起来类似. 但是, 它们依赖只能由驱
动提供的信息, 因此, 必须由每个驱动单独实现.
poll_table 结构, 给 poll 方法的第 2 个参数, 在内核中用来实现 poll, select, 和
epoll 调用; 它在 <linux/poll.h>中声明, 这个文件必须被驱动源码包含. 驱动编写者
不必要知道所有它内容并且必须作为一个不透明的对象使用它; 它被传递给驱动方法以便
驱动可用每个能唤醒进程的等待队列来加载它, 并且可改变 poll 操作状态. 驱动增加一
个等待队列到 poll_table 结构通过调用函数 poll_wait:
void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
poll 方法的第 2 个任务是返回位掩码, 它描述哪个操作可马上被实现; 这也是直接的.
例如, 如果设备有数据可用, 一个读可能不必睡眠而完成; poll 方法应当指示这个时间
状态. 几个标志(通过 <linux/poll.h> 定义)用来指示可能的操作:
23 [23]  实际上, epoll 是一组 3 个调用, 都可用来获得查询功能. 但是, 由于我们的目的, 我们可认为它是一个调用.
LINUX DEVICE DRIVERS,3RD EDITION
136
POLLIN
如果设备可被不阻塞地读, 这个位必须设置.
POLLRDNORM
这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回
( POLLIN|POLLRDNORM ).
POLLRDBAND
这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方
( DECnet 代码 )并且通常对设备驱动不可用.
POLLPRI
高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异
常情况, 因为 selct 报告带外数据作为一个异常情况.
POLLHUP
当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用
select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.
POLLERR
一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写
都返回一个错误码而不阻塞.
POLLOUT
这个位在返回值中设置, 如果设备可被写入而不阻塞.
POLLWRNORM
这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备
返回( POLLOUT|POLLWRNORM).
POLLWRBAND
如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的
数据报实现使用这个位, 因为一个数据报看传送带外数据.
应当重复一下 POLLRDBAND 和 POLLWRBAND 仅仅对关联到 socket 的文件描述符有意义:
通常设备驱动不使用这些标志.
poll 的描述使用了大量在实际使用中相对简单的东西. 考虑 poll 方法的 scullpipe 实
现:
LINUX DEVICE DRIVERS,3RD EDITION
137
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
这个代码简单地增加了 2 个 scullpipe 等待队列到 poll_table, 接着设置正确的掩码
位, 根据数据是否可以读或写.
所示的 poll 代码缺乏文件尾支持, 因为 scullpipe 不支持文件尾情况. 对大部分真实
的设备, poll 方法应当返回 POLLHUP 如果没有更多数据(或者将)可用. 如果调用者使用
select 系统调用, 文件被报告为可读. 不管是使用 poll 还是 select, 应用程序知道它
能够调用 read 而不必永远等待, 并且 read 方法返回 0 来指示文件尾.
例如, 对于 真正的 FIFO, 读者见到一个文件尾当所有的写者关闭文件, 而在 scullpipe
中读者永远见不到文件尾. 这个做法不同是因为 FIFO 是用作一个 2 个进程的通讯通道,
而 scullpipe 是一个垃圾桶, 人人都可以放数据只要至少有一个读者. 更多地, 重新实
现内核中已有的东西是没有意义的, 因此我们选择在我们的例子里实现一个不同的做法.
与 FIFO 相同的方式实现文件尾就意味着检查 dev->nwwriters, 在 read 和 poll 中,
并且报告文件尾(如刚刚描述过的)如果没有进程使设备写打开. 不幸的是, 使用这个实现
方法, 如果一个读者打开 scullpipe 设备在写者之前, 它可能见到文件尾而没有机会来
等待数据. 解决这个问题的最好的方式是在 open 中实现阻塞, 如同真正的 FIFO 所做的;

与 read 和 write 的交互

poll 和 select 调用的目的是提前决定是否一个 I/O 操作会阻塞. 在那个方面, 它们补
充了 read 和 write. 更重要的是, poll 和 select , 因为它们使应用程序同时等待几
个数据流, 尽管我们在 scull 例子里没有采用这个特性.
LINUX DEVICE DRIVERS,3RD EDITION
138
一个正确的实现对于使应用程序正确工作是必要的: 尽管下列的规则或多或少已经说明过,
我们在此总结它们.

  从设备中读数据


•  如果在输入缓冲中有数据, read 调用应当立刻返回, 没有可注意到的延迟, 即便
数据少于应用程序要求的, 并且驱动确保其他的数据会很快到达. 你可一直返回小
于你被请求的数据, 如果因为任何理由而方便这样(我们在 scull 中这样做), 如
果你至少返回一个字节. 在这个情况下, poll 应当返回 POLLIN|POLLRDNORM.
•  如果在输入缓冲中没有数据, 缺省地 read 必须阻塞直到有一个字节. 如果
O_NONBLOCK 被置位, 另一方面, read 立刻返回 -EAGIN (尽管一些老版本 SYSTEM
V 返回 0 在这个情况时). 在这些情况中, poll 必须报告这个设备是不可读的直
到至少一个字节到达. 一旦在缓冲中有数据, 我们就回到前面的情况.
•  如果我们处于文件尾, read 应当立刻返回一个 0, 不管是否阻塞. 这种情况 poll
应该报告 POLLHUP.


写入设备


•  如果在输出缓冲中有空间, write 应当不延迟返回. 它可接受小于这个调用所请求
的数据, 但是它必须至少接受一个字节. 在这个情况下, poll 报告这个设备是可
写的, 通过返回 POLLOUT|POLLWRNORM.
•  如果输出缓冲是满的, 缺省地 write 阻塞直到一些空间被释放. 如果 O_NOBLOCK
被设置, write 立刻返回一个 -EAGAIN(老式的 System V Unices 返回 0). 在这
些情况下, poll 应当报告文件是不可写的. 另一方面, 如果设备不能接受任何多
余数据, write 返回 -ENOSPC("设备上没有空间"), 不管是否设置了 O_NONBLOCK.
•  在返回之前不要调用 wait 来传送数据, 即便当 O_NONBLOCK 被清除. 这是因为许
多应用程序选择来找出一个 write 是否会阻塞. 如果设备报告可写, 调用必须不
阻塞. 如果使用设备的程序想保证它加入到输出缓冲中的数据被真正传送, 驱动必
须提供一个 fsync 方法. 例如, 一个可移除的设备应当有一个 fsnyc 入口.
尽管有一套通用的规则, 还应当认识到每个设备是唯一的并且有时这些规则必须稍微弯曲
一下. 例如, 面向记录的设备(例如磁带设备)无法执行部分写.


刷新挂起的输出


我们已经见到 write 方法如何自己不能解决全部的输出需要. fsync 函数, 由同名的系
统调用而调用, 填补了这个空缺. 这个方法原型是:
int (*fsync) (struct file *file, struct dentry *dentry, int datasync);
如果一些应用程序需要被确保数据被发送到设备, fsync 方法必须被实现为不管
O_NONBLOCK 是否被设置. 对 fsync 的调用应当只在设备被完全刷新时返回(即, 输出缓
冲为空), 即便这需要一些时间. datasync 参数用来区分 fsync 和 fdatasync 系统调用;
这样, 它只对文件系统代码有用, 驱动可以忽略它.
LINUX DEVICE DRIVERS,3RD EDITION
139
fsync 方法没有不寻常的特性. 这个调用不是时间关键的, 因此每个设备驱动可根据作者
的口味实现它. 大部分的时间, 字符驱动只有一个 NULL 指针在它们的 fops 中. 阻塞设
备, 另一方面, 常常实现这个方法使用通用的 block_fsync, 它接着会刷新设备的所有的
块.

底层的数据结构

​​​​​​​

​​​​​​​poll 和 select 系统调用的真正实现是相当地简单, 对那些感兴趣于它如何工作的人;
epoll 更加复杂一点但是建立在同样的机制上. 无论何时用户应用程序调用 poll,
select, 或者 epoll_ctl,
[24]24 内核调用这个系统调用所引用的所有文件的 poll 方法,
传递相同的 poll_table 到每个. poll_table 结构只是对一个函数的封装, 这个函数建
立了实际的数据结构. 那个数据结构, 对于 poll和 select, 是一个内存页的链表, 其中
包含 poll_table_entry 结构. 每个 poll_table_entry 持有被传递给 poll_wait 的
struct file 和 wait_queue_head_t 指针, 以及一个关联的等待队列入口. 对
poll_wait 的调用有时还添加这个进程到给定的等待队列. 整个的结构必须由内核维护以
至于这个进程可被从所有的队列中去除, 在 poll 或者 select 返回之前.
如果被轮询的驱动没有一个指示 I/O 可不阻塞地发生, poll 调用简单地睡眠直到一个它
所在的等待队列(可能许多)唤醒它.
在 poll 实现中有趣的是驱动的 poll 方法可能被用一个 NULL 指针作为 poll_table 参
数. 这个情况出现由于几个理由. 如果调用 poll 的应用程序已提供了一个 0 的超时值
(指示不应当做等待), 没有理由来堆积等待队列, 并且系统简单地不做它. poll_table
指针还被立刻设置为 NULL 在任何被轮询的驱动指示可以 I/O 之后. 因为内核在那一点
知道不会发生等待, 它不建立等待队列链表.
当 poll 调用完成, poll_table 结构被去分配, 并且所有的之前加入到 poll 表的等待
队列入口被从表和它们的等待队列中移出.
我们试图在图 poll 背后的数据结构中展示包含在轮询中的数据结构; 这个图是真实数据
结构的简化地表示, 因为它忽略了一个 poll 表地多页性质并且忽略了每个
poll_table_entry 的文件指针.​​​​​​​

在此, 可能理解在新的系统调用 epoll 后面的动机. 在一个典型的情况中, 一个对 poll
或者 select 的调用只包括一组文件描述符, 所以设置数据结构的开销是小的. 但是, 有
应用程序在那里, 它们使用几千个文件描述符. 在这时, 在每次 I/O 操作之间设置和销
毁这个数据结构变得非常昂贵. epoll 系统调用家族允许这类应用程序建立内部的内核数
据结构只一次, 并且多次使用它们.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1331154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

叮咚,微信年度聊天报告(圣诞节版)请查收~丨GitHub star 16.8k+

微信年度聊天报告——圣诞节特别版&#xff0c;快发给心仪的ta吧~ 开源地址 GitHub开源地址&#xff1a;https://github.com/LC044/WeChatMsg 我深信有意义的不是微信&#xff0c;而是隐藏在对话框背后的一个个深刻故事。未来&#xff0c;每个人都能拥有AI的陪伴&#xff0c;…

「Vue3面试系列」Vue3.0性能提升主要是通过哪几方面体现的?

文章目录 一、编译阶段diff算法优化静态提升事件监听缓存SSR优化 二、源码体积三、响应式系统参考文献 一、编译阶段 回顾Vue2&#xff0c;我们知道每个组件实例都对应一个 watcher 实例&#xff0c;它会在组件渲染的过程中把用到的数据property记录为依赖&#xff0c;当依赖发…

显卡之争!英伟达和AMD下场互掐!GPU霸主地位是否能保?

大家好&#xff0c;我是二狗。 英伟达和AMD这两家芯片巨头掐起来啦&#xff01; 事情的起因是&#xff0c;两周前AMD董事会主席兼CEO苏姿丰在一场活动中发布了用于生成式AI和数据中心的新一代Intinct MI300X GPU芯片加速卡。 单单发布显卡没啥问题&#xff0c;但是AMD声称MI300…

【Spring实战】03 JDBC常用操作

文章目录 1. JdbcTemplate 类1&#xff09;queryForList2&#xff09;update3&#xff09;query4&#xff09;execute5&#xff09;queryForObject 2.代码及执行1&#xff09;代码2&#xff09;执行 3. 优点4. 详细代码总结 Spring JDBC 是 Spring 框架提供的一种用于简化数据库…

05. Springboot admin集成Actuator(一)

目录 1、前言 2、Actuator监控端点 2.1、健康检查 2.2、信息端点 2.3、环境信息 2.4、度量指标 2.5、日志文件查看 2.6、追踪信息 2.7、Beans信息 2.8、Mappings信息 3、快速使用 2.1、添加依赖 2.2、添加配置文件 2.3、启动程序 4、自定义端点Endpoint 5、自定…

基于epoll的web服务器(C语言版本)

基于epoll的web服务器(C语言版本) 1. 初始化监听套接字 包括创建监听套接字&#xff0c;设置端口复用&#xff0c;绑定&#xff0c;设置监听等步骤 1.1 创建监听套接字&#xff08;socket函数&#xff09; socket()打开一个网络通讯端口&#xff0c;如果成功的话&#xff0…

界面控件DevExpress v23.2全新发布 - 官宣正式支持.NET 8

DevExpress拥有.NET开发需要的所有平台控件&#xff0c;包含600多个UI控件、报表平台、DevExpress Dashboard eXpressApp 框架、适用于 Visual Studio的CodeRush等一系列辅助工具。屡获大奖的软件开发平台DevExpress 今年第一个重要版本v23.1正式发布&#xff0c;该版本拥有众多…

【精选】vulnhub CTF6 linux udev提权 (青铜门笔记)

一、信息收集 1.主机探测 发现靶机的IP地址是&#xff1a;192.168.103.130 ┌──(root&#x1f480;kali)-[~] └─# arp-scan -l2.访问web页面 发现有个登录的页面&#xff0c;尝试了弱口令&#xff0c;但是发现没有成功&#xff1b; 所以&#xff0c;我们需要在后面的信…

单词接龙[中等]

一、题目 字典wordList中从单词beginWord和endWord的 转换序列 是一个按下述规格形成的序列beginWord -> s1 -> s2 -> ... -> sk&#xff1a; 1、每一对相邻的单词只差一个字母。 2、对于1 < i < k时&#xff0c;每个si都在wordList中。注意&#xff0c;beg…

数值分析期末复习

第一章 科学计算 误差 解题步骤 先求绝对误差: ∣ x − x ∗ ∣ |x - x^*| ∣x−x∗∣求相对误差限: ∣ x − x ∗ ∣ x ∗ \frac{|x\,\,-\,\,x^*|}{x^*} x∗∣x−x∗∣​求有效数字 ∣ x − x ∗ ∣ 需要小于它自身的半个单位 |x-x^*|\text{需要小于它自身的半个单位} ∣…

Kafka集群架构原理(待完善)

kafka在zookeeper数据结构 controller选举 客户端同时往zookeeper写入, 第一个写入成功(临时节点), 成为leader, 当leader挂掉, 临时节点被移除, 监听机制监听下线,重新竞争leader, 客户端也能监听最新leader leader partition自平衡 leader不均匀时, 造成某个节点压力过大, …

数字信号的理解

1 数字信号处理简介 数字信号处理 digital signal processing&#xff08;DSP&#xff09;经常与实际的数字系统相混淆。这两个术语都暗示了不同的概念。数字信号处理在本质上比实际的数字系统稍微抽象一些。数字系统是涉及的硬件、二进制代码或数字域。这两个术语之间的普遍混…

物联网产品设计,聊聊设备OTA的升级

物联网产品设计部分的OTA设备固件是一个非常重要的部分&#xff0c;能够实现升级用户服务、保障系统安全等功能。 在迅速变化和发展的物联网市场&#xff0c;新的产品需求不断涌现&#xff0c;因此对于智能硬件设备的更新需求就变得空前高涨&#xff0c;设备不再像传统设备一样…

SQL分类

SQL分类 DDL 查询库 查询表 创建表 修改表 DML 添加数据 修改数据 删除数据 DQL 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 执行顺序 DCL 管理用户 管理权限 数据类型 数值类型 字符串类型 日期类型

从零构建tomcat环境

一、官网构建 1.1 下载 一般来说对于开源软件都有自己的官方网站&#xff0c;并且会附上使用文档以及一些特性和二次构建的方法&#xff0c;那么我们首先的话需要从官网或者tomcat上下载到我们需要的源码包。下载地址&#xff1a;官网、Github。 这里需要声明一下&#xff…

龙芯loongarch64服务器编译安装tensorflow-io-gcs-filesystem

前言 安装TensorFlow的时候,会出现有些包找不到的情况,直接使用pip命令也无法安装,比如tensorflow-io-gcs-filesystem,安装的时候就会报错: 这个包需要自行编译,官方介绍有限,这里我讲解下 编译 准备 拉取源码:https://github.com/tensorflow/io.git 文章中…

80x86汇编—汇编程序基本框架

文章目录 First Program指令系统伪指令数值表达式 程序框架解释int 21 中断 通过一个基本框架解释各个指令和用处&#xff0c;方便复习。所以我认为最好的学习顺序就是先看一段完整的汇编代码程序&#xff0c;然后给你逐个逐个的解释每一个代码是干嘛用的。然后剩下的还有很多指…

linux的主线程提前子线程退出以及线程分离

主线程提前退出 如果主线程没有等待子线程提前退出&#xff0c;可能会发生以下情况&#xff1a; 子线程继续运行&#xff1a;如果主线程退出&#xff0c;但子线程仍在执行任务&#xff0c;子线程将继续独立运行。子线程的生命周期不受主线程控制&#xff0c;直到子线程自行完成…

Latex生成的PDF中加入书签/Navigation/导航

本文参考&#xff1a;【Latex学习】在生成pdf中加入书签/目录/提纲_latex 书签-CSDN博客 &#xff08;这篇文章写的真的太棒了&#xff01;非常推荐&#xff09; 题外话&#xff0c;我的碎碎念&#xff0c;这也是我如何提高搜索能力的办法&#xff1a;想在Latex生成的PDF中加入…

python脚本 链接到ssh服务器 快速登录ssh服务器 ssh登录

此文分享一个python脚本,用于管理和快速链接到ssh服务器。 效果演示 🔥完整演示效果 👇第一步,显然,我们需要选择功能 👇第二步,确认 or 选择ssh服务器,根据配置文件中提供的ssh信息,有以下情况 👇场景一,只有一个候选ssh服务器,则脚本会提示用户是否确认链…