跳到主要内容

3. 远程进程之间的进程间通信系统

前一节所述的 IPC 可以很容易地推广到允许地理位置不同的进程之间进行进程间通信, 例如在一个计算机网络之内。

先考虑一种简单的配置: 进程分布在一个星形结构的各个点上。星形的每个点上有一个自主的操作系统<5>。星形中心存在一个相当大而聪明的计算机系统, 称为网络控制器 (Network Controller)。这个中心系统上不能运行任何进程; 相反, 应当把它看作网络中各个操作系统的监督程序的一个扩展。

如果网络控制器能够执行 SEND、RECEIVE、SEND FROM ANY、RECEIVE ANY 和 UNIQUE 这些操作, 并且网络中所有分时系统的所有监督程序自己都不执行这些操作, 而是请网络控制器代它们执行, 那么远程进程之间的进程间通信问题就解决了。无需再做别的改动, 因为网络控制器可以跟踪哪些 RECEIVE 已经执行、哪些 SEND 已经执行, 并像模型分时系统中的监督程序那样把它们匹配起来。也可以实现一种网络范围的端口编号方案, 由网络控制器知道某个特定端口在某个特定时刻位于何处 (即在哪个站点)。

接着考虑一个更复杂的网络: 其中没有共同的中心点, 因而必须把网络控制器所执行的功能分散到网络各节点上。在本节余下的部分中, 我将说明: 可以高效而方便地把星形网络控制器所执行的功能分散到众多网络站点上, 同时仍然支持远程进程之间的一般进程间通信。

必须对上述四个 SEND/RECEIVE 操作各做一些改动, 以便在分布式网络控制器中使用。为 RECEIVE 增加一个参数, 指明该 RECEIVE 要发送到的站点。为 SEND FROM ANY 和 SEND 消息增加一个站点, 指明把该 SEND 发送到何处, 尽管通常就是本地站点。RECEIVE 和 RECEIVE ANY 都增加了取得所收到消息的来源站点的能力。于是, 当一个 RECEIVE 被执行时, 该 RECEIVE 被发送到所指明的站点, 这可能是远程站点。同时, 一个 SEND 被发送到同一个站点, 通常是执行该 SEND 的进程的本地站点。在这个称为会合站点 (rendezvous site) 的站点上, RECEIVE 与合适的 SEND 相匹配, 于是消息传输从 SEND 站点向 RECEIVE 所来自的那个站点进行。

RECEIVE ANY 从不离开其发起站点, 这正是 SEND FROM ANY 的必要性所在: 因为必须能够向一个 RECEIVE ANY 端口发送消息, 而又不让该消息在发送站点上阻塞、等待一个 RECEIVE。可以构造这样一种系统: 让 SEND/RECEIVE 的会合发生在 RECEIVE 站点, 从而取消 SEND FROM ANY 操作; 但依我判断, 能够在源站点阻塞一次普通的 SEND 传输所带来的好处, 远胜过由此增加的复杂性。

每个站点都维护一张会合表。该表为该站点收到的每一个未匹配的 SEND 或 RECEIVE 保存一个表项, 也为该站点给出的所有 RECEIVE ANY 保存表项。一对匹配的 SEND/RECEIVE 一旦完成匹配, 就立即从表中清除。与模型分时系统中所维护的类似表格一样, SEND 和 RECEIVE 表项如果长时间未匹配就会超时, 并通知发起者。RECEIVE ANY 表项在一条满足它的消息到达时从表中清除。

为分散网络控制器功能所需的最后一项改动, 是给每个站点分配一部分唯一编号, 由它通过 UNIQUE 操作分发。我将在下文进一步讨论这个题目。

为了让读者清楚分布式网络控制器如何工作, 下面给出一个例子。由哪个进程挑选端口号等细节只是示例性的, 并不是 IPC 所规定的标准。

假定在网络的 K 和 L 两个站点上, 站点 K 的进程 A 希望与站点 L 的进程 B 通信。进程 B 在端口 M 上有一个未决的 RECEIVE ANY。

                        SITE K                        SITE L

______ ______
/ \ / \
/ \ / \
/ \ / \
/ \ / \
| | | |
| Process A | | Process B |
| | | |
\ / \ /
\ / RECEIVE--> port M /
\ / ANY \ /
\______/ \______/

进程 A 幸好知道站点 L 上存在端口 M, 并使用 SEND FROM ANY 操作从端口 N 向端口 M 发送一条消息。该消息包含两个端口号, 以及供进程 B 把给进程 A 的消息从端口 Q 发送到端口 P 的指示。站点 K 的站点号连同该消息的 SEND 端口 N 一起附加到这条消息上。

                        SITE K                        SITE L

______ ______
/ \ / \
/ \ / \
/ \ / \
/ \ / \
| | | |
| Process A | | Process B |
| | | |
\ port N / \ port M /
\ /--->SEND FROM --->\ /
\ / ANY \ /
\______/ \______/

to port M, site L

containing K,N,P, & Q

进程 A 现在执行一个从端口 Q 到端口 P 的 RECEIVE。进程 A 指定会合站点为站点 L。

                        SITE K                        SITE L

______ ______
/ \ / \
/ \ / \
/ \ Rendezvous/ \
/ \ table \
| | | |
| Process A | ^ | Process B |
| | | | |
\ port P / | \ /
\ / | \ /
\ / <--RECEIVE __/ \ /
\______/ MESSAGE \______/

to site L

containing P, Q, & K

一条 RECEIVE 消息从站点 K 发送到站点 L, 并登记在站点 L 的会合表中。在另一个时刻, 进程 B 执行一个从端口 Q 到端口 P 的 SEND, 并指定站点 L 为会合站点。

                        SITE K                        SITE L

______ ______
/ \ / \
/ \ / \
/ \ Rendezvous/ \
/ \ table \
| | | |
| Process A | | Process B |
| | | |
\ port P / <--------- port Q /
\ / \ /
\ / SEND \ /
\______/ \______/
to site L

containing P & Q

会合完成, 会合表被清除, 向站点 K 上端口 P 的传输随即进行。发送站点的编号 (或许还有 SEND 端口号) 被附加到该次传输的各个消息上, 以供接收进程参考。

                        SITE K                         SITE L

______ ______
/ \ / \
/ \ / \
/ \ / \
/ \ / \
| | | |
| Process A | | Process B |
| | | |
\ port P / \ port Q /
\ /<--transmission<--\ /
\ / \ /
\______/ to port P, site K \______/

containing data and L

进程 B 可能同时在端口 M 上希望执行一个来自端口 N 的 RECEIVE。

注意, 本系统中只有一种重要的站点间控制消息, 也就是 [2] 中称为 Host/Host 协议消息的那类消息。这种控制消息就是 RECEIVE 消息。还可能有另外两种站点间控制消息: 当某个 RECEIVE 或 SEND 超时时发往发起站点的错误消息, 以及在会合站点不是 SEND 站点这种少见情况下发出的 SEND 消息。此外还必须有端口之间消息的标准格式。例如, 如下所示:

         _________________           __________________      _____________
| rendezvous site | <6> | destination site | | source site |
|-----------------| |------------------| |-------------|
| RECEIVE port | | RECEIVE port | | RECEIVE port|
|-----------------| |------------------| |-------------|
| SEND port | | SEND port | | SEND port |
|-----------------| |------------------| |-------------|
| | | source site | | |
| | |------------------| | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| data | | data | | data |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
|_________________| |__________________| |_____________|
transmitted transmitted received
by SEND by Network by RECEIVE
process Controller process

在模型分时系统中, 可以把一个端口从一个进程传给另一个进程。在有分布式网络控制器的情况下这仍然可行。

请记住, 要把一条消息从一个进程发送到另一个进程, 一个从端口 N 到端口 M 的 SEND 与一个从端口 N 到端口 M 的 RECEIVE 必须会合, 通常在 SEND 站点会合。两个进程都记录它们认为的会合站点在哪里, 并把该站点作为相应操作的参数提供。执行 RECEIVE 的进程也认为自己是 SEND 站点。由于一旦某个 SEND 与某个 RECEIVE 会合, 传输就被送往 RECEIVE 的来源处, 而会合表中的表项被清除、并且对于每次从 N 到 M 的后续传输都必须重新建立, 所以移动一个 RECEIVE 端口是容易的。如果一个进程把端口号和会合站点号都发送给位于其他某个站点的新进程, 而后者用这些旧的端口号和会合站点指定执行 RECEIVE, 那么发送方就永远不会知道接收方已经迁移了。让一个发送端口迁移则稍难一些。不过, 如果它迁移了, 那么一直被用于某个 SEND 的那对端口号以及原来的会合站点号就被传给新站点。新 SEND 站点上的进程在从新站点发出第一个 SEND 时指定原来的会合站点。执行 RECEIVE 的进程也仍然认为会合站点是旧站点, 因此 SEND 和 RECEIVE 会在旧站点相遇。它们相遇时, 该站点表中的表项被清除, SEND 和 RECEIVE 两条消息都被发送到新的 SEND 站点, 就如同它们从一开始就注定是发往那里的一样。随后 SEND 和 RECEIVE 在新的会合站点再次相遇, 传输可以继续进行, 就如同该端口从未迁移过一样。由于所有传输都带有来源站点号, 后续的 RECEIVE 会被发送到新的会合站点。可以察觉这种特殊处理必须发生, 是因为某个 SEND 消息被送达的站点并不是发出该 SEND 消息的站点<7>。注意, SEND 端口和 RECEIVE 端口可以同时迁移。

当然, 如果各进程来回发送消息宣告任何可能的迁移以及新的站点号, 这一切也同样可以做到。

读者可能会想到的一个问题是: SEND 和 RECEIVE 的缓冲区如何按大小匹配。最简单的解决方案是要求所有缓冲区都具有相同的大小, 但这是不可接受的, 因为它不易推广到自主操作系统中的进程试图通信的情形。第二种解决方案是由各进程传递指明缓冲区大小的消息。如果采用这种方案, 那么从 SEND 进程发出的、无法放入 RECEIVE 缓冲区的多余数据会被丢弃, 并通知执行 RECEIVE 的进程。这种方案因其简单而颇具吸引力。第三种解决方案是让 RECEIVE 的缓冲区大小随 RECEIVE 消息一起传给 SEND 站点, 并在发送的数据过多时通知 SEND 进程, 甚至把 RECEIVE 缓冲区大小转告 SEND 进程。最后这种方法还允许 SEND 站点上的网络控制器把一个 SEND 拆成两个或更多个 SEND —— 如果为了匹配较小的 RECEIVE 缓冲区大小而需要这样做的话。

当各进程在地理上分散时, 唯一编号的维护也是一个问题。这里给出该问题的三种解决方案。第一种可能是: 各自主操作系统一开始就向网络控制器索取唯一编号, 然后用操作系统所能支配的任何手段, 保证本地进程和程序当前所拥有的任何唯一编号的完整性。在这种情况下, 网络控制器将提供一种把唯一编号从一个站点发送到另一个站点的方法, 并为该编号在新站点上的身份作保。第二种方法是干脆把唯一编号交给正在使用它们的进程, 依靠进程的非恶意行为来保全这些唯一编号; 或者, 如果发生意外, 则依靠发起一次传输所需的那两个口令 (SEND 和 RECEIVE 端口号)。如果唯一编号以非顺序方式分发并且足够长 (比如 32 位), 危险就很小。在最后一种方法中, 端口号里包含一个用户标识, 并由各个操作系统保证这些标识位的完整性。于是, 一个进程虽然无法确知正在向它发送的是正确的端口, 但可以确知正在发送的是正确用户的某个端口。这就是 W. Crowther [2] 提出的所谓虚拟网 (virtual net) 概念。<8>

当远程进程希望通信时会出现第三个难题, 即如何在远程进程之间维持高带宽连接。该问题的解决之道在于让进程掌握有关正在进行的传输状态的大量信息。首先, 我们详细考察 SEND 进程。当一个进程执行 SEND 时, 网络控制器的本地部分把该 SEND 转交给会合站点, 通常就是本地站点。当一个匹配某个未决 SEND 的 RECEIVE 到达时, 网络控制器通过向指定的重启动位置产生一次中断来通知 SEND 进程。同时, 网络控制器开始把 SEND 缓冲区运往 RECEIVE 站点。传输完成时, 会设置一个标志, SEND 进程可以测试该标志。在一次传输进行期间, 该进程可以请网络控制器执行其他操作, 包括其他 SEND。在一对已处于传输过程中的端口上再次发出的 SEND 会被记录下来, 并在第一次传输完成之后立即生效。第三次发出同样的 SEND 则会导致一条发往执行 SEND 的进程的错误消息。接下来, 我们详细考察 RECEIVE 进程。当一个进程执行 RECEIVE 时, 该 RECEIVE 被发送到会合站点。当由该 RECEIVE 引起的数据开始到达 RECEIVE 站点时, 通过向指定的重启动位置产生一次中断来通知 RECEIVE 进程。当传输完成时, 会设置一个标志, RECEIVE 进程可以测试该标志。在同一端口对上第二次执行 RECEIVE 是允许的。第三次则会向 RECEIVE 进程发出一条错误消息。因此, 已有足够的机制让一对进程始终既能有一次传输在进行, 又有下一次传输处于未决状态。因而不会损失效率。另一方面, 每次传输之前都必须有一个到指定缓冲区的 RECEIVE, 从而继续提供完整的流控 (flow control)。