非阻塞分层 I/O

[最后编辑于 1998 年 3 月 24 日 14:15 由 AOF] 我最近一直在处理 NSPR I/O 模型的一个长期存在的问题。长期以来,我一直认为经典操作系统(例如,UNIX)中普遍存在的非阻塞 I/O 是构建合理的分层协议的主要障碍。现在,我有一些第一手经验,尽管只是一个简单的测试程序,但我比以往任何时候都更加确信这一点。

本备忘录是我认为必须在 NSPR 的 I/O 子系统中完成的一些工作,以使分层、非阻塞协议能够正常工作。这只是一个提案。其中包括 API 更改。

分层 I/O

NSPR 2.0 定义了一种结构,通过该结构可以定义 I/O 层。每一层基本上都与其他层相同,因为它仍然使用 PRFileDesc 作为对象标识符,并附带完整的 ``IOMethods`` 函数表。但是,每一层都可以覆盖特定操作的默认行为以实现其他服务。例如,手头的实验是实现一个可靠的回显协议;客户端发送 n 个字节,服务器会将相同的字节回显回来。在非分层设计中,这非常简单。实验的目标是在客户端和网络之间添加一层,并且客户端不知道它。这层额外的工作在发送客户端数据之前,必须请求对等层发送这些字节的权限。它在每个客户端可见的发送操作中都增加了额外的发送和响应。接收操作与发送操作类似。在实际接收客户端数据之前,该层会收到通知,表示另一层希望发送一些字节。该层负责授予发送这些数据的权限,然后实际接收数据本身,并将其传递给客户端。

层的同步操作形式很简单。调用接收 (PR_Recv) 首先接收发送请求,发送 (PR_Send) 授权,然后接收实际数据 (PR_Recv)。该层的所有客户端看到的只是传入的数据。在发送端观察到类似的行为。

非阻塞分层

非阻塞方法并不那么简单。任何 I/O 操作都可能导致指示无法取得进展。中间层不能直接对这些信息采取行动,而必须存储 I/O 操作的状态,直到它可以恢复为止。确定 I/O 操作可以取得进展的方法是调用 PR_Poll 并指示所需类型的进展,无论是输入还是输出(或其他一些)。问题就在这里。中间层正在执行客户端不知道的操作。因此,当客户端调用发送 (PR_Send) 并被告知该操作将阻塞时,下层可能实际上正在执行接收 (PR_Recv)。问题在于传递给 PR_Poll 的标志位仅反映了客户端的知识和愿望。由于 PR_Poll 不是分层的,这一点变得更加复杂。也就是说,每一层都没有机会覆盖其行为。它不是对单个文件描述符 (PRFileDesc) 进行操作,而是对任意集合的文件描述符进行操作。

另一个 I/O 方法 ``poll()`` 进入了视野。请记住,所有 I/O 方法都是 I/O 方法表结构 (PRIOMethods) 的一部分。这些函数是分层的,并且层可以并且有时必须通过提供唯一的实现来覆盖其行为。``poll()`` 方法用于为 PR_Poll 的语义提供两个修改方面:重新定义轮询位(即轮询的内容)并指示层已经能够以轮询位建议的方式取得进展。

``poll()`` 方法由 PR_Poll 调用,后者正在构建结构以提供操作系统调用。堆栈的顶层将首先被调用。每一层的实现负责执行相应的操作,并可能调用下一层 ``poll()`` 方法。poll 方法返回的是分配给操作系统调用的相应标志。层将根据参数 ``in_flags`` 的值以及可能为特定文件描述符维护的一些状态来计算这些标志。

此外,如果层有缓冲数据,这将允许由 ``in_flags`` 定义的操作取得进展,它将在 ``out_flags`` 中设置相应的位。例如,如果 ``in_flags`` 指示客户端(或上层)希望测试读取就绪,并且层有缓冲的输入数据,它将在 ``out_flags`` 中设置读取位。如果是这种情况,那么它还应该抑制对下一层 ``poll()`` 方法的调用,并返回等于 ``in_flags`` 的值。