unix网络编程2-IPC-1-简介
继续学习《unix网络编程卷2: 进程间通信》,本篇主要记录第一章简介的相关内容。
1.1 概述
在unix操作系统的历史中,消息传递经历了如下几个阶段。
管道
管道是第一个广泛使用的IPC形式,既可在程序中使用,也可以在shell中使用。管道的问题在于它们只能在具有共同祖先(指父子进程关系)的进程间使用,不过该问题已随有名管道即FIFO的引入解决了。
System V 消息队列
它可以用在同一主机上有亲缘关系或无亲缘关系的进程之间。
有亲缘关系 意味着进程具有共同的祖先,这些有亲缘关系的进程是从祖先进程经过一次或多次fork派生来的。考虑到IPC,父进程可以在调用fork前建立某种形式的IPC(如管道或消息队列),因为它知道随后派生的两个子进程将穿越(across) fork继承该IPC对象。
从理论上说,所有Unix进程与init进程都有亲缘关系,它是在系统自举(reboot)时启动所有初始化进程的祖先进程。
从实践上说,进程的亲缘关系开始于一个登录shell(称为一个会话)以及由该shell派生的所有进程。
Posix消息队列
它可以用在同一主机上有亲缘关系或无亲缘关系的进程之间。
远程过程调用(RPC)
它是从一个系统(客户主机)上某个程序调用另一个系统(服务器主机)上某个函数的一种方法,是作为显式网络编程 的一种替换方法开发的。
既然客户和服务器之间通常传递一些信息(被调用的函数的参数和返回值),而且RPC可以用在同一主机上的客户和服务器之间,因此可以认为RPC是另一种形式的消息传递。
unix系统中各种同步形式的演变如下:
- 记录上锁(record locking)
- System V 信号量
- Posix 信号量
- 互斥锁(mutex)和条件变量
- 读写锁(read-write lock)
1.2 进程、线程与信息共享
下图介绍了Unix进程间共享信息的三种方式。
- 左边的两个进程共享的信息在文件系统中某个文件中。为访问这些信息,每个进程都需要穿越内核(例如read、write、lseek等)。当一个文件有待更新时,某种形式的同步是必要的,这样既可保护多个写入者,防止相互串扰,也可以保护一个或多个读出者,防止写入者的干扰。
- 中间的两个进程共享驻留于内核中的某些信息。管道是这种共享类型的一个例子,System V 消息队列和System V信号量也是。现在访问共享信息的每次操作涉及对内核的一次系统调用。
- 右边的两个进程有一个双方都能访问的共享内存区。每个进程一旦设置好该共享内存区,就能根本不涉及内核而访问其中的数据,共享该内存区的进程需要某种形式的同步。
注:上面为了简述只写了两个进程,技术上支持任意数目的进程。
从IPC角度看,一个给定的进程内所有线程共享相同的全局变量(即共享内存区的概念对这种模型来说是内在的)。然而我们必须关注的是各个线程之间对全局数据的同步访问,同步尽管不是一种明确的IPC形式,但它却是伴随许多形式的IPC使用,以控制对某些共享数据的访问。
本书主要讲述进程间的IPC和线程间的IPC。假设一个线程环境:如果管道为空,调用线程就阻塞在它的read调用上,直到某个线程往该管道写入数据。在支持线程的系统上,只有对空管道调用read的那个线程阻塞,同一进程中的其余线程才可以继续执行。向该空管道写数据的工作既可以由同一进程中的另一个线程去做,也可以由另一个进程中的某个线程去做。
1.3 IPC对象的持续性
任意类型的IPC对象的**持续性(persistence)**:该类型的一个对象一直存在多长时间。
下图展示了三种类型的持续性:
随进程持续的(process-persistent)
IPC对象一直存在到打开该对象的最后一个进程关闭该对象为止。例如管道和IFIO就是这种对象。
随内核持续的(kernel-persistent)
IPC对象一直存在到内核重新自举(reboot)或显示删除该对象为止。例如System V的消息队列、信号量和共享内存区就是此类对象。Posix的消息队列、信号量和共享内存区必须至少是随内核持续的,但也可以是随文件系统持续的
随文件系统持续的(filesystem-persistent)
IPC对象一直存在到显示删除该对象为止。即使内核重新自举了,该对象还是保持其值。Posix消息队列、信号量和共享内存区如果是使用映射文件实现的(非必需条件),那么他们就是filesystem-persistent的。
在定义一个IPC对象的持续性时我们必须小心,因为它并不总是像看起来的那样。
管道内的数据是在内核中维护的,但管道具备的是随进程的持续性而不是随内核的持续性,最后一个将某个管道打开着用于读进程关闭该管道后,内核将丢弃所有的数据并删除数据。
尽量FIFO在文件系统中有名字,它们也只是具备随进程的持续性,因为最后一个将某个FIFO打开着的进程关闭该FIFO后,FIFO中的数据都被丢弃。
下图展示了本书中讲述的各种IPC对象的持续性。
虽然列表中没有任何类型的IPC具备随文件系统的持续性,但是之前说过三种类型的posix IPC可能会具备该持续性,这取决于它们的实现。
显然向一个文件写入数据提供了随文件系统的持续性,但这通常不作为一种IPC形式的调用。多数形式的IPC并没有在系统重新自举后继续存在的打算,因为进程不可能跨越重新自举继续存活。
对于一种给定形式的IPC,要求它具备文件系统的持续性可能会使其性能降低,而IPC的一个设计目标是高性能。
1.4 名字空间(name spaces)
当两个或多个无亲缘关系的进程使用某种类型的IPC对象来彼此交换信息时,该IPC对象必须有一个某种形式的名字(name)或标识符(identifier),这样其中一个进程(往往是服务器)可以创建该IPC对象,其余进程则可以指定同一个IPC对象。
管道没有名字(因此不能用于无亲缘关系的进程间),但是FIFO有一个在文件系统中的Unix路径名作为其标识符(因此可以用于无亲缘关系的进程间)。
对于一种给定的IPC类型,其可能的名字的集合称为它的名字空间。
名字空间非常重要,因为对于除普通管道外的所有形式的IPC来说,名字是客户与服务器彼此连接以交换消息的手段。
下图给出了各种形式IPC的名字空间。
尽管posix.1标准化了信号量,它们仍然是可选的特性。每种特性有强制,未定义,可选三种选择,如下图所示,对于可选的特性,指出了每种特性受支持是(通常在<unistd.h>头文件中)定义的常值的名字,例如_POSIX_THREAD。
1.5 fork、exec和_exit对IPC对象的影响
我们需要理解fork
、exec
和 _exit
函数对于所讨论的各种形式的IPC对象的影响,_exit使用exit调用的一个函数。
1.6 出错处理:包裹函数
包裹函数包装了实际调用的函数,运行实际调用的函数并检测其返回值,遇到错误时终止进程,在一定程度上减少了代码量。本书命名约定是将第一个字母改为大写字母。如:
1 | void |