01 概述
1.1 发展
Linux进程间通信(IPC)大致发展如下:
早期UNIX进程间通信、基于System V进程间通信、基于Socket进程间通信和Posix进程间通信。
UNIX进程间通信方式包括:管道、FIFO、信号。
System V进程间通信方式包括:System V消息队列、System V信号量、System V共享内存。
Posix进程间通信包括:Posix消息队列、Posix信号量、Posix共享内存。
1.2 目的
进程间通信(IPC)的目的:
1、数据传输:一个进程向另一个进程传输数据。
2、共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,其他进程应实时看到。
3、通知事件:一个进程需要向另一个进程发送消息,通知它发生了某种事件(如进程终止时需要通知父进程)。
4、资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制。
5、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
1.3 单机和跨主机通信
同一主机进程间的通信方式:
1、Unix进程间通信方式:PIPE、FIFO和信号
2、SystemV进程间通信方式:信号量(Semaphore)、消息队列(Message Queue)和共享内存(Shared Memory)
3、读写锁、互斥锁、条件变量
4、文件、文件锁
网络主机间的通信方式:
1、RPC:Remote Protocol Call远程过程调用(Thrift、gRPC)
2、Socket:基于TCP/IP协议
02 管道
2.1 概述
管道是Linux系统最古老的IPC方法,可以简单地将管道理解为一个通道,它允许数据从一个进程流向另外一个进程。
2.2 特点
1、管道传输的是无格式字节流
读者从管道中读取任意大小的数据块,而不管写者写入管道的数据块大小,即使用管道时不存在消息边界的概念。此外,通过管道传递的数据是顺序的,从管道中读取出来字节的顺序与它们被写入管道的顺序是完全一样的。
2、从管道中读取数据
试图从一个当前为空的管道中读取数据将会被阻塞直到至少有一个字节被写入到管道为止。如果管道的写入端被关闭了,那么从管道中读取数据的进程在读完管道中剩余的所有数据之后将会看到文件结束。
3、管道是半双工(单工)的(有的系统可能支持全双工),数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
4、管道的容量是有限的。管道其实是一个在内核内存中红维护的缓冲区,这个缓冲区的存储大小是有限的,一旦管道被填满之后,继续向该管道的写入操作就会被阻塞直到读者从管道中移除一些数据为止。
2.3 函数
1、管道创建/关闭
#include<unistd.h>intpipe(intfd[2]);
成功的pipe()调用会在数组fields中返回两个打开的文件描述符:一个表示管道的读取端(fields[0]),另一个表示管道的写入端(fields[1])。
2、管道的一个常见用途是执行shell命令并读取其输出或者向其发送一些输入,popen()和pclose()函数简化了这个任务。
FILE *popen(constchar* command ,constchar* type );intpclose( FILE * stream );
popen函数创建了一个管道,然后创建了一个子进程来执行shell,而shell又建立了一个子进程来执行command字符串。mode参数是一个字符串,它确定调用进程是从管道中读取数据(mode是r)还是将数据写入到管道中(mode是w)。
03 信号
3.1 概述
由于管道是一种无形、无名的文件,它就只能通过fork的过程创建在“近亲”的进程之间,而不可能成为可以在任意两个进程之间建立通信的机制,更不可能称为一般的、通用的进程间通信模型。因此引入有名管道FIFO,它与管道最大的区别在于在文件系统中有名称,并且打开方式与打开普通文件一样,可以实现非相关进程间的通信。
3.2 特点
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
3.3 函数
#include<sys/types.h>#include<sys/stat.h>intmkfifo(constchar*pathname,mode_tmode);
返回:若成功返回0,出错返回-1。
FIFO相关出错信息:
EACCES 无存取权限
EEXIST 指定文件不存在
ENAMETOOLONG 路径名太长
ENOENT 包含的目录不存在
ENOSPC 文件系统剩余空间不足
ENOTDIR 文件路径无效
EROFS 指定的文件存在于只读文件系统中
04 信号
4.1 概述
信号(signal)是Linux进程间通信的一种机制,全称为软中断信号,也被称为软中断。信号本质上是在软件层次上对硬件中断机制的一种模拟。
信号表示一种事件,也可能异步发生。如果程序并未安排怎么处理一个特定的信号,那么该信号出现时程序就作出一个缺省的反应。标准未定义这个缺省反应是什么,但绝大多数编译器选择终止程序。另外,程序可以调用signal函数,或者忽略这个信号,或者设置一个信号处理函数,当信号发生时程序就调用这个函数。
与其他进程间通信方式相比,信号所能传递的信息比较粗糙,只是一个整数。但正是由于传递的信息量少,信号也便于管理和使用,可以用于系统管理相关的任务,例如通知进程终结、中止或恢复等。
4.2 信号名
SIGABRT程序请求异常终止
SIGFPE发生一个算术错误
SIGILL检测到非法指令
SIGSEGV检测到对内存的非法访问
SIGINT收到一个交互性注意信号
SIGTERM收到一个终止程序的请求
注:前几个都是同步的,SIGINT、SIGTERM是异步的,它们在程序外部发生,同城是由程序的用户触发。
4.3 函数
void (*signal(intsig , void (*func)(int)))(int);
参数:
sig:信号编号
void(*func)(int):信号处理函数
返回值:类型是一个信号处理函数的函数指针
SIG_ERR错误返回结果(void(*)(int))(-1)
SIG_IGN忽略某个信号(void(*)(int))(0)
SIG_DFL默认处理某个信号(void(*)(int))(1)
信号集操作
对信号集进行信号的清空、加入、删除等操作。
intsigemptyset(sigset_t *set);将set集合置空
intsigfillset(sigset_t *set);将所有信号加入set集合
intsigaddset(sigset_t *set,intsigno)将signo信号加入到set集合
intsigdelset(sigset_t *set,intsigno);从set集合中移除signo信号
intsigismember(constsigset_t *set,intsigno);signo判断信号是否存在于set集合中
设置或获取信号集的信号屏蔽字:
intsigprocmask(inthow,constsigset_t*restrictset,sigset_t*restritoset);
返回值:成功返回0,出错返回-1
SIG_BLOCK 追加屏蔽某个信号
SIG_UNBLOCK 取消屏蔽某个信号
SIG_SETMASK 设置新的屏蔽字信号集
如果屏蔽所有信号,可以进行如下设置:
sig_set_tset;
sigfillset(&set);
sigprocmask(SIG_BLOCK, &set,NULL);
等价于如下设置:
sig_set_tset;
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set,NULL);
如果清空所有信号屏蔽字集合,可以进行如下设置:
sig_set_tset;
sigfillset(&set);
sigprocmask(UN_BLOCK, &set,NULL);
等价于如下设置:
sig_set_tset;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set,NULL);
获取当前信号屏蔽字信号集:
sig_set_tset;
sigemptyset(&set);
sigprocmask(SIG_BLOCK,NULL, &set);
05 消息队列
5.1 概述
消息队列是由消息的链表,存储在内核中并由消息队列标识符标识。消息队列克服了信号传递信号量少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
与管道和FIFO不同,进程可以在没有另外一个进程等待读的情况下进行写。另外一方面,管道和FIFO一旦相关进程都关闭并退出后,里面的数据也就没有了,但是对于消息队列,一个进程往消息队列中写入数据后退出,另外一个进程仍然可以打开并读取消息。消息队列与UNIX域套接字相比,在速度上没有多少优势。
5.2 函数
System V定义:
#include<sys/msg.h>
intmsgget(key_tkey,intflag);
intmsgctl(intmsgid,intcmd,struct msqid_ds *buf);
intmsgsnd(intmsgqid,constvoid*ptr,size_tnbytes,intflag);
size_tmsgrcv(intmsgqid,void*ptr,size_tnbytes,longtype,intflag);
Posix定义:
#include<mqueue.h>
mqd_tmq_open(constchar*name,intoflag,/* mode_t mode, struct mq_attr *attr */);
mqd_tmq_close(mqd_tmqdes);
mqd_tmq_unlink(constchar*name);
mqd_tmq_send(mqd_tmqdes,constchar*msg_ptr,size_tmsg_len,unsignedmsg_prio);
mqd_tmq_receive(mqd_tmqdes,char*msg_ptr,size_tmsg_len,unsigned*msg_prio);
06 共享内存
6.1 概述
共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。
共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
6.2 函数
SYSTEM V定义:
intshmget(key_tkey,intsize,intshmflg);
void*shmat(intshmid,constvoid*shmaddr,intshmflg);
intshmdt(constvoid*shmaddr);
intshmctl(intshmid,intcmd, struct shmid_ds *buf);
POSIX定义:
intshm_open(constchar*name,intoflag,mode_tmode);
intshm_unlink(constchar*name);
intftruncate(intfd,off_tlength);
由于POSIX标准比较通用,一般建议使用该标准定义的方法集。
07 信号量
7.1 概述
信号量(semaphore),有时称为信号灯,是多线程环境下使用的一种设施,可以用来保证两个或多个关键代码段不被并发调用(就是具有原子性的计数器)。
信号量是一个计数器,可用来控制多个进程对共享资源的访问。它常常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因为,作为进程间以及同一进程内不同线程之间的同步手段。
7.2 函数
System V定义:
#incldue<sys/sem.h>
intsemget(key_tkey,intnsems,intflag);
intsemctl(intsemid,intsemnum,intcmd,.../*union semun arg*/)
intsemop(intsemid,struct sembuf *semop,size_tnops);
Posix定义:
intsem_init(sem_t*sem,intpshared,unsignedintvalue);
intsem_wait(sem_t*sem);
intsem_trywait(sem_t* sem);
intsem_timedwait(sem_t*sem,conststruct timespec *abs_timeout);
intsem_post(sem_t*sem);
intsem_close(sem_t*sem);
intsem_unlink(constchar*name);
intsem_destroy(sem_t*sem);
08 内存映射
8.1 概述
mmap()系统调用在调用进程的虚拟地址空间中创建一个新内存映射,映射分为两种:
1、文件映射
文件映射将一个文件的一部分直接映射到调用进程的虚拟内存中,一旦一个文件被映射之后就可以通过在相应的内存区域中操作字节来访问文件内容了。映射的分页会在需要的时候从文件中(自动)加载。这种映射也被称为基于文件的映射或内存映射文件。
2、匿名映射
一个匿名映射没有对应的文件,相反,这种映射的分页会被初始化为0。
8.2 函数
void*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffset);
intmunmap(void*start,size_tlength);
参数:start:映射区的开始地址。
length:映射区的长度。
prot:期望的内存保护标志,不能与文件的打开模式冲突。
是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC//页内容可以被执行
PROT_READ//页内容可以被读取
PROT_WRITE//页可以被写入
PROT_NONE//页不可访问flags:指定映射对象的类型,映射选项和映射页是否可以共享。
09 RPC
远程过程调用(Remote Protocol Call),跨主机通信。
10 套接字
网络通信,包括UDP和TCP两种,单机或跨主机通信。