【编程基础】进程间通信(IPC)

本文深入解析了进程间通信(IPC)的各种方式,包括管道、FIFO、消息队列、信号量、共享内存、Socket和Streams,阐述了每种通信机制的特点、应用场景及使用方法。

进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或者交换信息的过程。常见的方式有:管道(无名管道)、FIFO(命名管道)、消息队列、信号量、共享内存、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上两个进程之间的通信。

管道

管道通常指无名管道,是 UNIX 系统最古老的 IPC 形式。它具有以下特点:

  • 半双工:数据只能在一个方向流动,有固定的读端和写端。
  • 亲缘通信:只能用于具有亲缘关系的进程之间的通信,也就是父子进程间或者兄弟进程间。
  • 只存在于内存中,不属于任何文件系统,但可以使用普通 write、read 函数进行读写,可以看做是一种特殊的文件类型
#include <unistd.h>
int pipe(int fd[2]);

当一个管道建立时,它会创建两个文件描述符,一个为读操作打开,另一个为写操作打开。由于单个进程中的管道几乎没有任何用处,所以通常调用 pipe 的进程紧接着就会调用 fork,从而创建父进程与子进程之间的 IPC 通信。

在这里插入图片描述

左图是 fork 后的读写开启情况;右图是关闭父进程的读端与子进程的写端后,实现数据流从父进程流向子进程。

FIFO

FIFO 也称为命名管道,它是一种文件类型。相比于无名管道,它具有以下特点:

  • FIFO 可以在无关的进程之间进行通信。
  • FIFO 有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

创建函数中的参数 mode 与文件操作函数 open 中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件 I/O 函数操作它。

在 open 一个 FIFO 时,是否设置非阻塞标志(``O_NONBLOCK`)会有下面的区别:

  • 若没有指定 O_NONBLOCK(默认),只读的 open 要阻塞其他进程为了写而打开此 FIFO 的操作。类似的,只写的 open 要阻塞其他进程为了读而打开它的操作。
  • 若指定了 O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错以及返回 -1。如果没有进程已经为了读而打开该 FIFO,其 errno 置为 ENXIO。

FIFO 的通信方式类似于在进程中使用文件来传输数据,只不过 FIFO 类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。

FIFO 的通信方式可以应用在客户进程—服务器进程之间的通信,write_fifo 的应用于客户端,可以打开多个客户端向一个服务器发送请求信息,read_fifo应用于服务器,它适时监控着 FIFO 的读端,当有数据时,读出并进行处理,但是有一个关键的问题是,每一个客户端必须预先知道服务器提供的 FIFO 接口

消息队列

消息队列是消息的链接表,存放在内核中。一个消息队列由一个标识符(队列 ID)来标识。它具有以下特点:

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  • 消息队列可以实现消息的随机查询,不一定要以先进先出的顺序读取,也可以按照消息的类型进行读取。
#include <sys/msg.h>
int msgget(key_t key, int flag);
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);  // type 用以选取消息类型。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

信号量

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semop(int semid, struct sembuf semoparray[], size_t numops);  
int semctl(int semid, int sem_num, int cmd, ...);

共享内存

共享内存指两个或者多个进程共享一个给定的存储区。它具有以下特点:

  • 速度最快的 IPC,因为进程直接对内存进行存取。
  • 多个进程可以同时操作,因此需要同步
  • 常与信号量一起使用,信号量用于同步对共享内存的访问。
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
void *shmat(int shm_id, const void *addr, int flag);
int shmdt(void *addr); 
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

当用 shmget 函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为 0 。当一段共享内存被创建以后,它并不能被任何进程访问。必须使用 shmat 函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

套接字 Socket

socket 属于更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值