进程间通信(IPC)概述
进程间通信(Inter-Process Communication, IPC)是Linux系统中多个进程共享数据或同步操作的机制。Linux提供了多种IPC方式,包括管道、消息队列、共享内存、信号量、套接字等。每种方式适用于不同场景,需根据需求选择。
管道(Pipe)
管道是半双工的通信方式,数据单向流动,通常用于父子进程或兄弟进程间的通信。
匿名管道
通过pipe()
系统调用创建,返回两个文件描述符:fd[0]
(读端)和fd[1]
(写端)。
代码示例
#include <unistd.h>
#include <stdio.h>
int main() {
int fd[2];
pid_t pid;
char buf[256];
if (pipe(fd) == -1) {
perror("pipe");
return 1;
}
pid = fork();
if (pid == 0) { // 子进程
close(fd[1]); // 关闭写端
read(fd[0], buf, sizeof(buf));
printf("Child received: %s\n", buf);
close(fd[0]);
} else { // 父进程
close(fd[0]); // 关闭读端
write(fd[1], "Hello from parent", 18);
close(fd[1]);
}
return 0;
}
命名管道(FIFO)
通过mkfifo
命令或mkfifo()
函数创建,允许无亲缘关系的进程通信。
代码示例
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
mkfifo("/tmp/myfifo", 0666);
int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "Hello FIFO", 11);
close(fd);
return 0;
}
消息队列(Message Queue)
消息队列是内核维护的链表,进程通过唯一标识符(key
)访问队列,支持不同类型消息。
常用函数
msgget()
:创建或获取队列。msgsnd()
:发送消息。msgrcv()
:接收消息。msgctl()
:控制队列(如删除)。
代码示例
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
struct msg_buffer {
long msg_type;
char msg_text[100];
};
int main() {
key_t key = ftok("progfile", 65);
int msgid = msgget(key, 0666 | IPC_CREAT);
struct msg_buffer message;
message.msg_type = 1;
strcpy(message.msg_text, "Hello Message Queue");
msgsnd(msgid, &message, sizeof(message), 0);
printf("Sent: %s\n", message.msg_text);
msgrcv(msgid, &message, sizeof(message), 1, 0);
printf("Received: %s\n", message.msg_text);
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
共享内存(Shared Memory)
共享内存是最快的IPC方式,允许多个进程访问同一块内存区域。
常用函数
shmget()
:创建或获取共享内存段。shmat()
:附加到进程地址空间。shmdt()
:分离共享内存。shmctl()
:控制共享内存(如删除)。
代码示例
#include <sys/shm.h>
#include <stdio.h>
#include <string.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("Write data: ");
fgets(str, 1024, stdin);
printf("Data written: %s\n", str);
shmdt(str);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
信号量(Semaphore)
信号量用于同步进程对共享资源的访问,避免竞态条件。
常用函数
semget()
:创建或获取信号量集。semop()
:执行原子操作(如P/V操作)。semctl()
:控制信号量(如初始化或删除)。
代码示例
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void p(int semid) {
struct sembuf op = {0, -1, SEM_UNDO};
semop(semid, &op, 1);
}
void v(int semid) {
struct sembuf op = {0, 1, SEM_UNDO};
semop(semid, &op, 1);
}
int main() {
key_t key = ftok("semfile", 65);
int semid = semget(key, 1, 0666 | IPC_CREAT);
union semun arg;
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
pid_t pid = fork();
if (pid == 0) {
p(semid);
printf("Child enters critical section\n");
sleep(2);
printf("Child leaves critical section\n");
v(semid);
} else {
p(semid);
printf("Parent enters critical section\n");
sleep(2);
printf("Parent leaves critical section\n");
v(semid);
}
semctl(semid, 0, IPC_RMID);
return 0;
}
套接字(Socket)
套接字支持跨网络通信,也可用于本地进程间通信(AF_UNIX域)。
本地套接字示例
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/socket");
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
int client = accept(sockfd, NULL, NULL);
char buf[256];
read(client, buf, sizeof(buf));
printf("Server received: %s\n", buf);
write(client, "Hello from server", 17);
close(client);
close(sockfd);
unlink("/tmp/socket");
return 0;
}
信号(Signal)
信号是异步通知机制,用于进程间简单事件通知(如终止、中断)。
常用函数
kill()
:发送信号。signal()
:注册信号处理函数。
代码示例
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) {
printf("Received signal: %d\n", sig);
}
int main() {
signal(SIGINT, handler);
printf("Press Ctrl+C to trigger signal\n");
pause(); // 等待信号
return 0;
}
对比与选择建议
IPC方式 | 适用场景 | 特点 |
---|---|---|
管道 | 父子进程简单通信 | 单向,容量有限 |
消息队列 | 无亲缘关系进程结构化通信 | 支持消息类型,内核持久化 |
共享内存 | 高频大数据量通信 | 无需拷贝,需同步机制 |
信号量 | 资源访问同步 | 计数器,避免竞态 |
套接字 | 跨网络或复杂通信 | 灵活,支持多种协议 |
信号 | 异步事件通知 | 简单,不可靠 |
根据需求选择:
- 高性能:共享内存 + 信号量。
- 结构化数据:消息队列。
- 简单通知:信号或管道。
- 跨机器通信:套接字。
注意事项
- 同步问题:共享内存和消息队列需配合信号量避免竞态。
- 资源释放:显式删除IPC对象(如
msgctl
、shmctl
)。 - 权限控制:设置正确的访问权限(如
0666
)。 - 错误处理:检查系统调用返回值(如
-1
表示失败)。