前置知识篇
1. 进程
2. 线程
进程间通信篇
1. IPC概述
- Linux学习笔记系列文章目录
- 一、前言
- 二、前置条件
- 三、本文参考资料
- 四、正文部分
- 4.1 IPC概述
- 4.2 system-V IPC概述
- 4.3 system-V IPC相关属性
- 4.4 POSIX IPC概述
- 4.5 POSIX IPC相关类型
- 4.6 POSIX IPC相关属性
- 4.7 IPC持续性
- 4.8 IPC比较
- 五、总结
一、前言
在了解完进程线程概念后,现在正式进入进程间通信的篇章,也按照Unix卷2的方式进行编写吧,分为IPC概述、信号、管道、消息传递、同步、共享内存这几个篇章。
二、前置条件
UB18 + 一点点的基础知识
三、本文参考资料
Unix卷2
百度
野火Linux教程
四、正文部分 4.1 IPC概述
Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。
而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室 以及 BSD(加州大学伯克利分校的伯克利软件发布中心),他们在进程间通信方面的侧重点有所不同;
前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system-V IPC”,通信进程局限在单个计算机内(同一个设备的不同进程间通讯);
而后者则跳过了该限制,形成了基于套接字(socket)的进程间通信机制(多用于不同设备的进程间通讯)。
Linux则把两者继承了下来,所以说Linux才是最成功的,既有“system-V IPC”,又支持“socket”。
-
本地进程间的通信方式(IPC)
消息传递(管道、FIFO、消息队列)
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内存(匿名的和具名的)
远程过程调用(Solaris门和Sun RPC) -
网络中进程之间通信
UNIX BSD的套接字(socket)
UNIX System V的TLI(已经被淘汰)
4.2 system-V IPC概述
消息队列、共享内存 和 信号量 被统称为 system-V IPC,V 是罗马数字5,是 Unix 的AT&T 分支的其中一个版本,一般习惯称呼他们为 IPC对象,
这些对象的操作接口都比较类似,在系统中他们都使用一种叫做 key 的键值来唯一标识,
而且他们都是“持续性”资源——即他们被创建之后, 不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们。
Linux的IPC对象(包括消息队列、共享内存和信号量)在内核内部使用链表维护,不同的对象使用 IPC标识符 来标识,如消息队列标识符 msqid、共享内存标识符 shmid,信号量标识符 semid。
对于用户来说,内核提供了简洁的接口,不同的进程通过 IPC关键字(key) 即可访问具体的对象。
通过如下 ipcs 命令可以查看系统当前的IPC对象,没有使用的情况下可能为空:
4.3 system-V IPC相关属性
-
ftok函数
三种类型均采用key_t值作为他们的名字,将其定义为一个至少32位的整数。
这个整数由ftok函数,将一个已存在的路径名和一个整数标识符,转换成一个key_t值,称为IPC键 ==> IPC = ftok(path + id)key_t ftok(const char *pathname, int id);
该函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键。
pathname不得是反复创建和删除的,因为这样系统赋予的索引节点号可能不同,导致对于下一个调用者ftok的返回键也可能不同
ftok的典型实现调用stat函数(获取文件信息),然后组合以下三个值,产生一个32位键IPC(高序 --> 低序: id(8) + st_dev(12) + st_ino(12) 实现方式不唯一)struct stat stat; stat("/usr/tmp", &stat); printf("%lx , %lx, %xn", (u_long)stat_st_dev, (u_long)stat.st_ino, ftok("/usr/tmp", 0x57)); a. pathname所在的文件系统的信息(st_dev成员) b. 该文件在本文件系统内的索引节点号(st_ino成员) c. id的低序8位(不能为0,可能导致行为未指定或是报错) -
ipc_perm结构
内核给每个IPC对象维护一个信息结构,其内容跟内核给文件维护的信息类似。struct ipc_perm { __kernel_key_t key; __kernel_uid_t uid; __kernel_gid_t gid; __kernel_uid_t cuid; __kernel_gid_t cgid; __kernel_mode_t mode; unsigned short seq; }; -
创建或打开一个IPC对象
创建或打开一个IPC对象的三个getXXX函数(msg/sem/shm get),第一个参数key的类型是key_t的IPC键,返回值id是一个整数标识符(不同于ftok函数的id参数)
对于key值,应用程序有两种选择a. 调用ftok,给它传递pathname和id b. 指定key位IPC_PRIVATE,这将保证会创建一个新的、唯一的IPC对象
IPC_CREAT未设置是不能创建新对象的,即只设置IPC_EXCL是没有意义的
-
IPC重用性
由于IPC标识符是可以使用在所有进程的,不同于文件的标识符只能用在当前进程内,
为了避免有行为不端的进程尝试遍历获取一个正在使用且允许大家读的消息队列,
故每次重用一个IPC表项时,把返回给调用进程的标识符值增加一个IPC表项数。(重新运行仍递增,即跨进程仍然保持)假设系统配置成最多50个消息队列: #include
#include #include #include #include #include #include int main(void) { int i, msqid; struct msqid_ds info; for (i = 0; i < 10; i++) { msqid = msgget(IPC_PRIVATE, 0677 | IPC_CREAT); msgctl(msqid, IPC_STAT, &info); printf("msqid = %d seq = %un", msqid, info.msg_perm.__seq); msgctl(msqid, IPC_RMID, NULL); } exit(0); }
可移植操作系统接口 (Portable Operating System Interface,缩写为POSIX),
POSIX是IEEE为要在各种UNIX操作系统上运行软件,而定义API接口的一系列互相关联的标准的总称,
其正式称呼为IEEEStd 1003,而国际标准名称为ISO/IEC9945,此标准源于一个大约开始于1985年的项目。
POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。
它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写, 而X则表明其对Unix API的传承。
简单来说,如果应用程序使用POSIX标准的接口来调用系统函数, 那么应用程序将非常容易移植甚至直接兼容到遵循POSIX标准的系统上。
在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread,它是一套通用的线程库,是由 POSIX提出的,因此具有很好的可移植性,使用时必须包含以下头文件:
#include
除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库, 所以在编译时要加上-lpthread 选项。
4.5 POSIX IPC相关类型
这三种POSIX IPC机制具体如下
- 消息队列可以用来在进程间传递消息。
与System V消息队列一样,消息边界被保留了下来,这样读者和写着就以消息为单位进行通信了。
POSIX消息队列允许给每个消息赋一个优先级,这样在队列中优先级较高的消息就会排在优先级较低的消息前面 - 信号量允许多个进程同步各自的动作。
System V 信号量一样,POSIX 信号量也是一个由内核维护的整数,其值永远都不会小于 0。
与 System V 信号量相比,POSIX信号量在用法上要简单一些:它们是逐个分配的(与 System V 信号量集相比),
并且在单个信号量上只能使用两个操作来将信号量的值加 1 或减 1(与 semop()系统调用能原子地在一个 System V 信号量集中的多个信号量上加上或减去一个任意值相比) - 共享内存使得多个进程能够共享同一块内存区域,
与System V共享内存一样,POSIX共享内存提供了一种快速IPC,一旦进程更新了共享内存之后,所发生的变更立即对共享同一区域的其他进程可见。
-
IPC 对象名字
mq / sem / shm均采用路径名标识,但可以不是文件系统中的实际路径名,所以会导致移植性问题
要访问一个POSIX IPC对象就必须通过某种方式来识别出它。
SusV3规定的唯一一种用来标识POSIX IPC对象的可移植方式是使用以斜线打头后面跟着一个或者多个非斜线字符的名字,比如/myproject。
Linux 和其他一些实现(如 Solaris)允许采用这种可移植的命名方式来给 IPC 对象命名
SUSv3 并没有禁止使用形式不为/myobject 的名字,但表示这种名字的语义是由实现定义的。
在一些系统上,创建 IPC 对象名字的规则是不同的。可移植的应用程序中应该将IPC对象名的生成工作放在一个根据目标裁剪过的单独的函数会在头文件中 -
mq_open / sem_open / shm_open
-
o_flag标志位
O_CREAT 用户ID: 均用当前进程的有效用户ID 组ID: sem / shm:当前进程的有效组ID,或者某个系统默认组ID mq:当前进程的有效组ID 若未指定O_EXCL,则无法判断是否为新创建还是原已有 O_EXCL 只有在O_CREAT | O_EXCL时,创建已存在的新对象才会返回EEXIST错误 检查与创建必须为原子操作 O_NONBLOCK 使一个mq为队列为空时读或是满时写不阻塞 O_TRUNC 读写模式打开一个已存在的shm,使该对象的长度被截成0
-
打开或创建后,需要进行权限测试:
a. 若当前进程的有效用户ID为0(超级用户),则允许访问
b. 若当前进程的有效用户ID等于该IPC对象的属主ID,且相应的访问权限位已设置,则允许访问
c. 若当前进程的有效组ID 或 某个辅助组ID 等于该IPC对象的组ID且相应的访问权限位已设置,则允许访问
d. 若其他用户的相应的访问权限位已设置,则允许访问
-
4.7 IPC持续性
分为三类:随进程 / 随内核 / 随文件系统
与 System V IPC 相比,POSIX IPC 拥有下列常规优势
- POSIX IPC 的接口比 System V IPC 接口简单
- POSIX IPC 模型——使用名字替代键、使用 open、close以及 unlink 函数——与传统的 UNIX 文件模型更加一致。
- POSIX IPC 对象是引用计数的。这就简化了对象删除,因为可以断开一个 POSIX IPC对象的链接,并且知道当所有进程都关闭该对象之后对象就会被销毁
然而 System V IPC 具备一个显著的优势:可移植性
五、总结
getXXX函数(msg/sem/shm get),第一个参数key的类型是key_t的IPC键,返回值id是一个整数标识符(不同于ftok函数的id参数)
在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread,它是一套通用的线程库,是由 POSIX提出的,因此具有很好的可移植性。除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库, 所以在编译时要加上-lpthread 选项。
只有在O_CREAT | O_EXCL时,创建已存在的新对象才会返回EEXIST错误,检查与创建必须为原子操作



