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