进程通信
进程间通信是指 两个或多个进程之间交换数据或同步操作的机制。由于每个进程有独立的地址空间,不能直接访问彼此的内存,所以需要通过操作系统提供的机制进行通信。
以下场景就需要进程通信:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样资源
通知事件:一个进程向其他进程发送消息,告知发生了某个事件
主要通信机制分类:
管道、System V
管道
一个进程连接到另一个进程的数据流称为一个 "管道",也就是一个命令的标准输出直接作为下一个命令的标准输入,无需中间文件。
bash
# 查看进程并筛选
ps aux | grep python
# 对文本去重、计数并取前 10 行。
cat data.txt | sort | uniq -c | head -n 10
匿名管道
没有名字,不能通过文件系统路径访问,数据只能从一端写入,另一端读出。
bash
int pipe(int fd[2]);
fd: 文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
数据流向:fd[1] → 内核缓冲区 → 从 fd[0] 读出
案例,父进程向子进程发送一条消息:
bash
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
int fd[2];
pid_t pid;
char buffer[128];
// 1.创建匿名管道
if (pipe(fd) == -1)
{
perror("pipe");
return 1;
}
// 2.创建子进程
pid = fork();
if (pid == -1)
{
perror("fork");
return 1;
}
if (pid == 0)
{
// 子进程从管道读取
close(fd[1]); // 关闭写端, 避免浪费
read(fd[0], buffer, sizeof(buffer));
printf("child received: %s\n", buffer);
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;
}
只要还有进程持有写端fd[1],read() 就不会返回0(EOF)。只有当所有写端都关闭后,read才会返回0表示管道关闭。
命名管道
用于任意两个进程之间(不要求有亲缘关系)进行单向通信,使用 mkfifo 命令。
cpp
int mkfifo(const char *filename,mode_t mode);
命名管道的行为类似普通文件,但有关键区别:
打开即阻塞:默认情况下,
若只有读端打开(open(..., O_RDONLY)),会阻塞,直到有写端打开。
若只有写端打开(open(..., O_WRONLY)),也会阻塞,直到有读端打开。
案例,两个独立程序通过 FIFO 通信:
写端程序 client.cpp:
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 以只写方式打开名为"mypipe"的命名管道(FIFO文件)
// 如果管道不存在或没有读取端,open()会阻塞
int wfd = open("mypipe", O_WRONLY);
// 检查管道是否成功打开
if(wfd < 0)
{
perror("open");
}
char buf[1024]; // 定义缓冲区,用于存储用户输入
while(1)
{
buf[0] = 0;
printf("please enter-> ");
fflush(stdout); // 强制刷新输出缓冲区,确保提示信息立即显示
// 从标准输入(键盘)读取用户输入,最多读取sizeof(buf)-1个字符
// 保留一个位置用于字符串结束符'\0'
ssize_t s = read(0, buf, sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0; // 在读取的数据末尾添加字符串结束符
write(wfd, buf, strlen(buf)); // 将输入写入管道
}else if(s <= 0)
{
perror("read");
}
}
close(wfd); // 关闭管道文件描述符
return 0;
}
读端程序server.cpp:
cpp
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h> // 包含此头文件以使用mkfifo函数
#include <unistd.h>
#include <string.h>
int main()
{
// 创建命名管道(FIFO文件)
// 参数1: "mypipe" - 管道文件名
// 参数2: 0644 - 管道文件权限(rw-r--r--)
// mkfifo函数成功返回0,失败返回-1
if(mkfifo("mypipe", 0644) < 0)
{
perror("mkfifo");
}
// 以只读方式打开命名管道, 如果没有任何写入端打开管道,open()会阻塞直到有写入端连接
int rfd = open("mypipe", O_RDONLY);
if(rfd < 0)
{
perror("open");
}
char buf[1024]; // 缓冲区,用于存储从管道读取的数据
while (1)
{
buf[0] = 0;
printf("please wait...\n");
// 从管道读取数据, 如果管道中没有数据且写入端未关闭,read()会阻塞等待
ssize_t s = read(rfd, buf, sizeof(buf)-1);
if(s > 0)
{
buf[s-1] = 0;
printf("client say# %s\n", buf);
}else if(s == 0)
{
printf("client quit!!!\n");
break;
}else{
perror("read");
}
}
close(rfd);
return 0;
}
system V共享内存
共享内存是最快的进程间通信形式,允许多个不相关的进程访问同一块物理内存区域,从而实现高效的数据共享。进程不再通过执行进入内核的系统调用来传递彼此的数据。
cpp
int shmget(ket_t key, size_t size, int shmflg);
功能:用来创建共享内存
key:共享内存段名字
size:共享内存大小
shmflg:权限位:如 0666(读写权限)创建标志:
IPC_CREAT:若不存在则创建
IPC_EXCL:与 IPC_CREAT 同用,若已存在则失败(类似 O_EXCL)
返回值:成功返回共享内存表示符,失败返回-1
cpp
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将共享内存段连接到进程空间地址空间
shmid:共享内存标识符
shmaddr:指定连接的地址
shmflg:标志位,0为可读可写,SHM_RDONLY:只读,SHM_RND:若 shmaddr 非 NULL,将其向下舍入到页边界
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
cpp
int shmdt(const void *shmaddr);
功能:将共享内存段与当前进程分离离(只是取消映射,不是删除共享内存段)
shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1
cpp
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:用于控制共享内存
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作:
IPC_STAT:获取段信息到 buf
IPC_SET:用 buf 设置权限/uid/gid
IPC_RMID:立即标记删除(实际删除在引用计数归零后
buf:指向一个保存共享内存的模式状态和访问权限的数据结构
返回值:成功返回0,失败返回-1
案例:创建两个程序:
写入端(server):创建共享内存并写入数据
读取端(client):读取共享内存中的数据
公共头文件 shm_chat.h:
cpp
#ifndef SHM_CHAT_H
#define SHM_CHAT_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
// 定义共享内存大小
#define SHM_SIZE 1024
// 定义共享内存的键值(任意唯一值)
#define SHM_KEY 0x1234
// 定义共享内存结构
typedef struct {
int written; // 0: 未写入新数据, 1: 已写入新数据
char text[SHM_SIZE - sizeof(int)]; // 实际存储的数据
} ShmData;
// 函数声明
int create_shm(int key, int size, int flags);
void* attach_shm(int shmid);
int detach_shm(const void* shmaddr);
int destroy_shm(int shmid);
#endif
公共函数实现 shm_caht.c
cpp
#include "shm_chat.h"
// 创建或获取共享内存段
int create_shm(int key, int size, int flags) {
int shmid;
// 使用shmget创建或获取共享内存
// key: 共享内存的键值,不同进程使用相同的key访问同一块共享内存
// size: 共享内存大小
// flags: 权限标志,如 IPC_CREAT | 0666 表示创建并设置权限
shmid = shmget(key, size, flags);
if (shmid == -1) {
perror("shmget failed");
exit(1);
}
printf("Shared memory created/retrieved, ID: %d\n", shmid);
return shmid;
}
// 将共享内存附加到进程地址空间
void* attach_shm(int shmid) {
void *shmaddr;
// 使用shmat将共享内存附加到当前进程
// shmid: 共享内存ID
// NULL: 让系统选择附加地址
// 0: 默认标志,可读写
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (void*)-1) {
perror("shmat failed");
exit(1);
}
printf("Shared memory attached at address: %p\n", shmaddr);
return shmaddr;
}
// 从进程地址空间分离共享内存
int detach_shm(const void* shmaddr) {
int ret;
// 使用shmdt分离共享内存
// shmaddr: 共享内存的附加地址
ret = shmdt(shmaddr);
if (ret == -1) {
perror("shmdt failed");
return -1;
}
printf("Shared memory detached\n");
return 0;
}
// 销毁共享内存段
int destroy_shm(int shmid) {
int ret;
// 使用shmctl控制共享内存
// shmid: 共享内存ID
// IPC_RMID: 命令,表示删除共享内存
// NULL: 不需要缓冲区
ret = shmctl(shmid, IPC_RMID, NULL);
if (ret == -1) {
perror("shmctl failed");
return -1;
}
printf("Shared memory destroyed\n");
return 0;
}
写入端程序 shm_write.c
cpp
#include "shm_chat.h"
int main() {
int shmid;
ShmData *shm_data;
char buffer[SHM_SIZE - sizeof(int)];
// 1. 创建共享内存(如果已存在则获取)
// IPC_CREAT: 如果不存在则创建
// 0666: 权限,用户、组、其他都可读写
shmid = create_shm(SHM_KEY, sizeof(ShmData), IPC_CREAT | 0666);
// 2. 附加共享内存到进程地址空间
shm_data = (ShmData*)attach_shm(shmid);
// 初始化共享内存数据
shm_data->written = 0;
memset(shm_data->text, 0, sizeof(shm_data->text));
printf("=== 共享内存聊天程序(写入端)===\n");
printf("输入 'exit' 退出程序\n\n");
while (1) {
// 等待读取端读取上次的数据
while (shm_data->written == 1) {
printf("等待对方读取...\n");
sleep(1);
}
// 获取用户输入
printf("请输入消息: ");
fflush(stdout);
if (fgets(buffer, sizeof(buffer), stdin) == NULL) {
break; // 读取失败或EOF
}
// 去掉换行符
buffer[strcspn(buffer, "\n")] = '\0';
// 检查是否退出
if (strcmp(buffer, "exit") == 0) {
// 通知读取端退出
strcpy(shm_data->text, "EXIT_SIGNAL");
shm_data->written = 1;
break;
}
// 3. 写入数据到共享内存
strncpy(shm_data->text, buffer, sizeof(shm_data->text) - 1);
shm_data->text[sizeof(shm_data->text) - 1] = '\0'; // 确保字符串结束
// 4. 设置标志,表示有新数据
shm_data->written = 1;
printf("消息已发送\n");
}
// 5. 分离共享内存
detach_shm(shm_data);
printf("写入端程序退出\n");
return 0;
}
读取端程序 shm_read.c
cpp
#include "shm_chat.h"
int main() {
int shmid;
ShmData *shm_data;
// 1. 获取已存在的共享内存(不创建新的)
shmid = create_shm(SHM_KEY, 0, 0666);
// 2. 附加共享内存到进程地址空间
shm_data = (ShmData*)attach_shm(shmid);
printf("=== 共享内存聊天程序(读取端)===\n");
printf("等待对方发送消息...\n\n");
while (1) {
// 等待新数据
while (shm_data->written == 0) {
sleep(1); // 休眠等待,避免忙等待消耗CPU
}
// 3. 检查退出信号
if (strcmp(shm_data->text, "EXIT_SIGNAL") == 0) {
printf("对方已退出\n");
break;
}
// 4. 读取并显示数据
printf("收到消息: %s\n", shm_data->text);
// 5. 重置标志,表示数据已读取
shm_data->written = 0;
printf("等待下一条消息...\n");
}
// 6. 分离共享内存
detach_shm(shm_data);
printf("读取端程序退出\n");
return 0;
}