文章目录
- 前言
- 一、管道(Pipes)
- 管道示例代码:
- 管道示例流程解读:
- 管道示例运行效果:
- 二、消息队列(Message Queues)
- 消息队列示例代码
- 消息队列示例流程解读:
- 消息队列示例执行效果
- 三、共享内存(Shared Memory)
- 共享内存示例代码
- 共享内存示例流程解读:
- 共享内存示例执行效果
- 四、 信号(Signals)
- 信号示例代码:
- 信号示例流程解读:
- 信号示例执行效果
- 五、套接字(Sockets)
- 套接字示例代码:
- 套接字示例流程解读:
- 套接字示例执行结果
- 六、信号量(Semaphores)
- 信号量示例代码:
- 信号量示例流程解读:
- 信号量示例执行结果
- 七、文件映射(Memory-Mapped Files)
- 文件映射示例代码:
- 文件映射示例流程解读:
- 文件映射示例执行结果
- 总结
前言
线程和进程间的通讯(Inter-Process Communication, IPC)是操作系统中的一个重要概念,用于实现不同进程或同一进程中的不同线程之间的数据交换和协调。
但是这些概念太多了,容易搞混了,所有特地写了这篇博客来记录这些方式的使用实例,建立大家收藏起来,将来需要复习的时候,再来看一看,能够快速的带你复习一遍!
以下是几个常见的进程间通信的方式:管道、消息队列、共享内存、信号、套接字、信号量、文件映射
提示:以下是本篇文章正文内容,下面案例可供参考
一、管道(Pipes)
管道是一种半双工的通信方式,数据只能单向流动。管道分为两种类型:
匿名管道:仅限于具有亲缘关系的进程之间使用,通常用于父子进程之间的通信。
命名管道(FIFO):可以在任意两个进程之间使用,通过文件系统中的一个特殊文件来标识。
管道示例代码:
以下是一个简单的 C 语言示例,演示如何使用管道实现父子进程间的通信。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h> // 用于 memsetint main() {int pipefd[2];pid_t pid;char buf[100];if (pipe(pipefd) == -1) {perror("pipe");return 1;}pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid == 0) { // 子进程close(pipefd[0]); // 关闭读端write(pipefd[1], "Hello from child", 18);close(pipefd[1]);} else { // 父进程close(pipefd[1]); // 关闭写端memset(buf, 0, sizeof(buf)); // 清空缓冲区ssize_t bytes_read = read(pipefd[0], buf, sizeof(buf) - 1); // 读取数据,留一个字节给终止符if (bytes_read > 0) {printf("Received: %s\n", buf);} else {perror("read");}close(pipefd[0]);wait(NULL);}return 0;
}
管道示例流程解读:
1、创建管道:使用 pipe 函数创建一个管道,该函数返回两个文件描述符,分别用于读取和写入。
2、创建子进程:使用 fork 函数创建一个子进程。fork 返回子进程的 PID,如果返回值为 -1 表示出错,0 表示当前是子进程,其他值表示当前是父进程。
3、关闭不必要的管道端:
在父进程中,关闭管道的端(pipefd[1]),因为子进程只读取数据。
在子进程中,关闭管道的读端(pipefd[0]),因为父进程只写入数据。
4、数据传输:
子进程使用 write 函数向管道中写入数据。
父进程使用 read 函数从管道中读取数据。
5、关闭管道:在完成数据传输后,关闭管道的相应端。
6、等待子进程结束:父进程使用 wait 函数等待子进程结束。
管道示例运行效果:
二、消息队列(Message Queues)
消息队列是一种更高级的通信方式,允许进程发送和接收消息。消息队列可以存储多个消息,并且可以设置消息的优先级。
消息队列示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/wait.h>#define MAX_MSG_SIZE 256// 定义消息结构体
struct msg_buffer {long msg_type;char msg_text[MAX_MSG_SIZE];
};int main() {key_t key;int msgid;struct msg_buffer message;pid_t cpid;// 生成一个唯一的键值key = ftok("/etc/passwd", 65); // 使用现有的文件 /etc/passwdif (key == -1) {perror("ftok");exit(EXIT_FAILURE);}// 创建消息队列msgid = msgget(key, 0666 | IPC_CREAT);if (msgid == -1) {perror("msgget");exit(EXIT_FAILURE);}// 创建子进程cpid = fork();if (cpid == -1) { // fork 失败perror("fork");exit(EXIT_FAILURE);} else if (cpid == 0) { // 子进程// 子进程接收消息if (msgrcv(msgid, &message, sizeof(message.msg_text), 1, 0) == -1) {perror("msgrcv");exit(EXIT_FAILURE);}printf("Child process received: %s\n", message.msg_text);// 子进程发送消息message.msg_type = 2;strcpy(message.msg_text, "Hello from child");if (msgsnd(msgid, &message, sizeof(message.msg_text), 0) == -1) {perror("msgsnd");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);} else { // 父进程// 父进程发送消息message.msg_type = 1;strcpy(message.msg_text, "Hello from parent");if (msgsnd(msgid, &message, sizeof(message.msg_text), 0) == -1) {perror("msgsnd");exit(EXIT_FAILURE);}// 父进程接收消息if (msgrcv(msgid, &message, sizeof(message.msg_text), 2, 0) == -1) {perror("msgrcv");exit(EXIT_FAILURE);}printf("Parent process received: %s\n", message.msg_text);// 等待子进程结束wait(NULL);// 删除消息队列if (msgctl(msgid, IPC_RMID, NULL) == -1) {perror("msgctl");exit(EXIT_FAILURE);}}return 0;
}
消息队列示例流程解读:
1、生成唯一键值:使用 ftok 函数生成一个唯一的键值,这个键值用于创建消息队列。这里需要指定一个文件
2、创建消息队列:使用 msgget 函数创建一个消息队列。0666 | IPC_CREAT 表示创建一个新的消息队列,并设置权限。
3、创建子进程:使用 fork 函数创建一个子进程。
4、父进程发送消息:
设置消息类型为 1。
使用 msgsnd 函数将消息发送到消息队列。
5、子进程接收消息:
使用 msgrcv 函数从消息队列中接收类型为 1 的消息。
打印接收到的消息。
设置消息类型为 2。
使用 msgsnd 函数将消息发送回父进程。
6、父进程接收子进程的消息:
使用 msgrcv 函数从消息队列中接收类型为 2 的消息。
打印接收到的消息。
7、删除消息队列:使用 msgctl 函数删除消息队列。
消息队列示例执行效果
三、共享内存(Shared Memory)
共享内存允许多个进程共享同一块内存区域,是最快速的进程间通信方式。但是需要同步机制(如信号量)来防止竞态条件。
共享内存示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/wait.h>#define SHM_SIZE 1024 // 共享内存大小int main() {key_t key;int shmid;char *shm;pid_t cpid;// 生成一个唯一的键值key = ftok("progfile", 65);if (key == -1) {perror("ftok");exit(EXIT_FAILURE);}// 创建共享内存段shmid = shmget(key, SHM_SIZE, 0666 | IPC_CREAT);if (shmid == -1) {perror("shmget");exit(EXIT_FAILURE);}// 将共享内存段连接到进程地址空间shm = shmat(shmid, NULL, 0);if (shm == (char *) -1) {perror("shmat");exit(EXIT_FAILURE);}// 创建子进程cpid = fork();if (cpid == -1) { // fork 失败perror("fork");exit(EXIT_FAILURE);} else if (cpid == 0) { // 子进程// 子进程读取共享内存中的数据printf("Child process reads: %s\n", shm);// 子进程写入共享内存strcpy(shm, "Hello from child");// 子进程断开共享内存if (shmdt(shm) == -1) {perror("shmdt");exit(EXIT_FAILURE);}exit(EXIT_SUCCESS);} else { // 父进程// 父进程写入共享内存strcpy(shm, "Hello from parent");// 父进程等待子进程结束wait(NULL);// 父进程读取共享内存中的数据printf("Parent process reads: %s\n", shm);// 父进程断开共享内存if (shmdt(shm) == -1) {perror("shmdt");exit(EXIT_FAILURE);}// 删除共享内存段if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl");exit(EXIT_FAILURE);}}return 0;
}
共享内存示例流程解读:
1、生成唯一键值:使用 ftok 函数生成一个唯一的键值,这个键值用于创建共享内存段。
2、创建共享内存段:使用 shmget 函数创建一个共享内存段。0666 | IPC_CREAT 表示创建一个新的共享内存段,并设置权限。
3、将共享内存段连接到进程地址空间:使用 shmat 函数将共享内存段连接到进程的地址空间。
4、创建子进程:使用 fork 函数创建一个子进程。
5、父进程写入共享内存:
父进程将消息 “Hello from parent” 写入共享内存。
6、子进程读取和写入共享内存:
子进程读取共享内存中的消息并打印。
子进程将消息 “Hello from child” 写入共享内存。
7、父进程读取子进程写入的消息:
父进程等待子进程结束。
父进程读取共享内存中的消息并打印。
8、断开共享内存:使用 shmdt 函数断开共享内存段。
9、删除共享内存段:使用 shmctl 函数删除共享内存段。
共享内存示例执行效果
四、 信号(Signals)
信号是一种异步通信方式,用于通知进程发生了某些事件。信号可以由操作系统或进程自身发送。
信号示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>// 信号处理函数
void signal_handler(int signum) {if (signum == SIGUSR1) {printf("Received SIGUSR1 signal\n");} else if (signum == SIGUSR2) {printf("Received SIGUSR2 signal\n");}
}int main() {pid_t cpid;struct sigaction sa;// 设置信号处理函数sa.sa_handler = signal_handler;sa.sa_flags = 0;sigemptyset(&sa.sa_mask);if (sigaction(SIGUSR1, &sa, NULL) == -1) {perror("sigaction SIGUSR1");exit(EXIT_FAILURE);}if (sigaction(SIGUSR2, &sa, NULL) == -1) {perror("sigaction SIGUSR2");exit(EXIT_FAILURE);}// 创建子进程cpid = fork();if (cpid == -1) { // fork 失败perror("fork");exit(EXIT_FAILURE);} else if (cpid == 0) { // 子进程// 子进程等待一段时间,让父进程先发送信号sleep(1);// 子进程发送 SIGUSR1 信号给父进程kill(getppid(), SIGUSR1);// 子进程等待一段时间,让父进程处理信号sleep(1);// 子进程发送 SIGUSR2 信号给父进程kill(getppid(), SIGUSR2);exit(EXIT_SUCCESS);} else { // 父进程// 父进程发送 SIGUSR1 信号给子进程kill(cpid, SIGUSR1);// 父进程等待一段时间,让子进程处理信号sleep(1);// 父进程发送 SIGUSR2 信号给子进程kill(cpid, SIGUSR2);// 父进程等待子进程结束wait(NULL);}return 0;
}
信号示例流程解读:
设置信号处理函数:
使用 sigaction 函数设置信号处理函数 signal_handler,处理 SIGUSR1 和 SIGUSR2 信号。
创建子进程:
使用 fork 函数创建一个子进程。
父进程发送信号:
父进程使用 kill 函数发送 SIGUSR1 信号给子进程。
父进程等待一段时间,让子进程处理信号。
父进程再次使用 kill 函数发送 SIGUSR2 信号给子进程。
子进程发送信号:
子进程等待一段时间,让父进程先发送信号。
子进程使用 kill 函数发送 SIGUSR1 信号给父进程。
子进程等待一段时间,让父进程处理信号。
子进程再次使用 kill 函数发送 SIGUSR2 信号给父进程。
父进程等待子进程结束:
使用 wait 函数等待子进程结束。
信号示例执行效果
五、套接字(Sockets)
套接字是一种网络通信方式,允许不同主机上的进程进行通信。套接字可以用于本地进程间通信(通过Unix域套接字)和远程进程间通信(通过Internet域套接字)。
套接字示例代码:
以下是一个使用套接字在父进程和子进程之间进行通信的示例。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h> #define PORT 8080
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket, valread;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};char *hello = "Hello from server";// 创建套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 绑定套接字address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听套接字if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}// 创建子进程pid_t cpid = fork();if (cpid == -1) { // fork 失败perror("fork");close(server_fd);exit(EXIT_FAILURE);} else if (cpid == 0) { // 子进程// 子进程作为客户端struct sockaddr_in serv_addr;int sock = 0;if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket");exit(EXIT_FAILURE);}serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr); // 显式指定服务器 IP 地址// 连接到服务器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("connect");close(sock);exit(EXIT_FAILURE);}// 发送消息send(sock, "Hello from client", strlen("Hello from client"), 0);// 接收消息valread = read(sock, buffer, BUFFER_SIZE);printf("Message received from server: %s\n", buffer);// 关闭套接字close(sock);exit(EXIT_SUCCESS);} else { // 父进程// 父进程作为服务器if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");close(server_fd);exit(EXIT_FAILURE);}// 接收消息valread = read(new_socket, buffer, BUFFER_SIZE);printf("Message received from client: %s\n", buffer);// 发送消息send(new_socket, hello, strlen(hello), 0);// 关闭套接字close(new_socket);close(server_fd);// 等待子进程结束wait(NULL);}return 0;
}
套接字示例流程解读:
1、创建套接字:父进程创建一个 TCP 套接字。
2、绑定套接字:父进程将套接字绑定到本地地址和端口。
3、监听套接字:父进程将套接字设置为监听状态,等待客户端连接。
4、创建子进程:父进程创建一个子进程。
5、子进程作为客户端:
子进程创建一个新的 TCP 套接字。
子进程连接到父进程的服务器。
子进程发送消息 “Hello from client” 到服务器。
子进程接收服务器的消息并打印。
子进程关闭套接字并退出。
6、父进程作为服务器:
父进程接受客户端的连接请求。
父进程接收客户端的消息并打印。
父进程发送消息 “Hello from server” 到客户端。
父进程关闭套接字并等待子进程结束。
套接字示例执行结果
六、信号量(Semaphores)
信号量是一种用于控制对共享资源访问的同步机制。它可以用于进程间的同步和互斥。
信号量示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>#define SEM_NAME "/my_semaphore"
#define SHARED_FILE "shared_file.txt"int main() {sem_t *sem;int fd;pid_t pid;// 创建信号量sem = sem_open(SEM_NAME, O_CREAT, 0644, 1);if (sem == SEM_FAILED) {perror("sem_open");exit(EXIT_FAILURE);}// 创建共享文件fd = open(SHARED_FILE, O_CREAT | O_RDWR, 0666);if (fd == -1) {perror("open");sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}close(fd);// 创建子进程pid = fork();if (pid == -1) {perror("fork");sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);} else if (pid == 0) { // 子进程// 子进程写入共享文件fd = open(SHARED_FILE, O_WRONLY);if (fd == -1) {perror("open");sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}// 等待信号量if (sem_wait(sem) == -1) {perror("sem_wait");close(fd);sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}// 写入消息const char *msg = "Hello from child";write(fd, msg, strlen(msg));printf("Child: Wrote message to shared file\n");// 释放信号量if (sem_post(sem) == -1) {perror("sem_post");close(fd);sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}close(fd);exit(EXIT_SUCCESS);} else { // 父进程// 父进程读取共享文件fd = open(SHARED_FILE, O_RDONLY);if (fd == -1) {perror("open");sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}// 等待信号量if (sem_wait(sem) == -1) {perror("sem_wait");close(fd);sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}// 重置文件偏移量到文件开头lseek(fd, 0, SEEK_SET);// 读取消息char buffer[1024];ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);if (bytes_read == -1) {perror("read");close(fd);sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}buffer[bytes_read] = '\0';printf("Parent: Read message from shared file: %s\n", buffer);// 释放信号量if (sem_post(sem) == -1) {perror("sem_post");close(fd);sem_close(sem);sem_unlink(SEM_NAME);exit(EXIT_FAILURE);}close(fd);// 等待子进程结束wait(NULL);// 关闭信号量sem_close(sem);sem_unlink(SEM_NAME);}return 0;
}
信号量示例流程解读:
1、创建信号量:使用 sem_open 函数创建一个命名信号量 SEM_NAME,初始值为 1。
2、创建共享文件:使用 open 函数创建一个共享文件 SHARED_FILE,如果文件已存在则打开它。
3、创建子进程:使用 fork 函数创建一个子进程。
4、子进程写入共享文件:
子进程打开共享文件,准备写入。
使用 sem_wait 函数等待信号量,确保只有一个进程可以访问共享文件。
写入消息 “Hello from child” 到共享文件。
使用 sem_post 函数释放信号量。
关闭文件并退出子进程。
5、父进程读取共享文件:
父进程打开共享文件,准备读取。
使用 sem_wait 函数等待信号量,确保只有一个进程可以访问共享文件。
重置文件偏移量到文件开头。
读取共享文件中的消息并打印。
使用 sem_post 函数释放信号量。
关闭文件,等待子进程结束,关闭信号量并删除信号量。
信号量示例执行结果
七、文件映射(Memory-Mapped Files)
文件映射允许将文件或文件的一部分映射到内存中,从而允许多个进程共享同一块内存区域。这在处理大文件时特别有用。
文件映射示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>#define SHARED_FILE "shared_file.txt"
#define MMAP_SIZE 1024int main() {int fd;pid_t pid;char *mapped_memory;// 创建共享文件fd = open(SHARED_FILE, O_CREAT | O_RDWR, 0666);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 设置文件大小if (ftruncate(fd, MMAP_SIZE) == -1) {perror("ftruncate");close(fd);exit(EXIT_FAILURE);}// 映射文件到内存mapped_memory = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped_memory == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}// 创建子进程pid = fork();if (pid == -1) {perror("fork");munmap(mapped_memory, MMAP_SIZE);close(fd);exit(EXIT_FAILURE);} else if (pid == 0) { // 子进程// 子进程写入共享内存const char *msg = "Hello from child";strcpy(mapped_memory, msg);printf("Child: Wrote message to shared memory: %s\n", mapped_memory);// 等待一段时间,让父进程有时间读取sleep(1);exit(EXIT_SUCCESS);} else { // 父进程// 父进程读取共享内存sleep(1); // 等待子进程写入printf("Parent: Read message from shared memory: %s\n", mapped_memory);// 等待子进程结束wait(NULL);// 取消内存映射if (munmap(mapped_memory, MMAP_SIZE) == -1) {perror("munmap");close(fd);exit(EXIT_FAILURE);}// 关闭文件close(fd);// 删除共享文件unlink(SHARED_FILE);}return 0;
}
文件映射示例流程解读:
1、创建共享文件:使用 open 和 ftruncate 函数创建并设置共享文件的大小。
2、映射文件到内存:使用 mmap 函数将文件映射到内存区域。
3、创建子进程:使用 fork 函数创建一个子进程。
4、子进程写入共享内存:子进程将消息写入共享内存区域,并打印写入的消息。
5、父进程读取共享内存:父进程读取共享内存区域中的消息并打印,等待子进程结束,取消内存映射,关闭文件并删除共享文件。
文件映射示例执行结果
总结
管道:适用于父子进程间的简单通信。
消息队列:适用于需要发送和接收消息的场景。
共享内存:适用于需要高效共享大量数据的场景。
信号:适用于异步事件的通知。
套接字:适用于网络通信和本地进程间通信。
信号量:适用于进程间的同步和互斥。
文件映射:适用于处理大文件和共享内存。