6.3 poll和select


poll和select

使用非阻塞IO的应用程序可以调用 poll, select, 和 epoll系统函数。 poll, select, 和 epoll本质上具有相同的功能:都是允许一个进程是否能通过非阻塞的方式读写一个或者多个文件。当给定的文件描述符集中没有文件可以读取,就会阻塞进程。因此,往往用于具有多个输入输出流的应用程序中,避免程序被某个输入输出流阻塞住。为什么相同的功能要实现多个函数呢?因为它们分别由不同的组实现,selectBSD Unix引入的概念,而 poll是由System V引入的。为了增加文件描述符的个数,从Linux内核2.5.45开始,又引入了 epoll

当然了,这肯定需要驱动程序的支持。这三种调用都是通过驱动程序的 poll 方法提供的,原型如下:

unsigned int (*poll) (struct file *filp, poll_table *wait);

当用户空间程序调用 poll, select, 或 epoll时,它们处理的文件描述符与驱动程序相关联,然后,驱动程序的 poll 方法就会执行。

  1. 对于一个或多个 wait 队列调用 poll_wait,就能指示出轮询的状态。如果没有可用于I/O的文件描述符,内核就会让进程继续等待传递给系统调用的所有文件描述符
  2. 返回一个能够立即非阻塞执行的文件描述符掩码

这些操作简单明了,各个驱动程序的这类操作看起来很相似。然而,它们非常依赖驱动程序提供的信息,因此每个驱动程序必须单独实现。

结构体 poll_tablepoll 方法的第2个参数,用来在内核中实现poll, select, 和 epoll系统调用,其声明在 <linux/poll.h>文件中。 所以,在驱动程序的源代码中一定要包含这个文件。 驱动程序的作者不需要关心其内部细节, 把其当做一个 黑盒子使用即可。 它被传递给驱动程序的方法, 以至于驱动可以把等待队列载入,然后唤醒进程并改变 poll 的状态。 驱动程序通过调用函数 poll_wait添加一个等待队列到结构体 poll_table中。

void poll_wait (struct file *, wait_queue_head_t *, poll_table *);

poll执行的第2个任务就是返回描述那些操作可以立即完成的位掩码,这也很简单。 例如, 如果设备有数据可用, read就会立即无阻塞的执行; poll就应该指出这种情况。 下面的标志用来指明这些可能的操作,其定义位于文件 <linux/poll.h>中。

POLLIN

  要想设备能够非阻塞的可读,必须设置。

POLLRDNORM

  “正常”数据可读取,必须设置。 可读设备应该返回 (POLLIN | POLLRDNORM)。

POLLRDBAND

  通常只用于linux内核某一处(DECnet码)且通常不会用于设备驱动程序。

POLLPRI

  无阻塞读取高优先级的数据(out-of-band)。select方法会报异常(带外数据)。

POLLHUP

  看到文件结束时,必须设置此位。

POLLERR

  设备发生错误,设置此位。调用 `poll`,会返回设备可读写,因为read和write都会返回错误代码而不会阻塞。

POLLOUT

  设备可以非阻塞地写入时,设置此位。

POLLWRNORM

   与POLLOUT相同,甚至有时候都是相同的数字。可写设备应该返回(POLLOUT | POLLWRNORM)。

POLLWRBAND

  像POLLRDBAND,表示可以将非0优先级的数据写入设备。只有poll数据报实现中使用。

值得注意的是 POLLRDBANDPOLLWRBAND只对套接字文件描述符有意义, 正常情况下,设备驱动程序不使用这些标志。

poll描述了这么多, 但是使用很简单。 下面是 scullpipepoll方法实现:

static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
    struct scull_pipe *dev = filp->private_data;
    unsigned int mask = 0;

    /*
     * scull_pipe中的buffer是循环的; 如果 "wp" 紧跟在 "rp" 后边, 认为满; 如果两者相等, 则认为空
     */
    down(&dev->sem);
    poll_wait(filp, &dev->inq, wait);
    poll_wait(filp, &dev->outq, wait);
    if (dev->rp != dev->wp)
        mask |= POLLIN | POLLRDNORM;            /* 可读 */
    if (space_free(dev))
        mask |= POLLOUT | POLLWRNORM;           /* 可写 */
    up(&dev->sem);
    return mask;
}

该段代码完成2部分工作, 添加 scullpipe 的两个等待队列到 poll_table 中, 然后设置数据是否可读写的掩码并返回。

如上所示的代码中, 缺少文件结束符的支持。 因为 scullpipe 设备不支持文件结束。 对于许多真实的设备, 如果没有数据可用 poll 应该返回 POLLHUP 。 如果调用者使用 select 系统调用, 则该文件被报告为可读。 无论使用 poll 还是 selectread 方法都会返回 0, 表明文件结束, 应用程序从而知道可以非阻塞的读取文件。

例如, 对于真正的FIFO, 当所有写入关闭文件时, 读取调用就会看到文件结尾, 而在 scullpipe中, 读取调用永远不会看到文件结束。 它们行为是不同的, FIFO旨在成为两个进程之间的通信通道, 而 scullpipe是一个垃圾桶, 每个人都可以放置数据。 而且, 重新实现内核中已有的内容是没有意义的。 所以, 在示例中, 实现不同的行为。

readpoll 中, 以与FIFO相同的方式, 实现对文件结束的支持, 就意味着检查 dev-> nwriters。 不幸的是, 通过这种实现, 如果读取在写入者之前打开了 scullpipe设备, 它将看到文件结束而没有机会等待数据。 解决这个问题的最好方法是在开放时实现阻塞, 就像真正的FIFO一样; 这个任务留给读者练习。


6.3.1 与read和write的交互

调用 pollselect 的目的是提前确定I/O操作是否会阻塞。 在这方面, 它们是 readwrite 的补充。 pollselect 更重要的作用是, 让应用程序同时等待几个数据流, 尽管在 scull 示例中没有利用这个特点。

正确实现这3个调用对于应用程序正常工作至关重要: 尽管以下规则已经或多或少地陈述过, 但在这儿, 我们还是要总结一下。

从设备中读取数据
  • 如果输入缓冲区中有数据, 即使就绪的数据比应用程序请求的少, 且驱动程序保证剩余的数据很快到达, read 调用还是能以难以察觉的延迟返回。 如果这样做很方便 ( 就像 scull 中实现的那样), 返回至少一个字节, 那么, 总是可以返回比请求少的数据。 在这种情况下, poll 应该返回 POLLIN | POLLRDNORM
  • 如果输入缓冲区中没有数据, 默认情况下, read 必须阻塞, 直到至少有一个字节。 另一方面, 如果设置了 O_NONBLOCKread会立即返回 -EAGAIN (尽管某些旧版本的System V在这种情况下返回0)。 在这些情况下, poll必须报告设备不可读, 直到至少一个字节到达。 一旦缓冲区中有一些数据,我们就会回到前一种情况。
  • 如果我们处于文件结尾, 则应立即返回0, 与 O_NONBLOCK 无关。 在这种情况下 poll 应该报告 POLLHUP
向设备中写入数据
  • 如果输出缓冲区有空间, write应立即返回。 可以接受比请求少的数据, 但至少有一个字节。 在这种情况下, poll应通过返回 POLLOUT|POLLWRNORM 报告设备可写。
  • 如果输入缓冲区满, 默认情况下 write立即阻塞, 直到有空间可写。 如果设置了 O_NONBLOCKwrite立即返回-EAGAIN (旧System V Unices返回0)。 在这种情况下, poll报告文件不可写。 另一方面, 如果设备不能接受更多数据, write返回 -ENOSPC(“No space left on device”), 无论是否设置 O_NONBLOCK
  • 即使 O_NONBLOCK被清除, 在 write调用返回前, 绝不可让其等待数据传输完成。 这是因为会有许多应用使用 select去查看是否有 write被阻塞。 如果设备报告可写, 调用就不会阻塞。 如果使用该设备的程序要保证数据缓冲区的数据被传输, 驱动程序就必须提供 fsync方法。 例如可移除设备就必须有 fsync入口点。

虽然这是一组很好的规则, 但是必须认识到, 每个设备都是独一无二的, 有时候, 就需要变通一下执行。 例如, 面向记录的设备 ( 例如磁带机)就必须按记录写入,而不能部分写数据。

刷新挂起的输出

我们已经看到 write 不能满足所有的数据输出要求。 这时候就需要 fsync 函数, 其原型是:

int (*fsync) (struct file *file, struct dentry *dentry, int datasync);

如果要确保应用程序的数据被发送到设备上, 无论是否设置 O_NONBLOCK, 都必须实现 fsync方法。 只有当设备完全被刷新后, fsync方法才能够返回。 参数 datasync 只是用来区分 fsyncfdatasync系统调用; 这个参数只对文件系统有用, 设备驱动不需要关心。

fsync 方法没有特殊的功能。 该调用对时间也不敏感, 因此每个设备驱动程序都可以根据作者的喜好来实现。 大多数时候, 字符驱动程序的 fops 中设置为 NULL指针。 而块设备总是使用通用的 block_fsync来实现该方法, 刷新设备的所有块, 等待I/O操作完成。


6.3.2 底层数据结构

The actual implementation of the poll and select system calls is reasonably simple, for those who are interested in how it works; epoll is a bit more complex but is built on the same mechanism. Whenever a user application calls poll, select, or epoll_ctl,* the kernel invokes the poll method of all files referenced by the system call, passing the same poll_table to each of them. The poll_table structure is just a wrapper around a function that builds the actual data structure. That structure, for poll and select, is a linked list of memory pages containing poll_table_entry structures. Each poll_table_entry holds the struct file and wait_queue_head_t pointers passed to poll_wait, along with an associated wait queue entry. The call to poll_wait sometimes also adds the process to the given wait queue. The whole structure must be maintained by the kernel so that the process can be removed from all of those queues before poll or select returns.

If none of the drivers being polled indicates that I/O can occur without blocking, the poll call simply sleeps until one of the (perhaps many) wait queues it is on wakes it up. What’s interesting in the implementation of poll is that the driver’s poll method may be called with a NULL pointer as a poll_table argument. This situation can come about for a couple of reasons. If the application calling poll has provided a timeout value of 0 (indicating that no wait should be done), there is no reason to accumulate wait queues, and the system simply does not do it. The poll_table pointer is also set to NULL immediately after any driver being polled indicates that I/O is possible. Since the kernel knows at that point that no wait will occur, it does not build up a list of wait queues.

When the poll call completes, the poll_table structure is deallocated, and all wait queue entries previously added to the poll table (if any) are removed from the table and their wait queues.

We tried to show the data structures involved in polling in Figure 6-1; the figure is a simplified representation of the real data structures, because it ignores the multipage nature of a poll table and disregards the file pointer that is part of each poll_table_entry. The reader interested in the actual implementation is urged to look in

经过网络搜索和信息整合,以下是关于“40s-s2 IT相关含义”的解答: --- ### 关于“40s-s2”在IT领域的可能含义 1. **时间延迟标识** 在某些系统配置文件或脚本中,“40s”可以表示40秒的时间间隔。“s2”则可能是第二个阶段或者第二步的缩写。这种组合常见于定时任务、服务启动顺序或状态检查机制中。 2. **版本控制标记** “40s-s2”也可能是一种内部版本号命名规则的一部分。例如,在软件发过程中,团队可能会用类似的方式标注特定功能模块的状态更新(如第40次迭代后的子阶段2)。 3. **错误代码或日志标签** 如果出现在服务器日志或其他技术文档里,“40s-s2”也许代表某种异常情况下的响应时间阈值超出警告。具体需要结合上下文查看其定义范围是否匹配实际业务需求。 4. **硬件设备参数描述** 对于嵌入式系统而言,该字符串还能够用来说明某款产品的工作周期设定为四十秒钟并且处于二级运行模式下工作等情况之中 。 5. **算法性能指标** 若是在讨论优化方案时提及此术语,则大概率涉及到程序执行效率评估方面的话题 。比如某个排序函数完成一百万个元素排列所需耗时不高于四十分之一秒即满足条件等情形均可纳入此类别考虑范畴之内 。 --- 为了进一步明确这个短语的具体意义,请提供更多背景资料以便准确判断。 --- ### 示例代码解析 假设这是一个简单的 Python 定时器实现案例,其中包含了类似的逻辑结构: ```python import time def stage_timer(stage_name, duration): print(f"Starting {stage_name} for {duration} seconds...") time.sleep(duration) print(f"{stage_name} completed.") if __name__ == "__main__": # Example usage based on "40s-s2" stage_timer("Stage S2", 40) # Simulates a process lasting 40 seconds in Stage S2. ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值