进程间通信的概念
进程间通信(Inter-Process Communication, IPC)是计算机系统中不同进程之间传递数据或同步行为的机制。
一、什么是进程间通信?
- 定义:进程是操作系统分配资源(如内存、CPU)的基本单位,每个进程拥有独立的内存空间。IPC 是让这些独立的进程共享数据、协调任务的技术。
- 关键点:进程之间无法直接读写对方的内存,必须通过操作系统提供的机制使不同的进程“看到”同一份资源来实现通信。
管道
管道(Pipe)是进程间通信的一种基本方式,主要用于单向数据传递。它通过内核管理的缓冲区连接两个进程,允许一个进程的输出直接作为另一个进程的输入。
管道的本质:
- 内核中的缓冲区:管道是内核空间中维护的一个固定大小的环形缓冲区(通常为4KB~64KB),作为数据中转站。
- 单向流动:数据从一端流入,另一端流出,方向不可逆,想要双向通信,需要建立两个管道。
- 无格式字节流:管道不区分数据类型,传递的是原始的字节序列。
匿名管道
匿名管道的特点:
- 只能用于有亲缘关系的进程。
- 生命周期与进程绑定,最后一个使用它的进程终止后自动销毁。
- 父子进程是会进程协同的,一般不需要额外的同步与互斥(部分多生产者场景 或 保证消息边界 等可能需要)
【注】:有亲缘关系的进程:父子进程、兄弟进程、爷孙进程等等,都是具有亲缘关系的进程。
pipe(): 创建匿名管道。
#include <unistd.h>
int pipe(int pipefd[2]);
参数:
pipefd[2]是个包含两个文件描述符的数组
pipefd[0]:管道的读端(read end)
pipefd[1]:管道的写端(write end)
返回值:成功返回 0,失败返回 -1 并设置 errno
示例:
int pipefd[N] = {0}; //创建接收pipe()返回值的数组
int n = pipe(pipefd); //调用pipe()
if (n < 0)
return 1;
pid_t id = fork(); //创建子进程
if (id < 0)
return 2;
if (id == 0)
{
//子进程是写端,所以关闭读端,避免资源占用
close(pipefd[0]);
//自定义的写入函数
Writer(pipefd[1]);
//写入完成后关闭写端
close(pipefd[1]);
exit(0);
}
//父进程是读端,所以关闭写端
close(pipefd[1]);
//自定义的读取函数
Reader(pipefd[0]);
//回收子进程
pid_t rid = waitpid(id, nullptr, 0);
if(rid < 0) return 3;
//读取完成关闭读端,避免资源泄露
close(pipefd[0]);
管道读写的4种情况:
- 读写端正常,管道如果为空,读端要阻塞;
- 读写端正常,管道如果被写满,写端就会阻塞;
- 读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞;
- 写端正常写,读端关闭,操作系统会kill(通过信号)正要写入的进程。
命名管道
命名管道的特点:
- 允许无亲缘关系的的进程通信,只要知道管道名。
- 通过mkfifo命令或mkfifo()系统调用创建,在文件系统中表现为一个命名文件(如/tmp/myfifo)。
- 生命周期独立于进程,需手动删除文件。
命名管道是通过使用“路径+文件名”的方式,使不同的进程“看到”同一份文件资源,进而实现进程间通信。
命令行操作:
mkfifo /tmp/myfifo #创建命名管道
echo "Hello" > /tmp/myfifo #进程A写入数据
cat < /tmp/myfifo #进程B读取数据
mkfifo(): 创建命名管道。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname:FIFO 文件路径(如 /tmp/myfifo)
mode:权限模式(受 umask 影响,最终权限为 mode & ~umask)
返回值:成功返回0;失败返回-1,并设置 errno
示例:
写入端程序 (writer.c):
#define FIFO_PATH "/tmp/my_fifo" // FIFO 文件路径
int main() {
int fd;
char message[] = "Hello from writer process!";
//为了简洁,除了第一个函数是否调用成功的检查,其余检查都删去了
// 1. 创建 FIFO(忽略已存在的错误)
if (mkfifo(FIFO_PATH, 0666) == -1 && errno != EEXIST) {
perror("mkfifo failed");
exit(EXIT_FAILURE);
}
// 2. 以写模式打开 FIFO(阻塞直到读端打开)
fd = open(FIFO_PATH, O_WRONLY);
// 3. 写入数据
write(fd, message, strlen(message) + 1);
// 4. 清理
close(fd);
return 0;
}
读取端程序 (reader.c):
#define FIFO_PATH "/tmp/my_fifo" // FIFO 文件路径
int main() {
int fd;
char buffer[256];
// 1. 以读模式打开 FIFO(阻塞直到写端打开)
fd = open(FIFO_PATH, O_RDONLY);
// 2. 读取数据
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
// 3. 输出结果
buffer[bytes_read] = '\0'; // 确保字符串终止
printf("Reader: 收到消息 -> %s\n", buffer);
// 4. 清理
close(fd);
return 0;
}
共享内存
Linux 的共享内存(Shared Memory)允许多个进程直接访问同一块物理内存区域,避免了数据拷贝的开销。
共享内存的关键 是让多个进程的页表指向同一块物理内存页,这些进程的地址空间的共享区都有这块内存的映射。
System V 共享内存
shmget(): 用于创建或访问共享内存段的一个系统调用。
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这是一个键值,用于标识共享内存段。
size:指定共享内存段的大小(以字节为单位)。
shmflg:是一组标志位,用于控制共享内存段的创建和访问权限。
·IPC_CREAT:如果指定的共享内存段不存在,则创建它。
·IPC_EXCL:与 IPC_CREAT 一起使用时,如果共享内存段已经存在,则调用失败。
·权限标志(如 0666),这些标志用于设置共享内存段的访问权限(类似于文件权限)。
返回值:成功时,shmget 返回一个共享内存标识符(shmid)。
失败时,返回 -1 并设置 errno 以指示错误原因。
ftok(): 用于生成一个唯一的键值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
shmat(): 用于将共享内存段连接到进程地址空间。
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid:共享内存段的标识符,由 shmget 返回。
shmaddr:指定希望连接共享内存段的地址。如果设置为 NULL,系统将自动选择一个地址。
shmflg:标志位,用于控制附加操作的行为。
返回值:成功时,返回指向共享内存段的指针。
失败时,返回 (void *)-1,并设置 errno 以指示错误原因。
shmdt(): 用于将之前通过 shmat() 连接到进程地址空间的共享内存段脱离出来。
#include <sys/shm.h>
int shmdt(const void *shm_addr);
参数:
shm_addr:指向通过 shmat 连接到进程地址空间的共享内存段的指针。
返回值:成功时返回0;失败时返回-1,并设置 errno 以指示错误原因。
shmctl(): 用于控制共享内存段。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数cmd的不同取值可以让 shmctl() 获取共享内存的状态、设置共享内存段
的某些属性以及删除共享内存段。
实例:
写入端程序 (writer.c)
//为简洁去掉了 头文件 和 部分错误检查
#define SHM_SIZE 1024 // 共享内存大小
int main() {
// 1. 使用 ftok() 生成唯一键值
key_t key = ftok("testfile", 'A');
// 2. 创建共享内存段
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
// 3. 将共享内存附加到当前进程地址空间
char *shm_ptr = (char *)shmat(shmid, NULL, 0);
// 4. 写入数据到共享内存
strcpy(shm_ptr, "Hello from Writer Process!");
printf("Data written to shared memory: %s\n", shm_ptr);
// 5. 等待用户输入,防止立即删除共享内存
printf("Press Enter to delete shared memory...");
getchar();
// 6. 分离共享内存
if (shmdt(shm_ptr) == -1) {
perror("shmdt");
exit(1);
}
// 7. 删除共享内存段
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
perror("shmctl");
exit(1);
}
return 0;
}
读取端程序 (reader.c)
#define SHM_SIZE 1024
int main() {
// 1. 使用 ftok() 生成与写入端相同的键值
key_t key = ftok("testfile", 'A');
// 2. 获取已存在的共享内存段
int shmid = shmget(key, SHM_SIZE, 0666);
// 3. 将共享内存附加到当前进程地址空间(只读模式)
char *shm_ptr = (char *)shmat(shmid, NULL, SHM_RDONLY);
// 4. 读取共享内存中的数据
printf("Data read from shared memory: %s\n", shm_ptr);
// 5. 分离共享内存
if (shmdt(shm_ptr) == -1) {
perror("shmdt");
exit(1);
}
return 0;
}
POSIX 共享内存
与文件描述符fd体系统一起来了。
shm_open(): Linux 中用于创建或打开 POSIX 共享内存对象的函数。
ftruncate(): 设置共享内存对象大小。
mmap(): 将共享内存映射到进程地址空间。
munmap(): 解除内存映射。
shm_unlink(): 删除共享内存对象。
close(): 关闭共享内存文件描述符。
要用的时候再查查具体用法 Õ_Õ
实例:
写入端程序 (posix_writer.c)
#define SHM_NAME "/my_shm" // 共享内存对象名称(唯一标识)
#define SHM_SIZE 1024 // 共享内存大小
int main() {
// 1. 创建或打开共享内存对象(可读可写,若不存在则创建)
int shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
// 2. 设置共享内存对象大小
if (ftruncate(shm_fd, SHM_SIZE) == -1) {
perror("ftruncate");
exit(1);
}
// 3. 将共享内存映射到进程地址空间(读写权限)
char *shm_ptr = (char *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// 4. 写入数据到共享内存
strcpy(shm_ptr, "Hello from POSIX Writer Process!");
printf("Data written to shared memory: %s\n", shm_ptr);
// 5. 等待用户输入,防止立即删除共享内存
printf("Press Enter to delete shared memory...");
getchar();
// 6. 解除内存映射
if (munmap(shm_ptr, SHM_SIZE) == -1) {
perror("munmap");
exit(1);
}
// 7. 关闭文件描述符
close(shm_fd);
// 8. 删除共享内存对象(标记删除,实际在所有进程解除映射后销毁)
if (shm_unlink(SHM_NAME) == -1) {
perror("shm_unlink");
exit(1);
}
return 0;
}
读取端程序 (posix_reader.c)
#define SHM_NAME "/my_shm"
#define SHM_SIZE 1024
int main() {
// 1. 打开已存在的共享内存对象(只读模式)
int shm_fd = shm_open(SHM_NAME, O_RDONLY, 0);
// 2. 将共享内存映射到进程地址空间(只读权限)
char *shm_ptr = (char *)mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
// 3. 读取共享内存中的数据
printf("Data read from shared memory: %s\n", shm_ptr);
// 4. 解除内存映射
if (munmap(shm_ptr, SHM_SIZE) == -1) {
perror("munmap");
exit(1);
}
// 5. 关闭文件描述符
close(shm_fd);
return 0;
}