众所周知,进程的地址空间是独立的,每个进程都有自己的虚拟内存,如果不借助内核是无法访问到其他进程的资源的,这样做的好处是提高了安全性,避免发生对其他进程的错误访问造成core dump,但与此同时也带来了弊端,就是无法进行进程间的信息同步(当多个进程需要协作完成一个任务时)。为了解决这一弊端,就需要借助内核完成进程之间的同步操作。(部分IPC在Uinux环境下有Posix标准和System V标准)
目录
管道
管道的特点是先进先出(默认是阻塞的,读取和写入时都有可能会阻塞),一个管道有两个文件描述符,一个文件描述符只能写,另一个文件描述符只能读,写入的数据都存放于内核中。文件描述符会随进程的关闭而关闭
优点:使用方便
缺点:每次读或者写都只能操作一个消息效率低下
使用场景:进程间少量消息的非频繁通信
命名管道
命名管道用于无父子关系的进程之间的通信,以文件名的方式打开文件获得文件描述符访问管道,存放于文件系统中
系统调用:
#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
命名管道实际上可以看作是一个大小为0的文件,访问的方式和普通文件一样都是通过open指定访问方式调用获得文件描述符,也因为如此多进程可以通过文件名来操作同一个管道。
匿名管道
匿名管道用于有父子关系的进程之间的通信,以文件描述符的方式访问管道,存放于内核内存中
系统调用:
#include<unistd.h>
Int pipe(int fd[2]);
匿名管道通过系统调用一次性打开两个文件描述符0用于读数据,1用于写数据,如果要实现父子进程之间的单向通信只需要创建一个管道,在fork产生新进程后在相应的进程中关闭不需要的文件描述符即可,如果要实现父子进程之间的双向通信则要打开两个管道
消息队列
消息队列实际上是一个保存在内核的消息链表,每个消息队列都有一个特有的id,队列id可以通过一个key来获得,对消息队列的操作包括创建和访问消息队列、发送消息、接收消息、删除消息
消息队列的特点:
- 不必像管道一样遵循先进先出的规则,可以按消息类型进行读取
- 消息是有特定格式的并且有特定的优先级
- 队列位于内核中,独立于进程,只要没有删除消息队列,那么即使与队列关联的进程都关闭了队列其存放在其中的消息都会继续存在
缺点:
- 如果忘记关闭消息队列的话,就会使得队列一直存在与内核中,造成内存泄漏
- 由于队列存在于内核中,每次读取或写入都要进行用户态和内核态之间的信息拷贝,极度浪费时间。
相关api:
#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
共享内存
共享内存的实质可以认为是不同进程中的不同虚拟地址映射到了同一个物理地址上。因此即使进程之间的地址空间是相互独立的,也能做到访问相同的物理内存。
在posix的共享内存是通过mmap函数实现的,可以分为将文件映射到共享内存和将一块匿名内存映射到共享内存。
共享内存的优点:
对于共享信息的访问是直接通过访问各自进程的虚拟内存,不需要进行用户态到内核态之间的数据拷贝
共享内存的缺点:
就像进程对全局变量的修改访问一样,进程对共享内存的访问修改访问也会有竞争条件,需要用额外的手段进行互斥。
信号量
信号量不是用于缓存进程间通信数据的容器,而是一个整数计数器,主要用于进程间共享内存的互斥与同步。
信号量主要是基于原子操作来实现的。
控制信号量的原子操作有两种:
- P操作,将信号量的值原子的减1
- V操作,将信号量的值原子的加1
当一个进程调用sem_wait函数等待信号量时,如果此时信号量大于0,则将信号量减一并立即返回,如果此时信号量为0则进程休眠进入阻塞态,直到信号量大于0,再将其减一返回。
二元的信号量类似于互斥锁
信号
信号主要用于进程处于异常状况下通知进程
信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。
1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。
2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,它们用于在任何时候中断或结束某一进程。
使用命令man 7 singal可以查看所有信号
发送信号则使用kill命令
Socket
Socket主要用于不同主机的进程之间的通信(也可以用于同一主机的不同进程),属于网络编程范畴