Linux poll 和 select 机制

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.hqwc.cn/news/296594.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 宝塔mysql莫名其妙数据库不见了恢复数据库

起因&#xff1a;宝塔安装的mysql 线上运行突然表包括库都不见了&#xff0c;想办法恢复数据库 登陆mysql cd /www/server/mysql/binmysql -u root -p查看binlog日志是否打开 show variables like log_%;log_bin如果为 ON 则为开启状态&#xff0c;如果开启了才可以进行下一…

C# WPF上位机开发(从demo编写到项目开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 C# WPF编程&#xff0c;特别是控件部分&#xff0c;其实学起来特别快。只是后面多了多线程、锁、数据库、网络这部分稍微复杂一点&#xff0c;不过…

【贪心】单源最短路径Python实现

文章目录 [toc]问题描述Dijkstra算法Dijkstra算法应用示例时间复杂性Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;贪心算法 问题描述 给定一个带权有向图 G ( V , E ) G (V , E) G(V,E)&#xff0c;其中每条边的权是非负实数&#xff0c;给定 V V V中的一个…

计算机网络-网络层

计算机网络-网络层 以下笔记整理为哔哩哔哩湖科大教书匠的《计算机网络微课堂》的教学视频。 链接&#xff1a;计算机网络微课堂 1. 网络层概述 1.1 网络层的主要任务是实现网络互联&#xff0c;进而实现数据包在各网络之间的传输。 1.2 要实现网络层任务&#xff0c;需要解决…

nodejs+vue+微信小程序+python+PHP计算机网络在线考试系统-计算机毕业设计推荐

信息数据的处理完全依赖人工进行操作&#xff0c; 所以电子化信息管理的出现就能缓解以及改变传统人工方式面临的处境&#xff0c;一方面可以确保信息数据在短时间被高效处理&#xff0c;还能节省人力成本&#xff0c;另一方面可以确保信息数据的安全性&#xff0c;可靠性&…

python实现批量替换目录下多个后缀为docx文档内容

批量替换目录下多个后缀为docx文档内容 摘要&#xff1a; 本文将介绍如何使用Python实现批量替换目录下多个后缀为docx文档内容。通过使用Python的os和glob模块&#xff0c;我们可以轻松地遍历目录下的所有文件&#xff0c;并对每个文件进行操作。此外&#xff0c;我们还将使用…

Netty Review - 深入理解Netty: ChannelHandler的生命周期与事件处理机制

文章目录 概述CodeLifeCycleInBoundHandler 验证 概述 Netty的ChannelHandler是处理网络事件&#xff08;如数据读取、数据写入、连接建立、连接关闭等&#xff09;的核心组件。 在Netty中&#xff0c;ChannelHandler的生命周期与Channel的状态紧密相关&#xff0c;主要涉及到…

WEB 3D技术 three.js 通过lil-gui 控制x y z轴数值 操作分组 设置布尔值控制 颜色材质控制

上文 WEB 3D技术 three.js 通过lil-gui管理公共事件中 我们用 lil-gui 处理了一下基础事件和按钮的管理 那么 本文 我们来具体说说它能做的其他事 我们先将基础代码改成这样 import ./style.css import * as THREE from "three"; //引入lil-gui import { GUI } fro…

【23-24 秋学期】NNDL 作业12 优化算法2D可视化

1.简要介绍图中的优化算法&#xff0c;编程实现并2D可视化 SGD&#xff1a;随机梯度下降&#xff0c;每次迭代采集1个样本&#xff08;随机&#xff09;。 AdaGrad算法&#xff1a;一种梯度下降法&#xff0c;是对批量梯度下降法的改进&#xff0c;但并不是对动量法的改进。它…

SysTick 定时器

SysTick定时器定义&#xff1a; SysTick 是一个 24 位的倒计数定时器&#xff0c;当计到 0 时&#xff0c;将从 RELOAD 寄存器中自动重装载定时初值。只要不把它在 SysTick 控制及状态寄存器中的使能位清除&#xff0c;就永不停息。 SysTick定时器分类&#xff1a; SysTic…

论文阅读——RS DINO

RS DINO: A Novel Panoptic Segmentation Algorithm for High Resolution Remote Sensing Images 基于MASKDINO模型&#xff0c;加了两个模块&#xff1a; BAM&#xff1a;Batch Attention Module 遥感图像切分的时候把一个建筑物整体比如飞机场切分到不同图片中&#xff0c;…

python dash 的学习笔记1

dash 用python开发web界面 https://dash.plotly.com/ 官方上支持jula F# python一类。当然我只会python只学习python中使用dash. 要做一个APP&#xff0c;用php,java以及.net都可以写&#xff0c;只所有选择python是因为最近在用这一个。同时也发现python除了慢全是优点。 资料…