一文读懂 Linux 上的进程间通信(IPC)方式
2023-11-11 18:54:35
Linux IPC 方式详解:跨越进程间通信的鸿沟
在现代计算环境中,应用程序之间的通信至关重要,使它们能够共享数据、同步动作并有效协作。在 Linux 操作系统中,进程间通信 (IPC) 提供了多种机制,允许不同进程在系统范围内进行交互。本文将深入探讨 Linux 中最常见的 IPC 方式,提供示例代码并阐述它们的优点和缺点。
1. 管道:单向数据传输
管道是一种轻量级的 IPC 方式,允许两个进程之间进行单向通信。它提供了一个匿名管道或一个有名管道。匿名管道仅在创建它的进程和它的子进程之间可见,而有名管道可以在任何进程之间使用。通过管道,一个进程可以写入数据,而另一个进程可以读取它,从而实现单向数据流。
优点:
- 简单易用
- 高效的单向通信
- 适合父进程与子进程之间的通信
缺点:
- 只支持单向通信
- 匿名管道只能在相关进程之间使用
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
close(pipefd[0]); // 关闭读端
write(pipefd[1], "Hello, world!", 13); // 向写端写入数据
close(pipefd[1]); // 关闭写端
} else { // 父进程
close(pipefd[1]); // 关闭写端
char buf[13];
read(pipefd[0], buf, 13); // 从读端读取数据
close(pipefd[0]); // 关闭读端
printf("Received: %s\n", buf);
}
return 0;
}
2. 消息队列:消息传递
消息队列是一种更灵活的 IPC 方式,允许进程在队列中发送和接收消息。消息队列可以是本地队列或远程队列。本地队列限制于同一台计算机上的进程,而远程队列允许不同计算机上的进程进行通信。通过消息队列,进程可以发送和接收可变长度的数据块,从而实现双向异步通信。
优点:
- 双向异步通信
- 支持多个进程之间的通信
- 可靠的消息传递,确保消息不会丢失
缺点:
- 比管道开销更大
- 队列大小有限制
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
int main() {
key_t key = ftok(".", 'a');
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid == -1) {
perror("msgget");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
struct msgbuf {
long mtype;
char mtext[100];
} msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello, world!");
if (msgsnd(msgid, &msg, sizeof(msg.mtext), 0) == -1) {
perror("msgsnd");
exit(EXIT_FAILURE);
}
} else { // 父进程
struct msgbuf {
long mtype;
char mtext[100];
} msg;
if (msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0) == -1) {
perror("msgrcv");
exit(EXIT_FAILURE);
}
printf("Received: %s\n", msg.mtext);
}
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
3. 共享内存:共享数据段
共享内存是一种高级 IPC 方式,允许多个进程访问同一块物理内存。它提供了一个匿名共享内存段或一个命名共享内存段。匿名共享内存段只能在创建它的进程和它的子进程之间共享,而命名共享内存段可以在任何进程之间共享。通过共享内存,进程可以高效地读取和写入共享数据,而无需复制数据。
优点:
- 高效的数据共享
- 允许多个进程同时访问数据
- 适用于需要频繁数据交换的情况
缺点:
- 同步访问共享内存的复杂性
- 必须手动管理内存分配和释放
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
int main() {
key_t key = ftok(".", 'a');
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
int shmid = shmget(key, 1024, IPC_CREAT | IPC_EXCL | 0666);
if (shmid == -1) {
perror("shmget");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
char *shm = shmat(shmid, NULL, 0);
if (shm == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
strcpy(shm, "Hello, world!");
shmdt(shm);
} else { // 父进程
char *shm = shmat(shmid, NULL, 0);
if (shm == (char *)-1) {
perror("shmat");
exit(EXIT_FAILURE);
}
printf("Received: %s\n", shm);
shmdt(shm);
}
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
4. 信号量:同步进程访问
信号量是一种 IPC 方式,用于同步对共享资源的访问。它提供了一个二值信号量或一个计数信号量。二值信号量只有两个状态:0 和 1,用于防止多个进程同时访问共享资源。计数信号量可以取任意非负整数的值,用于限制同时可以访问共享资源的进程数。通过信号量,进程可以协调对共享资源的访问,避免竞争条件。
优点:
- 防止对共享资源的竞争条件
- 适用于需要同步进程访问的情况
- 灵活的信号量类型(二值和计数)
缺点:
- 比其他 IPC 方式开销更大
- 需要小心处理死锁情况
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
int main() {
key_t key = ftok(".", 'a');
if (key == -1) {
perror("ftok");
exit(EXIT_FAILURE);
}
int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if (semid == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) { // 子进程
struct sembuf semop;
semop.sem_num = 0;
semop.sem_op = -1;
semop.sem_flg = 0;
if (semop(semid, &semop, 1) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
printf("Child process entered critical section.\n");
sleep(10);
semop.sem_op = 1;
if (semop(semid, &semop, 1) == -1) {
perror("semop");