在 Linux 系统中,进程间通信(Inter-Process Communication, IPC)是多个进程之间交换数据和同步操作的机制。Linux 提供了多种 IPC 方式,每种方式适用于不同的场景。以下是常见的 IPC 方式及其详解:
1. 管道(Pipe)
管道是一种半双工的通信方式,数据只能单向流动。通常用于父子进程或兄弟进程之间的通信。
(1)无名管道(Anonymous Pipe)
- 特点:
- 只能在具有亲缘关系的进程之间使用(如父子进程)。
- 通过
pipe()
系统调用创建。 - 数据是字节流,没有消息边界。
- 示例:
#include <unistd.h> #include <stdio.h> int main() { int fd[2]; pipe(fd); // 创建管道 if (fork() == 0) { // 子进程 close(fd[0]); // 关闭读端 write(fd[1], "Hello", 6); // 写入数据 close(fd[1]); } else { // 父进程 close(fd[1]); // 关闭写端 char buf[6]; read(fd[0], buf, 6); // 读取数据 printf("Received: %s\n", buf); close(fd[0]); } return 0; }
(2)命名管道(Named Pipe, FIFO)
- 特点:
- 可以在无关进程之间使用。
- 通过
mkfifo()
创建,是一个特殊的文件。 - 数据是字节流,没有消息边界。
- 示例:
#include <unistd.h> #include <fcntl.h> #include <stdio.h> int main() { mkfifo("my_fifo", 0666); // 创建命名管道 if (fork() == 0) { // 子进程 int fd = open("my_fifo", O_WRONLY); write(fd, "Hello", 6); close(fd); } else { // 父进程 int fd = open("my_fifo", O_RDONLY); char buf[6]; read(fd, buf, 6); printf("Received: %s\n", buf); close(fd); } return 0; }
2. 消息队列(Message Queue)
消息队列是一种全双工的通信方式,允许进程通过消息的形式交换数据。
- 特点:
- 消息是有边界的,发送和接收的是完整的消息。
- 消息队列独立于进程存在,即使进程结束,消息队列仍然存在。
- 通过
msgget()
、msgsnd()
、msgrcv()
等系统调用操作。
- 示例:
#include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <string.h> struct msg_buffer { long msg_type; char msg_text[100]; }; int main() { int msgid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT); // 创建消息队列 struct msg_buffer message; message.msg_type = 1; strcpy(message.msg_text, "Hello"); msgsnd(msgid, &message, sizeof(message), 0); // 发送消息 msgrcv(msgid, &message, sizeof(message), 1, 0); // 接收消息 printf("Received: %s\n", message.msg_text); msgctl(msgid, IPC_RMID, NULL); // 删除消息队列 return 0; }
3. 共享内存(Shared Memory)
共享内存允许多个进程共享同一块内存区域,是最高效的 IPC 方式。
- 特点:
- 数据直接存储在内存中,无需内核介入。
- 需要同步机制(如信号量)来避免竞争条件。
- 通过
shmget()
、shmat()
、shmdt()
等系统调用操作。
- 示例:
#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> int main() { int shmid = shmget(IPC_PRIVATE, 1024, 0666 | IPC_CREAT); // 创建共享内存 char *shmaddr = (char *)shmat(shmid, NULL, 0); // 附加共享内存 if (fork() == 0) { // 子进程 strcpy(shmaddr, "Hello"); shmdt(shmaddr); // 分离共享内存 } else { // 父进程 wait(NULL); // 等待子进程完成 printf("Received: %s\n", shmaddr); shmdt(shmaddr); // 分离共享内存 shmctl(shmid, IPC_RMID, NULL); // 删除共享内存 } return 0; }
4. 信号量(Semaphore)
信号量用于进程间的同步,避免竞争条件。
- 特点:
- 通常与共享内存一起使用。
- 通过
semget()
、semop()
等系统调用操作。
- 示例:
#include <sys/ipc.h> #include <sys/sem.h> #include <stdio.h> int main() { int semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // 创建信号量 semctl(semid, 0, SETVAL, 1); // 初始化信号量值为 1 struct sembuf op = {0, -1, 0}; // P 操作 semop(semid, &op, 1); printf("Critical section\n"); op.sem_op = 1; // V 操作 semop(semid, &op, 1); semctl(semid, 0, IPC_RMID); // 删除信号量 return 0; }
5. 套接字(Socket)
套接字是一种通用的 IPC 方式,可以用于本地或网络通信。
- 特点:
- 支持跨机器的进程通信。
- 通过
socket()
、bind()
、listen()
、accept()
等系统调用操作。
- 示例:
#include <sys/socket.h> #include <stdio.h> #include <string.h> #include <unistd.h> int main() { int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 创建套接字 struct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "my_socket"); bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); // 绑定地址 listen(sockfd, 5); // 监听连接 int clientfd = accept(sockfd, NULL, NULL); // 接受连接 write(clientfd, "Hello", 6); // 发送数据 close(clientfd); close(sockfd); return 0; }
6. 信号(Signal)
信号是一种异步通信机制,用于通知进程发生了某个事件。
- 特点:
- 信号是异步的,进程无法预测何时收到信号。
- 通过
kill()
发送信号,通过signal()
或sigaction()
处理信号。
- 示例:
#include <signal.h> #include <stdio.h> #include <unistd.h> void handler(int sig) { printf("Received signal: %d\n", sig); } int main() { signal(SIGINT, handler); // 注册信号处理函数 while (1) { printf("Running...\n"); sleep(1); } return 0; }
总结
IPC 方式 | 特点 |
---|---|
管道 | 半双工,适用于亲缘进程。 |
消息队列 | 全双工,消息有边界。 |
共享内存 | 高效,需要同步机制。 |
信号量 | 用于同步,避免竞争条件。 |
套接字 | 通用,支持本地和网络通信。 |
信号 | 异步通信,用于事件通知。 |
根据具体需求选择合适的 IPC 方式,可以高效地实现进程间的通信和同步。