UNIX环境高级编程——IPC总结
一、IPC对象的持久性
每种IPC机制都会借助一种数据结构,这种数据结构的实例称为该IPC机制的对象(相应的,用于同步互斥的数据结构的实体也可以称为该机制的对象)。理清IPC对象的持久性,有助于理解相应的IPC的工作机制。
1.对象持久性
大致上IPC对象的持久性可以分为三种:- 进程持久性:具有这种持久性的对象在持有它的最后一个进程关闭了该对象时就会消失。
- 内核持久性:具有这种持久性的对象在两种情形下会消失,(1)系统重启(2)它被显式的删除
- 文件系统持久性:具有这种持久性的对象只有在它被显式删除时才会消失。
下表是常见的IPC对象以及用于同步互斥的对象的持久性
类型 | 持久性 |
管道 FIFO | 进程持久性 进程持久性 |
Posix互斥锁 Posix条件变量 Posix读写锁 Fcntl记录锁 | 进程持久性 进程持久性 进程持久性 进程持久性 |
System V消息队列 System V信号量 System V共享内存 | 内核持久性 内核持久性 内核持久性 |
TCP socket UDP socket Unix域socket | 进程持久性 进程持久性 进程持久性 |
从表中看没有一种对象的持久性是文件系统的。这也是合理的,因为很少有进程能不受系统重启的影响;而且使用文件系统持久性也可能会降低该IPC机制的性能。
2.调用fork,exec,_exit对IPC对象及同步互斥对象的影响
类型 | fork | exec | _exit |
管道和FIFO | 子进程获得父进程的所有打开的描述符的拷贝 | 除非描述符的FD_CLOSEXEC比特被置位了,否则描述符保持打开状态 | 所有描述符都被关闭,在描述符最后一次被关闭时,管道或FIFO中的数据会被删除 |
System V信号量 | 所有的semadj的值在子进程中被设置为0 | 所有的semadj的值被传递给新的程序 | 所有的semadj的值被加到相应的信号量上 |
System V消息队列 | 没影响 | 没影响 | 没影响 |
System V共享内存 | 父进程中已连接上的共享内存被子进程保留 | 去除内存映射 | 去除内存映射 |
mmap共享内存 | 父进程的共享内存被子进程保留 | 去除内存映射 | 去除内存映射 |
Posix互斥锁和条件变量 | 如果在共享内存中并且设置了进程共享属性则就被共享 | 除非位于仍被打开的共享内存中并且具有进程共享属性否则就将消失 | 除非位于仍被打开的共享内存中并且具有进程共享属性否则就将消失 |
Posix读写锁 | 如果在共享内存中并且设置了进程共享属性则就被共享 | 除非位于仍被打开的共享内存中并且具有进程共享属性否则就将消失 | 除非位于仍被打开的共享内存中并且具有进程共享属性否则就将消失 |
fcntl记录锁 | 父进程持有的锁不会被子进程所继承 | 只要描述保持打开,锁就不会因为exec的动作而变化 | 所有被进程持有的锁都会被释放 |
二、System V IPC
System V IPC包含:
- System V消息队列
- System V信号量
- System V共享内存区
1.System V IPC函数汇总
| 消息队列 | 信号量 | 共享内存 |
头文件 | <sys/msg.h> | <sys/sem.h> | <sys/shm.h> |
创建、打开或删除IPC | msgget | semget | shmget |
控制IPC操作 | msgctl | semctl | shmctl |
IPC操作 | msgsend msgrcv | semop | shmat shmdt |
2.key_t键和ftok函数
三种类型的System V IPC使用key_t值作为他们的名字。头文件<sys/types.h>把key_t这个数据类型定义为一个整数,它通常是一个至少32位的整数。这些整数值通常是ftok函数赋赋予的。
函数ftok把一个已经存在的路径和一个整数标识符转换为一个key_t值,叫IPC键(IPC key).
ftok典型的实现调用stat函数,然后组合以下三个值:
1. pathname所在文件系统的信息;
2. 该文件在本文件系统内的索引节点号;
3. id的低序8位;
如果pathname不存在或者当前进程不能访问该文件,则ftok返回-1,另外需要注意的是其pathname用于产生key的文件在使用ftok产生的key的进程运行期间不能被创建和删除,因为每次文件被创建,它会需要一个新索引结点编号,这就会导致下次使用ftok时获得了一个不同的key。
3.ipc_perm结构
内核给每个IPC对象维护一个信息结构,其内容跟内核给文件维护的信息类似。
struct ipc_perm { uid_t uid;//owner的用户ID gid_t gid;//owner的组ID uid_t cuid;//creater的用户ID gid_t cgid;//creater的组ID mode_t mode;//读写模式 ulong_t seq;//序列号 key_t key;//IPC key };
4.创建与打开IPC通道
创建或打开一个IPC对象的三个getXXX函数的都需要一个类型为key_t的IPC键的key值,并且返回一个整数标识符。该标识符不同于ftok函数的id参数。对于key值,应用程序有两种选择:
- 调用ftok,给它传递pathname和id;
- 指定key为IPC_PRIVATE,这将保证会创建一个新的、唯一的IPC对象;
1.key指定为IPC_PRIVATE能保证创建一个唯一的IPC对象。没有一对id和pathname的组合会导致ftok产生IPC_PRIVATE这个键值;
2.设置oflag参数的IPC_CREAT位但不设置它的IPC_EXCL位时,如果指定键的IPC对象不存在,那就创建一个新对象,否则返回该对象;
3.设置oflag参数的IPC_CREAT位和它的IPC_EXCL位时,如果所指定键的IPC对象不存在,那就创建一个新的对象,否则返回一个EEXIST错误,因为该对象已经存在;
4.如果要访问一个已经存在的IPC,就不能指定IPC_PRIVATE标记,因为这是一个特殊的用于创建IPC对象的键值
5.IPC权限
创建一个新的IPC对象时,以下信息就保存该对象的ipc_perm结构中:
1. oflag参数中某些比特会初始化ipc_perm结构的mode成员。
2. cuid和cgid成员分别设置为调用进程的有效用户ID和有效组ID。这两个成员合称为创建者ID。
3. ipc_perm结构的uid和gid成员也分别设置为调用进程的有效用户ID和有效组ID。这两个成员合称为所有者ID。
在创建IPC结构时,除seq以外的所有字段都赋初值。以后,可以调用msgctl、semctl或shmctl修改uid、gid和mode字段。为了改变这些值,调用进程必须是IPC结构的创建者或超级用户。
许可权 | 消息队列 | 信号量 | 共享内存 |
用户读 | MSG_R | SEM_R | SHM_R |
组读 | MSG_R>>3 | SEM_R>>3 | SHM_R>>3 |
其他读 | MSG_R>>6 | SEM_R>>6 | SHM_R>>6 |
需要注意的是:
1.创建者ID永远不会改变,但是进程可以通过IPC机制中的IPC_SET命令修改所有者ID。三个getXXX没有使用UNIX的文件创建模式掩码,IPC对象的权限被设置为指定的值。Posxi IPC非常类似文件,但是System V IPC在权限的存储上是与文件系统的不同的,它的权限不受文件创建模式掩码的影响。
2.任意进程要访问一个IPC对象都需要经历两个层级的检查:一个在IPC对象被打开时(检查是否指定了未包含在ipc_perm结构中的模式,因为创建的IPC对象的权限是存在于ipc_perm结构中的),一个在IPC对象被使用时(过程类似于PosixIPC的权限检查)。
6.ipcs和ipcrm程序
由于System V IPC的三种类型不是以文件系统中路径名标识的,因此使用标准的ls和rm程序无法看到它们,也无法删除他们。不过实现了这些类型IPC的任何系统都提供两个特殊的程序:ipcs和ipcrm。ipcs输出有关System V IPC特性的各种信息,ipcrm则删除一个System V消息队列、信号量集或共享储存区。
7.内核限制
System V IPC的多数实现有内在的内核限制,例如消息队列的最大数目、每个信号量集的最大信号量数等。
另外还存在一些缺点:
- IPC结构是在系统范围内起作用的,没有访问计数。例如,如果创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不被删除。它们余留在系统中直至:由某个进程调用msgrcv或msgctl读消息或删除消息队列,或某个进程执行ipcrm命令删除消息队列;或由正在再启动的系统删除消息队列。
- 这些IPC结构并不按名字为文件系统所知。因此不能用常规的文件系统函数来存取它们或修改它们的特性。为了支持它们不得不增加了十多个全新的系统调用(msgget、semop、shmat等)。不能用ls命令见到它们,不能用rm命令删除它们,不能用chmod命令更改它们的存取权。于是,也不得不增加了全新的命令ipcs和ipcrm。
- 这些IPC不使用文件描述符,所以不能对它们使用多路转接I/O函数:select和poll。