进程间通信(IPC)是操作系统提供的用于不同进程之间共享数据或协调工作的机制。
总结与选型建议
场景 | 推荐 IPC 机制 | 理由 |
---|---|---|
父子进程简单通信 | 匿名管道 | 轻量、无需持久化 |
跨进程持久化通信 | 具名管道(FIFO) | 文件系统可见,支持多进程 |
高性能数据共享 | 共享内存 + 信号量 | 零拷贝,适合频繁数据交换 |
进程同步(如互斥锁) | POSIX 具名信号量 | 接口简洁,跨进程兼容性好 |
跨网络通信 | 套接字(TCP/UDP) | 支持远程通信,灵活可靠 |
异步事件通知 | 信号(如 SIGUSR1) | 简单快捷,适合紧急操作 |
目录
应用场景:Shell命令中的管道符 |,如 ls | grep .txt。
应用场景:进程终止(SIGTERM)、调试(SIGTRAP)、自定义事件处理。
1. 管道(Pipe)
匿名管道(Pipe)
-
特点:
-
仅适用于父子进程或有共同祖先的进程(如
fork()
创建的子进程)。 -
单向通信(半双工),一端写,另一端读。
-
基于内存缓冲区,无持久性,通信结束后自动销毁。
-
容量有限(通常几KB到几十KB)。
-
-
示例:
int fd[2];
pipe(fd); // 创建管道,fd[0]读端,fd[1]写端
if (fork() == 0) {
close(fd[1]); // 子进程关闭写端
read(fd[0], buf, sizeof(buf));
} else {
close(fd[0]); // 父进程关闭读端
write(fd[1], "Hello", 6);
}
具体例子:
父进程向子进程发送字符串。
#include <stdio.h> #include <unistd.h> #include <string.h> int main() { int fd[2]; char buf[100]; pipe(fd); // 创建管道 if (fork() == 0) { // 子进程 close(fd[1]); // 关闭写端 read(fd[0], buf, sizeof(buf)); printf("Child received: %s\n", buf); close(fd[0]); } else { // 父进程 close(fd[0]); // 关闭读端 const char *msg = "Hello from parent!"; write(fd[1], msg, strlen(msg) + 1); close(fd[1]); wait(NULL); // 等待子进程结束 } return 0; }
运行与输出:
gcc pipe_example.c -o pipe_example && ./pipe_example # 输出:Child received: Hello from parent!
-
应用场景:Shell命令中的管道符
|
,如ls | grep .txt
。
具名管道(FIFO)
-
特点:
-
通过文件系统路径标识(如
/tmp/myfifo
),允许任意进程访问。 -
支持多对一通信(多个进程写入,一个进程读取)。
-
需显式创建和删除(
mkfifo
命令或mkfifo()
函数)。
-
-
示例:
mkfifo /tmp/myfifo # 创建具名管道 echo "Data" > /tmp/myfifo & # 进程A写入 cat /tmp/myfifo # 进程B读取
-
应用场景:不同进程间的持久化通信,如日志收集。
具体例子:
场景:两个独立进程通过 FIFO 文件通信。
创建 FIFO 文件:
mkfifo /tmp/myfifo
进程 A(写入):
echo "Data from Process A" > /tmp/myfifo
进程 B(读取):
cat /tmp/myfifo # 输出:Data from Process A
#include <fcntl.h> #include <stdio.h> #include <unistd.h> int main() { int fd = open("/tmp/myfifo", O_WRONLY); write(fd, "Hello via FIFO!", 15); close(fd); return 0; }
2. 套接字(Socket)
-
特点:
-
支持跨网络通信(如 TCP/UDP)和本地通信(Unix域套接字)。
-
全双工(双向通信)。
-
需绑定地址和端口,支持多对多通信。
-
-
示例(Unix域套接字):
// 服务端 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr = {.sun_family = AF_UNIX, .sun_path = "/tmp/sock"}; bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); listen(sockfd, 5); accept(sockfd, ...); // 等待连接 // 客户端 connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
-
应用场景:分布式系统通信、Web服务器与浏览器交互。
具体例子:
场景:本地进程间传输消息。
服务端代码:
#include <stdio.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> int main() { int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr = {.sun_family = AF_UNIX, .sun_path = "/tmp/mysock"}; unlink("/tmp/mysock"); // 确保套接字文件不存在 bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); listen(sockfd, 5); int clientfd = accept(sockfd, NULL, NULL); char buf[100]; read(clientfd, buf, sizeof(buf)); printf("Server received: %s\n", buf); close(clientfd); close(sockfd); return 0; }
客户端代码:
#include <stdio.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> int main() { int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr = {.sun_family = AF_UNIX, .sun_path = "/tmp/mysock"}; connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)); write(sockfd, "Hello from client!", 18); close(sockfd); return 0; }
运行步骤:
编译并启动服务端:
gcc server.c -o server && ./server
在另一个终端运行客户端:
gcc client.c -o client && ./client
服务端输出:
Server received: Hello from client!
3. 信号(Signal)
-
特点:
-
异步通信:通过发送信号(如
SIGINT
、SIGKILL
)通知进程事件。 -
不传递数据,仅通知事件类型。
-
需注册信号处理函数(
signal()
或sigaction()
)。
-
-
示例:
void handler(int sig) { printf("Received SIGINT!\n"); } signal(SIGINT, handler); // 注册SIGINT处理函数
-
应用场景:进程终止(
SIGTERM
)、调试(SIGTRAP
)、自定义事件处理。
4. System-V IPC 对象
共享内存(Shared Memory)
-
特点:
-
效率最高(直接访问同一块内存)。
-
需同步机制(如信号量)避免竞争。
-
需显式创建和释放(
shmget()
、shmat()
、shmdt()
)。
-
-
示例:
key_t key = ftok("/tmp", 'A'); int shmid = shmget(key, 1024, 0666 | IPC_CREAT); char *shm = shmat(shmid, NULL, 0); sprintf(shm, "Shared Data"); // 写入 shmdt(shm); // 断开连接
-
应用场景:高性能数据共享(如数据库缓存)。
具体例子:
场景:两个进程通过共享内存交换数据。
写入进程:#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> int main() { key_t key = ftok("shmfile", 65); int shmid = shmget(key, 1024, 0666 | IPC_CREAT); char *str = (char*)shmat(shmid, NULL, 0); sprintf(str, "Shared Memory Data"); printf("Data written: %s\n", str); shmdt(str); return 0; }
读取进程:
#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> int main() { key_t key = ftok("shmfile", 65); int shmid = shmget(key, 1024, 0666 | IPC_CREAT); char *str = (char*)shmat(shmid, NULL, 0); printf("Data read: %s\n", str); shmdt(str); shmctl(shmid, IPC_RMID, NULL); // 删除共享内存 return 0; }
运行步骤:
编译并运行写入进程:
gcc write_shm.c -o write_shm && ./write_shm # 输出:Data written: Shared Memory Data
运行读取进程:
gcc read_shm.c -o read_shm && ./read_shm # 输出:Data read: Shared Memory Data
消息队列(Message Queue)
-
特点:
-
类似管道,但支持消息类型标识(按类型读取)。
-
消息持久化(即使进程退出仍存在)。
-
容量较大(受系统配置限制)。
-
-
示例:
struct msgbuf { long mtype; char mtext[100]; }; key_t key = ftok("/tmp", 'B'); int msgid = msgget(key, 0666 | IPC_CREAT); msgsnd(msgid, &msg, sizeof(msg), 0); // 发送 msgrcv(msgid, &msg, sizeof(msg), 1, 0); // 接收类型为1的消息
-
应用场景:多进程协作的任务队列。
信号量组(Semaphore Set)
-
特点:
-
用于进程同步(协调资源访问)。
-
提供原子操作(
semop()
)增减信号量值。
-
-
示例:
key_t key = ftok("/tmp", 'C'); int semid = semget(key, 1, 0666 | IPC_CREAT); semctl(semid, 0, SETVAL, 1); // 初始化为1(互斥锁) struct sembuf op = {0, -1, 0}; // P操作(获取锁) semop(semid, &op, 1); // 临界区操作... op.sem_op = 1; // V操作(释放锁) semop(semid, &op, 1);
-
应用场景:多进程共享资源的互斥访问(如文件写入)。
5. POSIX 信号量
POSIX匿名信号量
-
特点:
-
适用于多线程或共享内存的多进程。
-
需在共享内存中初始化(
sem_init()
)。
-
-
示例:
sem_t sem; sem_init(&sem, 0, 1); // 初始值1(跨线程共享) sem_wait(&sem); // P操作 // 临界区... sem_post(&sem); // V操作
POSIX具名信号量
-
特点:
-
通过名称标识(如
/mysem
),适用于多进程。 -
需显式创建和删除(
sem_open()
、sem_unlink()
)。
-
-
示例:
sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1); sem_wait(sem); // P操作 // 临界区... sem_post(sem); // V操作 sem_close(sem);
具体例子:
场景:协调两个进程对共享资源的访问。
进程 A(获取信号量):#include <fcntl.h> #include <semaphore.h> #include <stdio.h> #include <unistd.h> int main() { sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1); sem_wait(sem); printf("Process A entered critical section\n"); sleep(2); // 模拟操作 sem_post(sem); sem_close(sem); return 0; }
进程 B(等待信号量):
#include <fcntl.h> #include <semaphore.h> #include <stdio.h> #include <unistd.h> int main() { sem_t *sem = sem_open("/mysem", O_CREAT, 0666, 1); sem_wait(sem); printf("Process B entered critical section\n"); sem_post(sem); sem_close(sem); sem_unlink("/mysem"); // 删除信号量 return 0; }
运行步骤:
编译并运行进程 A:
gcc sem_processA.c -o semA -lpthread && ./semA # 输出:Process A entered critical section
立即运行进程 B(在另一个终端):
gcc sem_processB.c -o semB -lpthread && ./semB # 输出:Process B entered critical section(等待2秒后输出)
对比与选型建议
机制 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
匿名管道 | 父子进程简单通信 | 轻量、简单 | 仅单向、容量有限 |
具名管道 | 任意进程间持久化通信 | 支持多对一 | 需要文件系统路径 |
套接字 | 跨网络或本地进程通信 | 灵活、跨平台 | 协议复杂、开销较大 |
共享内存 | 高性能数据共享 | 零拷贝、速度快 | 需同步机制 |
消息队列 | 按类型处理任务 | 支持消息类型、持久化 | 系统资源占用较多 |
信号量(System-V/POSIX) | 进程/线程同步 | 原子操作、可靠性高 | 配置繁琐 |
总结
-
简单通信:优先选管道或信号。
-
高性能需求:共享内存 + 信号量。
-
跨网络通信:套接字。
-
同步协调:System-V/POSIX信号量。
-
现代开发:优先使用 POSIX 接口(更简洁、跨平台)。