Linux进程间通信
Linux进程间通信
- 进程间通信(IPC)详解
- 进程间通信介绍
- 进程间通信的概念
- 进程间通信的目的
- 进程间通信的本质
- 进程间通信的分类
- 管道
- 什么是管道
- 匿名管道
- 匿名管道的原理
- `pipe` 函数
- 匿名管道使用步骤
- 管道读写规则
- 管道的特点
- 管道的四种特殊情况
- 管道的大小
- 命名管道
- 命名管道的原理
- 使用命令创建命名管道
- 创建命名管道(程序)
- 命名管道的打开规则
- 用命名管道实现 Server & Client 通信
- 用命名管道实现派发计算任务
- 用命名管道实现进程遥控
- 用命名管道实现文件拷贝
- 命名管道与匿名管道的区别
- 命令行中的管道
- System V 进程间通信详解
- System V 进程间通信概述
- System V 共享内存
- 基本原理
- 数据结构
- 创建与释放
- 创建共享内存
- 释放共享内存
- 关联与去关联
- 关联共享内存
- 去关联共享内存
- 用共享内存实现 Server & Client 通信
- 与管道的对比
- System V 消息队列
- 基本原理
- 数据结构
- 创建与释放
- 创建消息队列
- 释放消息队列
- 发送与接收数据
- 发送数据
- 接收数据
- System V 信号量
- 相关概念
- 数据结构
- 相关函数
- 创建信号量集
- 删除信号量集
- 操作信号量
- System V IPC 的联系
- 总结
进程间通信(IPC)详解
进程间通信介绍
进程间通信的概念
进程间通信(Interprocess Communication,简称IPC)是指在不同进程之间传播或交换信息的过程。由于进程之间具有独立性(尤其在数据层面),实现通信需要借助操作系统提供的机制。
进程间通信的目的
- 数据传输:一个进程将数据发送给另一个进程。
- 资源共享:多个进程共享同一资源。
- 通知事件:一个进程向另一个或一组进程发送消息,通知某种事件发生(如进程终止时通知父进程)。
- 进程控制:某些进程需要完全控制另一个进程的执行(如调试进程),包括拦截其异常并实时监控状态变化。
进程间通信的本质
进程间通信的本质是让不同进程访问同一份资源。这份资源通常由操作系统提供,可以是内存区域、文件内核缓冲区等。由于进程的数据独立性,通信必须依赖第三方资源,通过读写该资源实现数据交换。
进程间通信的分类
- 管道
- 匿名管道
- 命名管道
- System V IPC
- 消息队列
- 共享内存
- 信号量
- POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道
什么是管道
管道是Unix中最古老的进程间通信形式,指从一个进程到另一个进程的数据流。例如,使用who | wc -l
命令统计云服务器登录用户数,其中who
进程通过管道将数据传递给wc
进程,完成数据传输和处理。
匿名管道
匿名管道的原理
匿名管道适用于本地父子进程间的通信。其核心原理是让父子进程共享同一份文件资源(由操作系统维护),通过对该文件的读写实现通信。
注意:
- 该文件资源是内存文件,不会将数据刷新到磁盘(避免IO开销)。
- 父子进程操作该文件时,缓冲区数据不会触发写时拷贝。
pipe
函数
pipe
函数用于创建匿名管道,原型如下:
int pipe(int pipefd[2]);
- 参数:
pipefd
是一个数组,返回两个文件描述符:pipefd[0]
:读端文件描述符pipefd[1]
:写端文件描述符
- 返回值:成功返回0,失败返回-1。
匿名管道使用步骤
- 父进程调用
pipe
创建管道。 - 父进程通过
fork
创建子进程。 - 根据通信方向,关闭不必要的读写端(如父进程关闭写端,子进程关闭读端)。
示例代码(子进程写,父进程读):
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int fd[2];
if (pipe(fd) < 0) {
perror("pipe");
return 1;
}
pid_t id = fork();
if (id == 0) { // 子进程
close(fd[0]); // 关闭读端
const char* msg = "hello father, I am child...\n";
int count = 10;
while (count--) {
write(fd[1], msg, strlen(msg));
sleep(1);
}
close(fd[1]);
exit(0);
}
// 父进程
close(fd[1]); // 关闭写端
char buff[64];
while (1) {
ssize_t s = read(fd[0], buff, sizeof(buff) - 1);
if (s > 0) {
buff[s] = '\0';
printf("child send to father: %s", buff);
} else if (s == 0) {
printf("read file end\n");
break;
} else {
printf("read error\n");
break;
}
}
close(fd[0]);
waitpid(id, NULL, 0);
return 0;
}
管道读写规则
- 无数据可读:
- 默认(阻塞):
read
阻塞直到有数据。 O_NONBLOCK
:read
返回-1,errno
为EAGAIN
。
- 默认(阻塞):
- 管道已满:
- 默认(阻塞):
write
阻塞直到数据被读取。 O_NONBLOCK
:write
返回-1,errno
为EAGAIN
。
- 默认(阻塞):
- 写端全关闭:
read
返回0。 - 读端全关闭:
write
触发SIGPIPE
信号,可能导致进程退出。 - 写入数据量:
- 小于
PIPE_BUF
:保证原子性。 - 大于
PIPE_BUF
:不保证原子性。
- 小于
管道的特点
- 自带同步与互斥:
- 管道是临界资源,同一时刻只允许一个进程操作。
- 同步:读写操作按序协调(如写后读)。
- 互斥:多个进程不能同时访问。
- 生命周期随进程:所有相关进程退出后,管道资源被释放。
- 流式服务:数据无明确分段,读取长度任意。
- 半双工通信:数据单向流动,双向通信需创建两个管道。
管道的四种特殊情况
- 写端不写,读端一直读:读端挂起,直到管道有数据。
- 读端不读,写端一直写:管道满时写端挂起,直到数据被读取。
- 写端关闭,读端读完:读端继续执行后续逻辑,不挂起。
- 读端关闭,写端继续写:写端进程被操作系统杀掉,收到
SIGPIPE
信号。
验证第四种情况(代码略,结果显示收到信号13,即 SIGPIPE
)。
管道的大小
管道容量有限,写满时会阻塞。测试方法:
- man 手册:Linux 2.6.11 后,默认容量为 65536 字节。
- ulimit -a:显示 4096 字节(512 × 8)。
- 代码测试:写端持续写入,读端不读,实测容量为 65536 字节。
命名管道
命名管道的原理
命名管道(FIFO)适用于无亲缘关系的进程间通信。它是一个特殊文件类型,通过文件名让不同进程访问同一资源。
注意:
- 命名管道是内存文件,磁盘映像大小恒为0。
- 与普通文件不同,命名管道专为通信设计,安全性更高。
使用命令创建命名管道
使用 mkfifo
命令:
mkfifo fifo
创建后文件类型为 p
,可用作通信媒介。
创建命名管道(程序)
使用 mkfifo
函数:
int mkfifo(const char *pathname, mode_t mode);
- 参数:
pathname
:管道文件路径或名称。mode
:权限(如0666
,受umask
影响)。
- 返回值:成功返回0,失败返回-1。
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FILE_NAME "myfifo"
int main() {
umask(0);
if (mkfifo(FILE_NAME, 0666) < 0) {
perror("mkfifo");
return 1;
}
return 0;
}
命名管道的打开规则
- 读打开:
- 默认(阻塞):等待写端打开。
O_NONBLOCK
:立即返回成功。
- 写打开:
- 默认(阻塞):等待读端打开。
O_NONBLOCK
:立即返回失败(ENXIO
)。
用命名管道实现 Server & Client 通信
服务端(创建并读取管道):
#include "comm.h"
int main() {
umask(0);
if (mkfifo(FILE_NAME, 0666) < 0) {
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME, O_RDONLY);
if (fd < 0) {
perror("open");
return 2;
}
char msg[128];
while (1) {
ssize_t s = read(fd, msg, sizeof(msg) - 1);
if (s > 0) {
msg[s] = '\0';
printf("client# %s\n", msg);
} else if (s == 0) {
printf("client quit!\n");
break;
} else {
printf("read error!\n");
break;
}
}
close(fd);
return 0;
}
客户端(写入管道):
#include "comm.h"
int main() {
int fd = open(FILE_NAME, O_WRONLY);
if (fd < 0) {
perror("open");
return 1;
}
char msg[128];
while (1) {
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, msg, sizeof(msg) - 1);
if (s > 0) {
msg[s - 1] = '\0';
write(fd, msg, strlen(msg));
}
}
close(fd);
return 0;
}
头文件(comm.h
):
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define FILE_NAME "myfifo"
运行:先启动服务端,再启动客户端,验证通信成功。
用命名管道实现派发计算任务
服务端接收客户端发送的计算请求(如 3+5
),计算并输出结果:
#include "comm.h"
int main() {
umask(0);
if (mkfifo(FILE_NAME, 0666) < 0) {
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME, O_RDONLY);
if (fd < 0) {
perror("open");
return 2;
}
char msg[128];
while (1) {
ssize_t s = read(fd, msg, sizeof(msg) - 1);
if (s > 0) {
msg[s] = '\0';
printf("client# %s\n", msg);
char* lable = "+-*/%";
int flag = 0;
for (char* p = msg; *p; p++) {
if (*p == '+') flag = 0;
else if (*p == '-') flag = 1;
else if (*p == '*') flag = 2;
else if (*p == '/') flag = 3;
else if (*p == '%') flag = 4;
}
char* data1 = strtok(msg, "+-*/%");
char* data2 = strtok(NULL, "+-*/%");
int num1 = atoi(data1), num2 = atoi(data2), ret = 0;
switch (flag) {
case 0: ret = num1 + num2; break;
case 1: ret = num1 - num2; break;
case 2: ret = num1 * num2; break;
case 3: ret = num1 / num2; break;
case 4: ret = num1 % num2; break;
}
printf("%d %c %d = %d\n", num1, lable[flag], num2, ret);
} else if (s == 0) {
printf("client quit!\n");
break;
} else {
printf("read error!\n");
break;
}
}
close(fd);
return 0;
}
用命名管道实现进程遥控
客户端发送命令(如 ls
),服务端执行:
#include "comm.h"
int main() {
umask(0);
if (mkfifo(FILE_NAME, 0666) < 0) {
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME, O_RDONLY);
if (fd < 0) {
perror("open");
return 2;
}
char msg[128];
while (1) {
ssize_t s = read(fd, msg, sizeof(msg) - 1);
if (s > 0) {
msg[s] = '\0';
printf("client# %s\n", msg);
if (fork() == 0) {
execlp(msg, msg, NULL);
exit(1);
}
waitpid(-1, NULL, 0);
} else if (s == 0) {
printf("client quit!\n");
break;
} else {
printf("read error!\n");
break;
}
}
close(fd);
return 0;
}
用命名管道实现文件拷贝
服务端(读取管道,写入新文件):
#include "comm.h"
int main() {
umask(0);
if (mkfifo(FILE_NAME, 0666) < 0) {
perror("mkfifo");
return 1;
}
int fd = open(FILE_NAME, O_RDONLY);
if (fd < 0) {
perror("open");
return 2;
}
int fdout = open("file-bat.txt", O_CREAT | O_WRONLY, 0666);
if (fdout < 0) {
perror("open");
return 3;
}
char msg[128];
while (1) {
ssize_t s = read(fd, msg, sizeof(msg) - 1);
if (s > 0) {
write(fdout, msg, s);
} else if (s == 0) {
printf("client quit!\n");
break;
} else {
printf("read error!\n");
break;
}
}
close(fd);
close(fdout);
return 0;
}
客户端(读取文件,写入管道):
#include "comm.h"
int main() {
int fd = open(FILE_NAME, O_WRONLY);
if (fd < 0) {
perror("open");
return 1;
}
int fdin = open("file.txt", O_RDONLY);
if (fdin < 0) {
perror("open");
return 2;
}
char msg[128];
while (1) {
ssize_t s = read(fdin, msg, sizeof(msg));
if (s > 0) {
write(fd, msg, s);
} else if (s == 0) {
printf("read end of file!\n");
break;
} else {
printf("read error!\n");
break;
}
}
close(fd);
close(fdin);
return 0;
}
意义:本地拷贝看似简单,但若将管道视为网络,可实现文件上传/下载功能。
命名管道与匿名管道的区别
- 创建与打开:
- 匿名管道:
pipe
函数创建并打开。 - 命名管道:
mkfifo
创建,open
打开。
- 匿名管道:
- 语义:创建与打开方式不同,其余操作一致。
命令行中的管道
命令行管道(|
)如 cat data.txt | grep dragon
,连接的进程(如 cat
和 grep
)由同一父进程(bash)创建,互为兄弟进程,具有亲缘关系,且无磁盘上的命名管道文件,因此是匿名管道。
System V 进程间通信详解
我们详细探讨了进程间通信(IPC)的概念、目的、本质以及基于管道的实现方式,包括匿名管道和命名管道。从本质上看,管道通信依赖于文件系统,操作系统并未对其进行过多专门设计。而 System V IPC 则是操作系统针对进程间通信特别设计的一套机制,尽管其本质仍是通过让不同进程看到同一份资源来实现通信,但它提供了更高效、更灵活的方式。本篇博客将深入剖析 System V IPC 的三种主要形式:共享内存、消息队列 和 信号量,并探讨它们之间的联系与应用。
System V 进程间通信概述
System V IPC 是 Unix System V 体系中定义的一组进程间通信机制,包括以下三种核心方式:
- System V 共享内存:允许不同进程直接访问同一块物理内存,实现高效的数据共享。
- System V 消息队列:通过队列结构在进程间传递带有类型的数据块。
- System V 信号量:用于协调进程间的同步与互斥,确保临界资源的安全访问。
其中,共享内存和消息队列以数据传输为主要目的,而信号量则专注于进程间的协调。虽然信号量看似与通信无直接关联,但它是保障共享资源访问秩序的重要工具,因此也被归入 IPC 范畴。
我们可以将这三者比喻为现实生活中的场景:
- 共享内存和消息队列 好比手机,用于直接传递信息。
- 信号量 则像下棋时的棋钟,确保双方按规则轮流行动。
与管道通信不同,System V IPC 的生命周期随内核而非进程。这意味着,即使创建 IPC 资源的进程退出,资源也不会自动释放,必须显式删除或等待系统重启。这种特性体现了 IPC 资源由内核管理和维护的特点。
System V 共享内存
基本原理
共享内存是 System V IPC 中最直接高效的通信方式。其核心思想是:在物理内存中分配一块空间,并通过操作系统的映射机制,将这块内存分别映射到多个进程的虚拟地址空间。进程通过虚拟地址访问这块共享内存,从而实现数据共享。
具体过程如下:
- 操作系统在物理内存中开辟一块共享内存区域。
- 通过页表映射,将共享内存挂接到各个进程的地址空间。
- 进程通过虚拟地址操作共享内存,实际访问的是同一块物理内存。
这种方式避免了管道通信中频繁的数据拷贝,使得共享内存成为所有 IPC 方式中速度最快的。
数据结构
系统中可能存在多个共享内存实例,操作系统通过内核数据结构对其进行管理。共享内存的核心数据结构是 shmid_ds
,定义如下(位于 /usr/include/linux/shm.h
):
struct shmid_ds {
struct ipc_perm shm_perm; /* 操作权限 */
int shm_segsz; /* 共享内存大小(字节) */
__kernel_time_t shm_atime; /* 最后关联时间 */
__kernel_time_t shm_dtime; /* 最后去关联时间 */
__kernel_time_t shm_ctime; /* 最后修改时间 */
__kernel_ipc_pid_t shm_cpid; /* 创建者 PID */
__kernel_ipc_pid_t shm_lpid; /* 最后操作者 PID */
unsigned short shm_nattch; /* 当前关联进程数 */
/* 其他未使用字段略 */
};
其中,shm_perm
是 ipc_perm
类型结构体,用于存储共享内存的唯一标识 key
值,定义如下(位于 /usr/include/linux/ipc.h
):
struct ipc_perm {
__kernel_key_t key; /* IPC 键值 */
__kernel_uid_t uid; /* 拥有者 UID */
__kernel_gid_t gid; /* 拥有者 GID */
__kernel_uid_t cuid; /* 创建者 UID */
__kernel_gid_t cgid; /* 创建者 GID */
__kernel_mode_t mode; /* 权限模式 */
unsigned short seq; /* 序列号 */
};
key
值是共享内存的全局唯一标识,确保不同进程能找到同一块共享内存。
创建与释放
创建共享内存
创建共享内存使用 shmget
函数:
int shmget(key_t key, size_t size, int shmflg);
- key:共享内存的唯一标识,通常由
ftok
函数生成。 - size:共享内存的大小(字节)。
- shmflg:创建标志,常用组合包括:
IPC_CREAT
:若不存在指定 key 的共享内存则创建,否则返回已有句柄。IPC_CREAT | IPC_EXCL
:确保创建全新共享内存,若已存在则报错。
- 返回值:成功返回共享内存句柄(
shmid
),失败返回 -1。
ftok
函数用于生成 key
值:
key_t ftok(const char *pathname, int proj_id);
- pathname:已有文件的路径名。
- proj_id:整数标识符。
- 注意:不同进程需使用相同参数以生成相同
key
。
示例代码:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define PATHNAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
int main() {
key_t key = ftok(PATHNAME, PROJ_ID);
if (key < 0) {
perror("ftok");
return 1;
}
int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0) {
perror("shmget");
return 2;
}
printf("key: %x, shmid: %d\n", key, shmid);
return 0;
}
运行后,可用 ipcs -m
查看共享内存信息。
释放共享内存
释放共享内存使用 shmctl
函数:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- shmid:共享内存句柄。
- cmd:控制命令,常用
IPC_RMID
表示删除。 - buf:通常设为
NULL
。 - 返回值:成功返回 0,失败返回 -1。
示例:在创建后释放共享内存:
shmctl(shmid, IPC_RMID, NULL);
或使用命令行:ipcrm -m shmid
。
关联与去关联
关联共享内存
将共享内存映射到进程地址空间使用 shmat
函数:
void *shmat(int shmid, const void *shmaddr, int shmflg);
- shmid:共享内存句柄。
- shmaddr:映射地址,通常设为
NULL
由内核自动选择。 - shmflg:标志,如
SHM_RDONLY
(只读)。 - 返回值:成功返回映射地址,失败返回
(void*)-1
。
去关联共享内存
取消映射使用 shmdt
函数:
int shmdt(const void *shmaddr);
- shmaddr:
shmat
返回的地址。 - 返回值:成功返回 0,失败返回 -1。
示例:关联并去关联共享内存:
char *mem = shmat(shmid, NULL, 0);
if (mem == (void*)-1) {
perror("shmat");
return 1;
}
printf("Attached at %p\n", mem);
sleep(2);
shmdt(mem);
用共享内存实现 Server & Client 通信
以下是一个简单的 Server 和 Client 通过共享内存通信的实现:
头文件 comm.h
:
#ifndef _COMM_H_
#define _COMM_H_
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define PATHNAME "/tmp"
#define PROJ_ID 0x6666
#define SIZE 4096
#endif
服务端 server.c
:
#include "comm.h"
int main() {
key_t key = ftok(PATHNAME, PROJ_ID);
int shmid = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666);
if (shmid < 0) {
perror("shmget");
return 1;
}
char *mem = shmat(shmid, NULL, 0);
while (1) {
printf("Client sent: %s\n", mem);
sleep(1);
}
shmdt(mem);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
客户端 client.c
:
#include "comm.h"
int main() {
key_t key = ftok(PATHNAME, PROJ_ID);
int shmid = shmget(key, SIZE, IPC_CREAT);
char *mem = shmat(shmid, NULL, 0);
int i = 0;
while (1) {
mem[i] = 'A' + i;
i++;
mem[i] = '\0';
sleep(1);
}
shmdt(mem);
return 0;
}
运行结果:服务端持续输出客户端写入的字符串,证明通信成功。
与管道的对比
管道通信需要多次数据拷贝(通常四次:用户态到内核态再到用户态),而共享内存只需两次拷贝(输入到共享内存,共享内存到输出),因此速度更快。然而,管道自带同步与互斥机制,而共享内存缺乏保护机制,需配合信号量使用。
System V 消息队列
基本原理
消息队列是一个由操作系统维护的队列,每个队列元素是一个数据块,包含类型(mtype
)和数据(mtext
)。进程通过队列尾添加数据块,从队列头读取数据块。数据块的类型决定其接收者。
特点:
- 支持按类型接收数据。
- 生命周期随内核,需手动删除。
数据结构
消息队列的核心数据结构是 msqid_ds
(位于 /usr/include/linux/msg.h
):
struct msqid_ds {
struct ipc_perm msg_perm; /* 权限 */
struct msg *msg_first; /* 队列首消息 */
struct msg *msg_last; /* 队列尾消息 */
__kernel_time_t msg_stime; /* 最后发送时间 */
__kernel_time_t msg_rtime; /* 最后接收时间 */
__kernel_time_t msg_ctime; /* 最后修改时间 */
unsigned short msg_qnum; /* 队列中消息数 */
unsigned short msg_qbytes; /* 队列最大字节数 */
__kernel_ipc_pid_t msg_lspid; /* 最后发送者 PID */
__kernel_ipc_pid_t msg_lrpid; /* 最后接收者 PID */
};
msg_perm
同为 ipc_perm
类型,包含 key
值。
创建与释放
创建消息队列
使用 msgget
函数:
int msgget(key_t key, int msgflg);
- 参数与
shmget
类似。 - 返回值:成功返回消息队列句柄(
msqid
)。
释放消息队列
使用 msgctl
函数:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- cmd:如
IPC_RMID
。
发送与接收数据
发送数据
使用 msgsnd
函数:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
- msgp:指向
msgbuf
结构:
struct msgbuf {
long mtype; /* 消息类型,大于 0 */
char mtext[1]; /* 消息数据,可自定义大小 */
};
接收数据
使用 msgrcv
函数:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
- msgtyp:指定接收的消息类型。
示例:略(可参考管道通信模式实现)。
System V 信号量
相关概念
信号量是一个计数器,用于保护临界资源,确保进程互斥或同步访问。分为:
- 二元信号量:值为 0 或 1,用于互斥。
- 多元信号量:值大于 1,表示资源份数。
操作包括:
- P 操作:申请资源(计数器减 1)。
- V 操作:释放资源(计数器加 1)。
数据结构
信号量数据结构为 semid_ds
(位于 /usr/include/linux/sem.h
):
struct semid_ds {
struct ipc_perm sem_perm; /* 权限 */
__kernel_time_t sem_otime; /* 最后操作时间 */
__kernel_time_t sem_ctime; /* 最后修改时间 */
struct sem *sem_base; /* 信号量数组指针 */
struct sem_queue *sem_pending; /* 待处理操作 */
unsigned short sem_nsems; /* 信号量个数 */
};
相关函数
创建信号量集
int semget(key_t key, int nsems, int semflg);
- nsems:信号量个数。
删除信号量集
int semctl(int semid, int semnum, int cmd, ...);
操作信号量
int semop(int semid, struct sembuf *sops, unsigned nsops);
- sops:操作结构体数组。
System V IPC 的联系
共享内存、消息队列和信号量的数据结构都以 ipc_perm
作为首个成员。这种设计便于内核统一管理所有 IPC 资源。通过一个 ipc_perm
数组,内核可以快速定位并操作任意 IPC 资源。这种切片式管理提高了效率和一致性。
总结
System V IPC 提供了比管道更灵活和高效的通信方式:
- 共享内存 速度最快,但需自行实现同步。
- 消息队列 支持类型化数据传输,适合结构化通信。
- 信号量 保障资源访问秩序,是其他 IPC 方式的必要补充。