当前位置: 首页 > news >正文

进程间通信之-----零拷贝

1 零拷贝的原理

零拷贝原理
注意: 零拷贝的申请内存,如果之前已经使用,必须要手动释放,代码示例有讲解。

2 代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>// 零拷贝文件结构体,用于管理资源
typedef struct {int fd;             // 文件描述符void* mapped_data;  // 映射的内存地址off_t file_size;    // 文件大小
} ZeroCopyFile;// 初始化零拷贝文件(打开并映射文件)
int zero_copy_file_init(ZeroCopyFile* zfile, const char* filename) {// 初始化结构体memset(zfile, 0, sizeof(ZeroCopyFile));zfile->fd = -1;zfile->mapped_data = NULL;zfile->file_size = 0;// 1. 打开文件(只读模式)zfile->fd = open(filename, O_RDONLY);if (zfile->fd == -1) {fprintf(stderr, "open failed: %s\n", strerror(errno));return -1;}// 2. 获取文件大小struct stat file_stat;if (fstat(zfile->fd, &file_stat) == -1) {fprintf(stderr, "fstat failed: %s\n", strerror(errno));close(zfile->fd);  // 失败时释放已打开的文件描述符zfile->fd = -1;return -1;}zfile->file_size = file_stat.st_size;// 3. 映射文件到内存(零拷贝核心操作)zfile->mapped_data = mmap(NULL,               // 由系统自动分配映射地址zfile->file_size,   // 映射大小(文件大小)PROT_READ,          // 只读权限MAP_PRIVATE,        // 私有映射(修改不反映到文件)zfile->fd,          // 关联的文件描述符0                   // 映射偏移量(从文件开头));if (zfile->mapped_data == MAP_FAILED) {fprintf(stderr, "mmap failed: %s\n", strerror(errno));close(zfile->fd);  // 失败时释放资源zfile->fd = -1;zfile->file_size = 0;return -1;}return 0;  // 初始化成功
}// 释放零拷贝文件资源
void zero_copy_file_destroy(ZeroCopyFile* zfile) {// 1. 解除内存映射(必须先于关闭文件描述符)if (zfile->mapped_data != NULL && zfile->mapped_data != MAP_FAILED) {if (munmap(zfile->mapped_data, zfile->file_size) == -1) {fprintf(stderr, "munmap failed: %s\n", strerror(errno));}zfile->mapped_data = NULL;}// 2. 关闭文件描述符if (zfile->fd != -1) {if (close(zfile->fd) == -1) {fprintf(stderr, "close failed: %s\n", strerror(errno));}zfile->fd = -1;}// 重置文件大小zfile->file_size = 0;
}int main() {ZeroCopyFile zfile;const char* filename = "example.txt";// 初始化零拷贝文件(打开并映射)if (zero_copy_file_init(&zfile, filename) != 0) {fprintf(stderr, "Failed to initialize zero copy file\n");return 1;}// 直接访问映射的内存(零拷贝读取)printf("File size: %ld bytes\n", zfile.file_size);printf("File content:\n%s\n", (char*)zfile.mapped_data);// 释放资源(防止泄露)zero_copy_file_destroy(&zfile);return 0;
}
代码讲解
  1. 零拷贝核心逻辑
    mmap 将文件直接映射到进程虚拟地址空间,进程通过 mapped_data_ 指针直接访问文件内容,无需通过 read 系统调用将数据从内核缓冲区拷贝到用户缓冲区。
    相比传统的 read 方式,减少了一次内核到用户空间的数据拷贝,提升了大文件读取效率。
  2. 资源泄露防护措施
    RAII 机制:通过类 ZeroCopyFile 的析构函数自动释放资源(解除映射 + 关闭文件描述符),无论程序正常退出还是抛出异常,资源都会被释放。
    错误处理中的资源清理:构造函数中若某一步失败(如 mmap 失败),会先释放已获取的资源(如关闭文件描述符),再抛出异常。
    禁止拷贝和移动:通过 = delete 禁用拷贝构造、赋值运算符等,避免多个对象管理同一资源导致的重复释放问题。
    显式状态标记:用 fd_ = -1 和 mapped_data_ = nullptr 标记资源已释放,避免析构函数重复操作。
两个进程共享内存

posix_shm.h

#ifndef POSIX_SHM_H
#define POSIX_SHM_H#include <stddef.h>// 共享内存中的数据结构(包含同步标志)
typedef struct {int is_ready;          // 同步标志:0-未就绪,1-数据可用char data[1024];       // 实际传输的数据
} ShmData;// 创建或打开共享内存,返回文件描述符
int shm_create(const char* name, size_t size);// 将共享内存映射到进程地址空间
ShmData* shm_map(int fd, size_t size);// 解除共享内存映射
int shm_unmap(ShmData* ptr, size_t size);// 关闭共享内存文件描述符
int shm_close(int fd);// 删除共享内存对象(彻底清理)
int shm_delete(const char* name);#endif

posix_shm.c

#include "posix_shm.h"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>// 创建或打开共享内存
int shm_create(const char* name, size_t size) {// O_CREAT:不存在则创建;O_RDWR:读写权限// 0666:权限(所有者、组、其他用户都有读写权限)int fd = shm_open(name, O_CREAT | O_RDWR, 0666);if (fd == -1) {fprintf(stderr, "shm_open failed: %s\n", strerror(errno));return -1;}// 设置共享内存大小(若新创建则初始化大小)if (ftruncate(fd, size) == -1) {fprintf(stderr, "ftruncate failed: %s\n", strerror(errno));close(fd);shm_unlink(name);  // 清理已创建的对象return -1;}return fd;
}// 将共享内存映射到进程地址空间
ShmData* shm_map(int fd, size_t size) {// PROT_READ | PROT_WRITE:读写权限// MAP_SHARED:修改对其他进程可见(共享模式)ShmData* ptr = mmap(NULL,           // 由系统分配映射地址size,           // 映射大小PROT_READ | PROT_WRITE,  // 读写权限MAP_SHARED,     // 共享映射(关键:修改对其他进程可见)fd,             // 共享内存文件描述符0               // 偏移量(从开头映射));if (ptr == MAP_FAILED) {fprintf(stderr, "mmap failed: %s\n", strerror(errno));return NULL;}return ptr;
}// 解除共享内存映射
int shm_unmap(ShmData* ptr, size_t size) {if (munmap(ptr, size) == -1) {fprintf(stderr, "munmap failed: %s\n", strerror(errno));return -1;}return 0;
}// 关闭共享内存文件描述符
int shm_close(int fd) {if (close(fd) == -1) {fprintf(stderr, "close failed: %s\n", strerror(errno));return -1;}return 0;
}// 删除共享内存对象(彻底清理)
int shm_delete(const char* name) {if (shm_unlink(name) == -1) {fprintf(stderr, "shm_unlink failed: %s\n", strerror(errno));return -1;}return 0;
}

writer.c

#include "posix_shm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>// 共享内存名称(POSIX要求以 '/' 开头)
#define SHM_NAME "/my_posix_shm"int main() {int fd = -1;ShmData* shm_ptr = NULL;const size_t shm_size = sizeof(ShmData);// 1. 创建共享内存fd = shm_create(SHM_NAME, shm_size);if (fd == -1) {return 1;}// 2. 映射共享内存到当前进程shm_ptr = shm_map(fd, shm_size);if (shm_ptr == NULL) {shm_close(fd);shm_delete(SHM_NAME);  // 清理已创建的共享内存return 1;}// 3. 初始化共享内存(标志位设为未就绪)shm_ptr->is_ready = 0;// 4. 等待用户输入数据printf("Writer: 请输入要发送的数据(最多1023字符):\n");fgets(shm_ptr->data, sizeof(shm_ptr->data), stdin);// 去除fgets带来的换行符shm_ptr->data[strcspn(shm_ptr->data, "\n")] = '\0';// 5. 标记数据就绪(零拷贝:直接修改共享内存)shm_ptr->is_ready = 1;printf("Writer: 数据已发送,等待读取...\n");// 6. 等待读进程读取完毕while (shm_ptr->is_ready == 1) {usleep(100000);  // 等待100ms}// 7. 清理资源(解除映射 -> 关闭文件描述符 -> 删除共享内存)if (shm_unmap(shm_ptr, shm_size) != 0) {fprintf(stderr, "Writer: 解除映射失败\n");}if (shm_close(fd) != 0) {fprintf(stderr, "Writer: 关闭文件描述符失败\n");}if (shm_delete(SHM_NAME) != 0) {fprintf(stderr, "Writer: 删除共享内存失败\n");}printf("Writer: 退出\n");return 0;
}

reader.c

#include "posix_shm.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>// 共享内存名称(必须与写进程一致)
#define SHM_NAME "/my_posix_shm"int main() {int fd = -1;ShmData* shm_ptr = NULL;const size_t shm_size = sizeof(ShmData);// 1. 打开已创建的共享内存(不创建新的)fd = shm_open(SHM_NAME, O_RDWR, 0666);  // 仅打开,不创建if (fd == -1) {fprintf(stderr, "Reader: 共享内存未创建,请先运行writer\n");return 1;}// 2. 映射共享内存到当前进程shm_ptr = shm_map(fd, shm_size);if (shm_ptr == NULL) {shm_close(fd);return 1;}// 3. 等待写进程数据就绪(零拷贝:直接读取共享内存)printf("Reader: 等待数据...\n");while (shm_ptr->is_ready == 0) {usleep(100000);  // 等待100ms}// 4. 读取数据printf("Reader: 收到数据:%s\n", shm_ptr->data);// 5. 标记数据已读取shm_ptr->is_ready = 0;// 6. 清理资源(仅解除映射和关闭文件描述符,不删除共享内存)if (shm_unmap(shm_ptr, shm_size) != 0) {fprintf(stderr, "Reader: 解除映射失败\n");}if (shm_close(fd) != 0) {fprintf(stderr, "Reader: 关闭文件描述符失败\n");}printf("Reader: 退出\n");return 0;
}

代码讲解

  1. 零拷贝通信流程
    共享内存创建:写进程通过 shm_create(内部调用 shm_open)创建名为 /my_posix_shm 的共享内存对象,并设置大小为 sizeof(ShmData)。
    内存映射:两个进程都通过 shm_map(内部调用 mmap)将共享内存映射到自己的地址空间,得到指向同一物理内存的 shm_ptr。
    数据传输:写进程直接向 shm_ptr->data 写入数据,读进程直接从 shm_ptr->data 读取数据,中间无任何数据拷贝(零拷贝核心)。
    同步机制:通过 is_ready 标志位实现简单同步(实际场景可使用 sem_open 信号量)。
  2. 资源泄漏防护措施
    完整的资源释放链
    每个进程使用共享内存后,必须执行:munmap(解除映射)→ close(关闭文件描述符)。
    写进程作为共享内存的创建者,最后需调用 shm_unlink 彻底删除共享内存对象(避免系统中残留)。
    错误处理中的资源清理
    若映射(mmap)失败,写进程会立即关闭文件描述符并删除共享内存对象,避免资源泄漏。
    所有系统调用都有错误检查,确保异常情况下资源不残留。
    POSIX 共享内存的特性保障
    共享内存对象通过名称(/my_posix_shm)标识,避免与其他共享内存冲突。
    权限控制(0666)确保只有授权进程可访问,增强安全性。
http://www.lryc.cn/news/597254.html

相关文章:

  • AI替代人工:浪潮中的沉浮与觉醒
  • 【Java学习|黑马笔记|Day21】IO流|缓冲流,转换流,序列化流,反序列化流,打印流,解压缩流,常用工具包相关用法及练习
  • Log4j2漏洞复现
  • 论文解析 基于遗传算法增强YOLOv5算法的合成数据风力涡轮叶片缺陷检测
  • mysql什么时候用char,varchar,text,longtext
  • 什么是HTTP长连接、短连接?谁更能抗DoS攻击?
  • C# 正则表达式
  • C#使用socket报错 System.Net.Sockets.SocketException:“在其上下文中,该请求的地址无效。
  • 抽奖系统(2)——注册/登陆
  • C#面向对象三大特性的封装
  • C#定时任务实战指南:从基础Timer到Hangfire高级应用
  • 【系统全面】常用SQL语句大全
  • 避坑:C# json反序列化为float精度丢失
  • 棱镜技术在光谱相机中应用
  • 第八章 W55MH32 HTTP Client示例
  • 机器人行业工商注册企业基本信息数据(1958-2023年)
  • 9.0% 年增速驱动!全球自清洁滚轮拖布机器人市场2031年将迈向 946 百万美元
  • [spring6: @EnableWebSocket]-源码解析
  • [深度学习] 大模型学习3下-模型训练与微调
  • (Arxiv-2025)OmniGen2:通向先进多模态生成的探索
  • springboot集成LangChain4j
  • 华为仓颉编程语言实践体验
  • 开源 Arkts 鸿蒙应用 开发(十)通讯--Http数据传输
  • 高级IO简单介绍
  • 小架构step系列23:加载自定义配置
  • 基于 XGBoost 与 SHAP 的医疗自动化办公与可视化系统(上)
  • 快速梳理遗留项目
  • AI聊天方案:vue+nodeJs+SSE
  • Git 常用的提交类型
  • NX741NX777美光固态闪存NX783NX791