PR_Poll() 和分层 I/O

[最后编辑于 1998 年 8 月 8 日,作者:AOF] 本备忘录讨论了在与分层 I/O结合使用 PR_Poll() 时的一些细微差别。这是 NSPR 2.0 中一个相对较新的功能,并非指它之前没有出现在源代码树中,而是指它一直没有客户端使用。

实现

NSPR 提供了一个公共 API 函数 PR_Poll(),其模型来自 UNIX 的poll()系统调用。

PR_Poll的实现比较复杂。它不仅将PRPollDesc数组映射到底层操作系统所需的结构,还必须处理分层 I/O。尽管PR_Poll本身不是分层的,但仍然需要这样做。对于PRPollDesc数组中每个具有非空PRFileDescin_flags不为零的元素,它都会调用文件描述符的poll() 方法poll()方法是PRIOMethods表中包含的向量之一。在分层 I/O 的情况下,方法表的元素(方法)可以由该层的实现者覆盖。然后,这些层被堆叠。使用该堆栈的 I/O 将通过顶层的方法进行调用,并且每一层都可以对 I/O 操作的执行方式做出修改决策。

poll()方法的目的是允许一层修改最终用于调用底层操作系统poll()(或等效函数)的标志。如果要实现增强的流协议(例如SSL),则此类修改可能很有用。SSL 代表安全套接字层,因此作为示例的适用性很明显。但它过于复杂,无法在本备忘录中描述,因此本备忘录将使用一个更简单的分层协议。示例协议是,为了发送n个字节,它必须首先询问连接的对等方是否愿意接收这么多字节。请求的形式是 4 个字节(二进制)表示发送方希望传输的字节数。对等方将发送回它愿意接收的字节数(在测试代码中没有错误条件,所以不要问)。

协议的含义很明显。为了执行PR_Send操作,该层必须首先执行不同的发送,然后接收响应。做到这一点并使堆栈的客户端保持不知情是目标。NSPR 2.0 的目标不是隐藏同步与非阻塞 I/O 的细微差别

分层方法

每一层都必须为方法表中的每个元素实现合适的函数。可以通过调用PR_GetDefaultIOMethods获取默认方法的副本。这些方法只是将所有调用通过该层传递到堆栈的下一层。

层实现者可以将从该函数获取的PRIOMethods元素复制到自己的方法表中,然后仅覆盖感兴趣的方法。通常(只有一个例外)分层方法将执行其设计职责,然后调用堆栈下一层的等效函数。

分层poll()

更有趣的方法之一是poll()。每当客户端调用PR_Poll时,运行时都会调用它。它可能会在顶层为轮询描述符中的每个文件描述符调用。它可能被调用零次或多次。poll()方法的目的是为该层提供一个调整轮询位的机会。例如,如果客户端(,顶层)正在为特定文件描述符调用PR_Poll以进行读取轮询请求,则较低层可能会决定它必须首先执行写入。在这种情况下,该层的poll()方法将使用包含PR_POLL_READ标志的``in_flags``进行调用。但是,poll()方法将使用设置的PR_POLL_WRITE位调用下一层的poll()方法。重新分配轮询标志的过程可以根据堆栈中的层数重复多次。它是最终值,即返回到顶层poll()方法(PR_Poll)调用者的值,运行时将在调用操作系统的poll()(或等效)系统调用时使用该值。

预计轮询位的修改将从堆栈顶部向下传播,允许最靠近堆栈底部的层提供最终设置。这意味着在分层函数的返回阶段不应修改``in_flags``

例如

不建议在调用下一层的poll()方法和return语句之间修改final_in_flagspoll()方法的第三个参数是指向 16 位字的指针。如果该层通过该指针设置内存中的值并且返回具有对应位的值,则运行时假设文件描述符已立即准备好。

有两个重要的偏差。首先,这是(已知的)分层例程调用堆栈下一层方法的一个(已知)例外。如果在out_flags中设置了位,则该方法应直接返回。其次,运行时将观察到该层声明此文件描述符已准备好,并抑制对操作系统poll()系统调用的调用。

目前,此功能的唯一已知用途是允许一层指示它已缓冲输入。请注意,它不适用于缓冲的输出,因为为了写入/发送输出,运行时仍必须与操作系统确认此类操作是否允许。

由于poll()方法可能被调用零次或多次,因此它必须是幂等的或至少是函数式的。它需要查看该层的状态,但不得对该状态进行修改,这会导致在同一PR_Poll调用中进行的后续调用返回不同的答案。由于poll()方法可能根本不会被调用,因此不能保证例程将执行的任何修改都将发生。