分类: BSD
2009-07-23 17:55:19
进程间通信
1. 管道 (pipe) 的限制
UNIX 管道要求所有的通信进程都源自一个共同的父进程。因此,管道的使用造成了一些系统在设计上采用了有些不自然的结构。
2.naive/sophisticated process
所谓一个单纯进程 (navie process) 就是仅靠从标准输入文件中读数据和向标准输出文件写数据来完成工作的进程。
而一个成熟的进程 (sophisticated process) 则了解和掌握了操作系统提供的更多接口,并且它能用掌握的这些信息完成自己的工作。
3.IPC 在设计上应支持的特性
透明性: IPC 应该和这些通信进程是否在同一台机器上没有关系。
高效性
兼容性:现有的单纯进程应该无需改动便可以在分布式环境里使用。
4. 通信语义与属性
通信的语义包括:可靠数据传输的代价 (cost), 支持组播 (multicast) 传输,以及能够传送访问权限或能力等等。
其属性有:
数据的有序传送
数据的无重复发送
数据的可靠传送
面向连接的通信
信息分界的保存
对带外消息的支持
带外 (out-of-band) 消息是在正常传入流 ( 即带内数据 ) 之外发送给接收方的消息。通常是在紧急或异常情况下发送它。
5. 常用套接口
1> 数据报套接口 (datagram socket) 提供了一个不可靠的,无连接数据包通信模型;
2> 流套接口 (stream socket) 提供了一个可靠的,面向连接的字节流模型,支持带外数据发送;
3> 有序数据包套接口 (sequenced packet socket) 提供了一个有序,可靠,无重复的机遇连接的通信模型。
连接是一种由协议使用的机制,协议用它来避免在每个发送的数据包上都带套接口标识。两端都各自保留状态信息。另一方面,无连接的同学要求在每一次传送时都带上源和目的地址。
6. 使用套接口
( UNP 才是关于 socket 的最佳参考资料)
创建套接口
int sock, domain = AF_INET, type = SOCK_STREAM, protocol = 0;
sock = socket(domain, type, protocol);
初始化一个连接
int error;
int sock; /* Previously created by a socket() call. */
struct sockaddr_in rmtaddr; /* Assigned by the program. */
int rmtaddrlen = sizeof (struct sockaddr_in);
error =
connect(sock, (struct sockaddr *)&rmtaddr, rmtaddrlen);
客户端就到吃为止了,而服务器端则需把一个地址同一个套接口绑定
int error;
int sock;
struct sockaddr_in addr;
int addrlen = sizeof (struct sockaddr_in);
error =
bind(sock, (struct sockaddr*)&localaddr, localaddrlen);
监听套接口
int error, sock, backlog;
error = listen(sock, backlog);
接受传入的连接
int newsock, sock;
struct sockaddr_in clientaddr;
int clientaddrlen = sizeof(struct sockaddr_in);
newsock =
accept(sock, (struct sockaddr *)&clientaddr, clientaddrlen);
在套接口上发送和接受数据的例程
Routine |
Connected |
Disconnected |
Address Info |
read |
Y |
N |
N |
readv |
Y |
N |
N |
write |
Y |
N |
N |
writev |
Y |
N |
N |
recv |
Y |
Y |
N |
send |
Y |
Y |
N |
recvmsg |
Y |
Y |
Y |
sendmsg |
Y |
Y |
Y |
Sendmsg 与 recvmsg 还可以传递有特殊解释的辅助数据或者控制信息
IPC 覆盖在连网机制上,应用程序的数据流通过套接口层到达网 络层,反之亦然。套接口层所需的状态完全封装在它自己的里面,而所有与协议有关的状态都保存在专门支持该协议的辅助数据结构中。而存储传输数据的责任则从 套接口层下放到网络层。始终坚持这原则有助于简化存储管理的细节。
套接口层支持的例程
Routine |
Function |
socreate() |
create a new socket |
sobind() |
bind a name to a socket |
solisten() |
mark a socket as listening for connection requests |
soclose() |
close a socket |
soabort() |
abort connection on a socket |
soaccept() |
accept a pending connection on a socket |
soconnect() |
initiate a connection to another socket |
soconnect2() |
create a connection between two sockets |
sodisconnect() |
initiate a disconnect on a connected socket |
sosend() |
send data |
soreceive() |
receive data |
soshutdown() |
shut down data transmission or reception |
sosetopt() |
set the value of a socket option |
sogetopt() |
get the value of a socket option |
7. IPC 和网络协议对内存管理模式的要求
需要内存的可能是想通信协议数据包这样长度不定的结构。
数据包和其他数据对象必须在等待发送和接受时排成队列。
8.mbuf
mbuf 即内存缓冲区 (memory buffer) ,它的大小随着它所保存的内容不同而变化。所有的 mbuf 都包含一个大小固定的 m_hdr 结构,这个结构记录了各种有关 mbuf 的信息位。一个只含数据的 mbuf 有 234 字节的空间 (mbuf 总长 256 字节,减去 mbuf 头 [m_hdr] 的 22 字节 ) 。
把一则消息的内容向上传递给更高层的处理模块之前,协议要例行地剥去此消息前后的协议信息;而在向下传递消息时,又要加上协议信息。
由 m_next 链接器阿里的一个 mbuf 结构的集合称为一个链;而由 m_nextpkt 链接起来的若干个 mbuf 链则被称为一个队列。
mbuf 除了 m_hdr 外还可以只用标志指定第二组数据包头
Memory-buffer (mbuf) data structure with M_PKTHDR
Memory-buffer (mbuf) data structure with external storage
9.mbuf 的数据域长度固定不可变的原因
长度固定使得内存的碎片化降至最低
要求通信协议频繁地在已有的数据区前后添加协议头,对数据区进行分割,或者是从一个数据区的开始或末尾截取数据。 Mbuf 在设计必须能够在热和可能的时候无需重新分配或复制内存,就可以处理这样的变化。
如果 mbuf 打大小不固定的话,一些相关函数的执行开销将会大得多。
10. 为了支持 SMP 对 mbuf 的改进
每个 CPU 都有自己的存放 mbuf 和 mbuf 簇的链表。还有一个通用的 mbuf 和 mbuf 簇的链表,当每个 CPU 自己的链表使用完了,就要尝试从那个通用链表分配内存,而当每个 CPU 自己的链表都有足够空间了,就要把内存释放回该通用链表。
11.socket 与通信域的相关数据结构
1> 系统支持的套接口类型
Name |
Type |
Properties |
SOCK_STREAM |
stream |
reliable, sequenced, data transfer; may support out-of-band data |
SOCK_DGRAM |
datagram |
unreliable, unsequenced, data transfer, with message boundaries preserved |
SOCK_SEQPACKET |
sequenced packet |
reliable, sequenced, data transfer, with message boundaries preserved |
SOCK_RAW |
raw |
direct access to the underlying communication protocols |
2>domain 结构
其中 dom_family 项去确定了通信域所使用的地址族
Name |
Description |
AF_LOCAL |
(AF_UNIX) local communication |
AF_INET |
Internet (TCP/IP) |
AF_INET6 |
Internet Version 6 (TCP/IPv6) |
AF_NS |
Xerox Network System (XNS) architecture |
AF_ISO |
OSI network protocols |
AF_CCITT |
CCITT protocols, e.g., X.25 |
AF_SNA |
IBM System Network Architecture (SNA) |
AF_DLI |
direct link interface |
AF_LAT |
local-area-network terminal interface |
AF_APPLETALK |
AppleTalk network |
AF_ROUTE |
communication with kernel routing layer |
AF_LINK |
raw link-layer access |
AF_IPX |
Novell Internet protocol |
3>socket 结构
Socket 包含的信息有关它们的类型,使用的支持协议以及它们的状态。
State |
Description |
SS_NOFDREF |
no file-table reference |
SS_ISCONNECTED |
connected to a peer |
SS_ISCONNECTING |
in process of connecting to peer |
SS_ISDISCONNECTING |
in process of disconnecting from peer |
SS_CANTSENDMORE |
cannot send more data to peer |
SS_CANTRCVMORE |
cannot receive more data from peer |
SS_RCVATMARK |
at out-of-band mark on input |
SS_ISCONFIRMING |
peer awaiting connection confirmation |
SS_NBIO |
nonblocking I/O |
SS_ASYNC |
asynchronous I/O notification |
SS_INCOMP |
connection is incomplete and not yet accepted |
SS_COMP |
connection is complete but not yet accepted |
SS_ISDISCONNECTED |
socket is disconnected from peer |
用来接受传入连接请求的套接口有两个与连接请求相关的套接口队列。由 so_incomp 字段开头的套接口链表代表了一个由连接组成的队列,必须在协议层完成这些连接后才能将它们返回。 so_comp 字段开头的则是一个准备返回给监听进程的套接口链表。
4> 套接口地址
12. 高位线、低位线
为了避免耗尽资源,对于在一个套接口数据缓冲区中排队的数据字节数,也就是数据所能使用的存储空间量,套接口都给它们设置了上界。这个高位线 (high watermark) 由协议进行初始化,当然一个应用程序也可以改变这个值,但它不能超过系统最大值 ( 通常是 256KB) 。网络协议能够检查高位线,并将这个值用于流控策略。在每个套接口数据缓冲区中还有一个低位线 (low watermark) 。低位线是满足一次数据接收请求所需要的最少字节数,应用程序可以通过它控制数据流。
13. 建立连接
进程对接期间套接口的状态转换图
Socket state transitions during process rendezvous
在套接口上排队以等待 accept() 调用的链接
在连接建立的过程中,套接口的状态有套接口层和支持协议层联合管理。一个协议永远不会直接改变套接口的状态值;为促进模块化,所有的改变都由替代套接口 例程来执行。这些例程按照指示改变套接口的状态,并通知任何处于等待中的进程。支持协议层从来不直接使用同步或信号机制。异步检查出的错误被传递到一个套 接口的 so_error 项中。
14. 传送 / 接收数据
套接口层主要的工作就是发送和接收数据。必须注意,除了可以选择加记录边界之外,套接口层本身明确不允许在通过套接口传输或接收的数据上施加任何结构。在整个 IPC 中,对任何数据的解释或者结构化在逻辑上都独立于通信域的实现。
对于那些要保证数据可靠传递的套接口来说,协议通常要在套接口的发送队列里保留所有要发送数据的一个副本,直到接收方确认收到这些数据为止。那些不保证数据一定发送到的协议则通常从 sosend 中接受数据,然后将其直接发往目的地,不保存任何副本。但是 sosend() 自己并不区分可靠与不可靠传输。
在接受数据缓冲中出现的数据组织通常有 3 中情况:分别针对流,数据报以及有序数据包的套接口。在一般情况下,接收数据缓冲区是按照消息链表的方式来组织的。
Data queueing for datagram socket
15. 关闭套接口
1> 对可靠协议的 close 隐含的语义
当一个承诺可靠传送数据的套接口在关闭时还有排队等待发送的数据,或者还有等待确认收到的数据,套接口就必须为了 close 调用, ( 可能无限次地 ) 尝试发送这些数据,以保证套接口承诺的可靠传递数据的语义。如果套接口为了使 close 调用成功完成而丢弃数据的话,它就违背了关于可靠传递数据的承诺。根据 close 隐含语义,丢弃数据就可能导致这些进程在网络环境中的工作不可靠。然而,如果一直到所有的数据都被成功发送后才关闭套接口,那么,在有些通信域中, close 调用可能永远不会结束。
2> 关闭套接口时其状态转换图
16. 本地 IPC
本地 IPC 的用户级 API
Subsystem |
Create |
Control |
Communicate |
semaphores |
semget |
semctl |
semop |
message queues |
msgget |
mesgctl |
msgrcv, msgsnd |
shared memory |
shmget |
shmctl, shmdt |
n/a |
消息队列天生就是半双工的,在通信端点之间传递的消息里带有一个类型和一个数据区。
对于共享内存,当进程用完共享内存的时候,它就使用系统调用 shmdt 把共享内存从它的进程空间中剥离。这个例程不会释放共享内存,因为其他进程可能还在使用它,但是它会解除调用它的进程对虚拟内存的映射。