目录标题
- 0. 前言
- 1. 概述
- 2. 进程、线程与信息共享
- 3. IPC对象的持续性
- 4. 名字空间
- 5. fork、exec和exit对IPC对象的影响
- 6. 出错处理: 包裹函数
- 7. Unix标准
- 8. 小结
0. 前言
进程间通信这块是学习linux-c编程的关键, 这篇为后续进程间通信技术的引子篇,后续讲到单独的某一个系统调用 都附该引用。
1. 概述
IPC是进程间通信(interprocess communication)的简称。系统上不同进程之间消息传递(message passing)的方式。
共享内存需要某种实行的同步(synchronization)参与运作。
发展阶段。
-
管道
(pipe) 是第一个广泛使用的IPC形式, 使用在Kernel之上的(应用程序或shell)。- 问题在于智能父子进程之间使用。
- 解决方案是FIFO(无名管道)。
-
System V消息队列
(System V message queue)- 用在同一主机上有亲缘关系或无亲缘关系的进程之间。
- 大多数版本的Unix都支持。
有亲缘关系
不仅指父子,还包含子子关系,比如fork两次,派生2个子进程。
所有Unix进程与init进程都有亲缘关系。 -
Posix消息队列
-
远程过程调用
(Remote Procedure Call, RPC)- 20世纪80年代中期,它是从一个系统(客户端主机)上某个程序调用另外一个系统(服务器主机)上某个函数的一种方法,是作为显式网络编程的一种替代方法开发的。
同步演变
- 需要某种同步形式(往往是为了防止多个进程同时修改同一文件)的早期程序使用了文件系统的诡秘特性。
记录上锁
(record locking) 1988年由Posix.1标准化。System V信号量
(System V semaphore)是System V消息队列加上System V内核的同事伴随System V共享内存(System V shared memory)加入的。Posix信号量
(Posix semaphore)和Posix共享内存也由Posix实时标准加入。互斥锁(mutex)和条件变量
(condition variable)是由Posix线程标准定义的两种同步形式。尽管往往用于线程间的同步,他们也能提供不同进程间的同步。读写锁
(read-write lock)是另一种形式的同步。
2. 进程、线程与信息共享
按照传统的Unix编程模型,我们在一个系统上运行多个进程,每个进程都有各自的地址空间。Unix进程间的信息共享可以有多个方式:
1)左边的两个进程共享存留于文件系统中某个文件上的某些信息。为访问这些信息,每个进程都得穿越内核(例如read,write, lseek等)。当一个文件有待更新时,某种形式的同步是必要的,这样即可保护多个写入者,防止相互干扰,也可保护一个或多个读出者,防止写入者的干扰。
2)中间的两个进程共享驻留于内核中的某些信息。管道是这种共享类型的一个例子,System V消息队列和System V 信号量也是。现在访问共享信息的每次操作涉及对内核的一次系统调用。
3)右边的两个进程有一个双方都能访问的共享内存区。每个进程一旦设置好该共享内存区,就能根本不涉及内核而访问其中的数据。共享该内存区的进程需要某种形式的同步。
注意没有任何东西限制任何IPC技术只能使用两个进程。我们讲述的技术适用于任意数目的进程。
线程
虽然Unix系统中进程的概念已使用很久,一个给定进程中多个线程(thread)的概念却相对较新。Posix.1线程标准 (称Pthreads)是于1995年通过的。从IPC角度看来,一个给定进程内的所有线程共享同样的全局变量。
然而我们必须关注的是各个线程间对全局数据的同步访问。同步尽管不是一种明确的IPC形式,但它确实伴随许多形式的IPC使用, 以控制对某些共享数据的访问。
3. IPC对象的持续性
我们可以把任意类型的IPC的持续性(persistence)定义成该类型的一个对象一直存在多长时间。以下展示了三种类型的持续性。
- 随进程持续的(process-persistent) , 例如管道和FIFO就是这种对象。
- 随内核持续的(kernel-persisten) 例如System V的消息队列,信号量和共享内存区就是该类对象。Posix的消息队列,信号量和共享内存必须至少是随内核持续的,但也可以是随文件系统持续的,具体取决于实现。
- 随文件系统持续的(filesystem-persistent)。 Posix消息队列,信号量和共享内存如果是使用映射文件实现的(不是必需条件),那么他们就是随文件系统持续的。
在定义一个IPC对象的持续性时我们必须小心,因为它并不总是像看起来的那样。例如管道内的数据是在内核中维护的,但管道具备的是随进程的持续性而不是随内核的持续性:最后一个将某个管道打开着用于读的进程关闭该管道后,内核将丢弃所有的数据并删除该管道。类似的,尽管FIFO在文件系统中有名字,他们也只是具备随进程的持续性,因为最后一个将某个FIFI打开着的进程关闭该FIFI后,FIFI中的数据将被丢弃。
IPC类型 | 持续性 |
---|---|
管道 | 随进程 |
FIFO | 随进程 |
Posix互斥锁 | 随进程 |
Posix条件变量 | 随进程 |
Posix读写锁 | 随进程 |
fcntl记录锁 | 随进程 |
Posix消息队列 | 随内核 |
Posix有名信号量 | 随内核 |
Posix基于内存的信号量 | 随进程 |
Posix共享内存区 | 随内核 |
System V消息队列 | 随内核 |
System V信号量 | 随内核 |
System V共享内存区 | 随内核 |
TCP套接字 | 随进程 |
UDP套接字 | 随进程 |
Unix域套接字 | 随进程 |
注意该列表中没有任何类型的IPC具备随文件系统的持续性,但是我们说过有三种类型的Posix IPC可能会具备该持续性, 这取决于它们的实现。显然,向一个文件写入数据提供了随文件系统的持续性,但这通常不作为一种IPC形式使用。多数形式的IPC并没有在系统重新自举后继续存在的打算,因为进程不可能跨越重新自举继续存活。对于一种给定形式的IPC,要求它具备随文件系统的持续性可能会将其性能降低,而IPC的一个基本的设计目标是高新能。
4. 名字空间
- 当两个或多个无亲缘关系的进程使用某种类型的IPC对象来彼此交换信息时,该IPC对象必须有一个某种形式的名字或标识符,这样其中一个进程(往往是服务器)可以创建该IPC对象,其余进程则可以指定同一个IPC对象。
- 管道没有名字,因为不能用于无亲缘关系的进程间,但是FIFO有一个在文件系统中的Unix路径名作为其标识符(因此可用于无亲缘关系的进程间)。对于一种给定的IPC类型,其可能的名字的集合称为它的名字空间。名字空间非常重要,因为对于除普通管道以外的所有形式的IPC来说,名字是客户与服务器彼此连接以交换消息的手段。
IPC类型 | 用于打开或创建IPC的名字空间 | IPC打开后的标识 | Posix.1 1996 | Unix98 |
---|---|---|---|---|
管道 | 没有名字 | 描述符 | * | * |
FIFO | 路径名 | 描述符 | * | * |
Posix互斥量 | 没有名字 | pthread_mutex_t指针 | * | * |
Posix条件变量 | 没有名字 | pthread_cond_t指针 | * | * |
Posix读写锁 | 没有名字 | pthread_rwlock_t指针 | * | |
fcntl记录上锁 | 路径名 | 描述符 | * | * |
Posix消息队列 | Posix IPC名字 | mqd_t值 | * | * |
Posix有名信号量 | Posix IPC名字 | sem_t指针 | * | * |
Posix基于内存的信号量 | 没有名字 | sem_t指针 | * | * |
Posix共享内存区 | Posix IPC名字 | 描述符 | * | * |
System V消息队列 | key_t键 | System V IPC标识符 | * | |
System V 信号量 | key_t键 | System V IPC标识符 | * | |
System V共享内存区 | key_t键 | System V IPC标识符 | * | |
门 | 路径名 | 描述符 | ||
Sun RPC | 程序/版本 | RPC句柄 | ||
TCP套接字 | IP地址与端口 | 描述符 | .1g | * |
UDP套接字 | ip和端口 | 描述符 | .1g | * |
Unix域套接字 | 路径名 | 描述符 | .1g | * |
尽管Posix.1标准化了信号量,他们依然是可选的。