目录
Linux下的进程间通信(Interprocess Communication,IPC)方式基本上是从Unix继承而来的。对Unix发展做出重大贡献的两个实验室:AT&T的贝尔实验室、BSD在IPC方面的侧重点有所不同。前者对Unix早期的IPC手段进行了系统的改进和扩充,形成了“System V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接字(Socket)的进程间通信机制。Linux则把两者都继承了下来,加上操作系统早期的信号、管道、命名管道,构成了Linux丰富的IPC方式。
下面先介绍几个操作系统最早使用的IPC机制:
信号
信号又称软件中断,本质是对硬件中断的模拟,是操作系统最原始的异步通信机制,Linux提供了一系列函数让用户注册信号,自定义中断处理例程,发送信号等,进程间通过信号进行同步。但信号存在系统开销大、传送信息量有限等问题,所以实际应用中,信号机制常常用于进程间的事件通知,而不应用于复杂的交互操作。
关于中断概念及中断处理请点击:https://blog.csdn.net/qq_37653144/article/details/82929246
关于Linux信号机制请点击:https://blog.csdn.net/qq_37653144/article/details/81942026
管道
管道也称匿名管道,相当于一块环形缓冲区,它只存在于内存中,进程通过对管道的读写来实现数据交互。管道有单工的也有半双工的,单工管道只能单向传输,半双工管道可以实现双向传输,但同一时刻只允许一个方向的传输。管道只存在于内存中的操作系统内核部分,因此只能在有亲属关系(父子进程、兄弟进程)的进程间使用。
Linux管道编程请点击:https://blog.csdn.net/qq_37653144/article/details/83927634
命名管道
命名管道相较于管道去除了只能在有亲属关系的进程间使用的限制,因为命名管道是一个实实在在的文件,存在于文件系统中,因此任何有权限访问该命名管道的进程都能使用命名管道进行数据交互。
Linux命名管道编程请点击:https://blog.csdn.net/qq_37653144/article/details/83932845
System V IPC
需要注意的是,POSIX大体继承了System V IPC,可以说POSIX只是做了统一标准化的工作,而除了在函数命名格式上有所不同外,可以将POSIX的IPC视为System V IPC看待。Linux同时支持这两种标准。
System V IPC由以下三种IPC对象组成,三者都有着类似的结构(下文详述):
消息队列:https://blog.csdn.net/qq_37653144/article/details/83960769
信号量:https://blog.csdn.net/qq_37653144/article/details/83963143
共享内存:https://blog.csdn.net/qq_37653144/article/details/83993840
组成
System V IPC由消息队列、信号量和共享内存三种IPC方式组成,这三种IPC通信方式在编程接口和内部实现上都非常类似,例如都采用类似的控制函数、ipc_perm结构等。
Linux内核提供了相应的头文件用于实现System V IPC,三种不同的通信方式分别对应不同的头文件和动作操作函数,如下表所示:
实现方式 | 头文件 | 创建或打开IPC对象 | IPC对象控制函数 | IPC对象操作函数 |
---|---|---|---|---|
消息队列 | <sys/msg.h> | msgget | msgctl | msgsnd msgcv |
信号量 | <sys/sem.h> | semget | semctl | semop |
共享内存 | <sys/shm.h> | shmget | shmctl | shmat shmdt |
标识符
每一个System V IPC的结构(消息队列、信号量和共享内存)都对应了一个标识符(ID),它是一个非负整数,当一个IPC结构被创建时,和该结构相关的标识符会自动加1并赋予新创建的IPC结构,这个非负整数溢出时会回滚到0重新开始。
标识符的唯一性局限于结构类型内。假设某个消息队列的标识符为1,则这个标识符在所有消息队列中是唯一的,但有可能存在标识符为1的信号量或共享内存。
三种IPC结构的创建函数都有一个key_t类型的参数key,用户进程将这个参数值传递给内核,由内核转换为标识符。
ftok函数
Linux提供了ftok函数用于将一个路径和项目ID转换为关键字,其标准调用格式说明如下:
#include <sys/types.h>
#incldue <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
其中第一个参数pathname必须是一个存在的、可以访问的文件路径名,而第二个参数proj_id则只有低8位有效(如果低8位为0,返回的是一个随机结果)。函数调用成功则返回key_t类型的关键字供进程创建IPC对象使用,否则返回-1。
需要注意的是,ftok函数返回的关键字是根据文件的物理索引(inode) 确定的,因此路径名相同并不意味着frok函数产生的关键字也会相同。
虽然使用IPC机制的进程可以直接利用诸如123这样的整数作为关键字,但它们要在程序编码上保持一致,并且这么做还有一个缺点:其他进程也可能使用这个整数作为另外的IPC资源的关键字,这种情况下可能导致混乱。因此最好利用ftok函数来生成关键字。
结构定义
每一个IPC对象都有一个ipc_perm结构与之对应,这个结构中记录了对象的一些信息,如所有者、创建者和权限等:
struct ipc_perm
{
uid_t uid; //所有者的有效用户ID
gid_t gid; //所有者的有效组ID
uid_t cuid; //创建者的有效用户ID
gid_t cgid; //创建者的有效组ID
mode_t mode; //访问权限
unsigned long seq; //应用序号,每次IPC对象被使用都会加1,溢出后回滚
key_t key; //关键字
};
ipc_perm结构中的mode记录了IPC对象的访问权限,它与文件的访问权限类似,同样分为用户、组用户和其他用户三类。但是这些权限中没有可执行权限。
权限 | 消息队列 | 信号量 | 共享内存 | |
---|---|---|---|---|
用户 | 可读 | MSG_R | SEM_R | SHM_R |
可写 | MSGW | |||
组用户 | 可读 | MSG_R >> 3 | SEM_R >> 3 | SHM_R >> 3 |
可写 | MSG_W >> 3 | SEM_A >> 3 | SHM_W >> 3 | |
其他用户 | 可读 | MSG_R >> 6 | SEM_R >> 6 | SHM_R >> 6 |
可写 | MSG_W >> 6 | SEM_A >> 6 | SHM_W >> 6 |
mode位还经常和IPC_CREATE、IPC_EXCL两种标志位进行位或运算,以完成对IPC对象的创建管理。
#include <sys/ipc.h>
#define IPC_CREATE 01000 /* Create key if key does not exist. */
#define IPC_EXCL 02000 /* Fail if key exists. */
Linux内核提供了相应的函数来创建一个新的或者访问一个已经存在的IPC对象,对其创建或者访问的规则如下:
·指定key为IPC_PRIVATE,操作系统保证创建一个唯一的IPC对象。
·设置flag参数的IPC_CREATE位,但不设置它的IPC_EXCL位时,如果所指定key的IPC对象不存在,就创建一个新的对象,否则返回该对象。
·同时设置flag参数的IPC_CREATE和IPC_EXCL时,如果所指定key的IPC对象不存在,就创建一个新的对象,否则返回一个EEXIST错误。
特点
System V IPC机制具有以下几个特点:
·IPC结构存在于Linux内核空间,它没有计数机制,也不会自我删除,停止使用的IPC结构会一直保留在系统内核空间中,除非主动删除。
·IPC结构不能再文件系统中公开访问,不能使用文件操作的函数来对这些结构进行操作。
·IPC结构没有文件描述符,所以不能对其使用I/O多路复用seclect和epoll,也不能在文件或者设备I/O中使用IPC结构,还不能一次性使用多个IPC结构。
消息队列
消息队列(Message Queue)由链表实现,消息不一定要按照先进先出的形式来读写,因此可以实现消息的随机查询。最重要的是,消息队列可以按格式读写数据,而管道和有名管道只能处理无格式字节流。
消息队列编程:https://blog.csdn.net/tennysonsky/article/details/46331643
信号量
信号量的本质是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,提供对共享资源的互斥访问。而在线程中信号量的应用十分广泛,因为同一进程内的多个线程共享进程的资源,因此线程之间对共享资源的访问是信号量应用的理想场景。
信号量编程:https://blog.csdn.net/qq_37653144/article/details/83963143
共享内存
共享内存就是将一块内存映射到其他进程的虚拟地址空间中,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
共享内存编程:https://blog.csdn.net/qq_37653144/article/details/83993840
套接字
套接字(Socket)最早是由BSD为实现多计算机之间的通信而开发的,在计算机网络通信中套接字起着非常重要的作用。
套接字编程(TCP):https://blog.csdn.net/qq_37653144/article/details/81605294