进程间通信

进程间通信的概念

进程间通信(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遥逖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值