#Linux内存管理# 详细介绍使用mmap函数创立共享匿名映射的工作原理
使用 mmap 创建共享匿名映射 - 详细解析与代码示例
共享匿名映射的工作原理
共享匿名映射 (MAP_SHARED | MAP_ANONYMOUS) 允许不相关进程间建立高效的共享内存通信机制:
1.零文件依赖
不涉及磁盘文件,纯内存通信
使用 MAP_ANONYMOUS 标志,忽略文件描述符
2.内核分配机制
内核直接分配匿名物理内存页
页内容初始化为零
3.共享内存原理
不同进程的虚拟地址映射到相同物理内存页
映射地址在不同进程中通常不同
4.高速通信特性
进程间通信绕过内核缓冲
比管道/套接字快10-100倍
适合高频、低延迟场景
5.进程终止处理
映射自动释放
内存回收由内核管理
完整代码示例(父子进程通信)
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#define SHM_SIZE 4096 // 共享内存大小(页大小倍数)
#define DATA_SIZE 256
// 共享数据结构
struct shared_data {
pid_t owner;
int counter;
char timestamp[DATA_SIZE];
char message[DATA_SIZE];
};
void child_process(void *shm_ptr) {
struct shared_data *data = (struct shared_data *)shm_ptr;
// 子进程等待父进程初始化
while (data->owner == 0) {
usleep(100000); // 100ms等待
}
printf("\n子进程 [%d] 开始访问共享内存\n", getpid());
for (int i = 0; i < 5; i++) {
// 读取父进程写入的数据
printf("子进程读取: 计数器=%d | %s | 时间: %s\n",
data->counter, data->message, data->timestamp);
// 更新计数器(可能产生竞争条件)
data->counter++;
// 写入子进程消息
time_t now = time(NULL);
strftime(data->timestamp, DATA_SIZE, "%Y-%m-%d %H:%M:%S", localtime(&now));
snprintf(data->message, DATA_SIZE, "子进程 %d 写入第 %d 条", getpid(), i+1);
usleep(300000); // 300ms
}
}
int main() {
// 创建共享匿名映射
void *shm_ptr = mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, // 关键参数
-1, 0);
if (shm_ptr == MAP_FAILED) {
perror("共享内存映射失败");
exit(EXIT_FAILURE);
}
struct shared_data *data = (struct shared_data *)shm_ptr;
// 初始化共享区域
memset(data, 0, sizeof(struct shared_data));
data->owner = getpid();
data->counter = 0;
strcpy(data->timestamp, "未初始化");
strcpy(data->message, "等待数据传输...");
printf("=============================================\n");
printf("共享匿名映射创建成功!\n");
printf("映射地址: %p\n", shm_ptr);
printf("内存大小: %d 字节\n", SHM_SIZE);
printf("父进程 PID: %d\n", getpid());
printf("=============================================\n");
// 创建子进程
pid_t pid = fork();
if (pid == 0) {
// 子进程分支
child_process(shm_ptr);
exit(EXIT_SUCCESS);
} else if (pid > 0) {
// 父进程分支
printf("\n父进程 [%d] 开始写入共享内存\n", getpid());
for (int i = 0; i < 5; i++) {
// 更新父进程数据
data->counter += 10;
// 更新时间戳
time_t now = time(NULL);
strftime(data->timestamp, DATA_SIZE, "%Y-%m-%d %H:%M:%S", localtime(&now));
// 更新消息
snprintf(data->message, DATA_SIZE, "父进程 %d 写入第 %d 条", getpid(), i+1);
printf("父进程写入: 计数器=%d | %s\n", data->counter, data->message);
usleep(500000); // 500ms
}
// 等待子进程结束
int status;
wait(&status);
printf("\n最终共享状态:\n");
printf(" 最终计数器: %d\n", data->counter);
printf(" 最终消息: %s\n", data->message);
printf(" 最终时间: %s\n", data->timestamp);
// 解除映射
if (munmap(shm_ptr, SHM_SIZE) == -1) {
perror("取消映射失败");
}
printf("共享内存已释放\n");
} else {
perror("fork 失败");
// 解除映射
munmap(shm_ptr, SHM_SIZE);
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
关键代码解析
1. 创建共享匿名映射
void *shm_ptr = mmap(NULL, SHM_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
MAP_ANONYMOUS:创建基于内存的映射(不依赖文件)
MAP_SHARED:使映射可在进程间共享
fd = -1:文件描述符被忽略
内存自动初始化为零
2. 共享数据结构设计
struct shared_data {
pid_t owner; // 标识数据创建者
int counter; // 共享计数器
char timestamp[256]; // 时间戳
char message[256]; // 消息内容
};
定义清晰的通信协议
可扩展为复杂数据结构
3. 父子进程通信模式
// 父进程写入数据
data->counter += 10;
snprintf(data->message, ...);
// 子进程读取数据
printf("子进程读取: %s", data->message);
内存访问完全透明,如同访问常规内存
修改立即可见(无需显式同步)
4. 资源管理
// 解除映射
munmap(shm_ptr, SHM_SIZE);
父子进程都应解映射
最后一个解映射的进程会触发内存回收
5. 编译运行与输出示例
# 编译程序
gcc anon_shared_mmap.c -o anon_shared_mmap
# 运行程序
./anon_shared_mmap
示例输出:
=============================================
共享匿名映射创建成功!
映射地址: 0x7fd94ed1a000
内存大小: 4096 字节
父进程 PID: 31245
=============================================
父进程 [31245] 开始写入共享内存
父进程写入: 计数器=10 | 父进程 31245 写入第 1 条
子进程 [31246] 开始访问共享内存
子进程读取: 计数器=10 | 父进程 31245 写入第 1 条 | 时间: 2023-08-05 15:45:22
父进程写入: 计数器=20 | 父进程 31245 写入第 2 条
子进程读取: 计数器=21 | 子进程 31246 写入第 1 条 | 时间: 2023-08-05 15:45:23
子进程读取: 计数器=21 | 子进程 31246 写入第 1 条 | 时间: 2023-08-05 15:45:23
父进程写入: 计数器=31 | 父进程 31245 写入第 3 条
...
父进程写入: 计数器=50 | 父进程 31245 写入第 5 条
子进程读取: 计数器=51 | 子进程 31246 写入第 4 条 | 时间: 2023-08-05 15:45:25
最终共享状态:
最终计数器: 51
最终消息: 子进程 31246 写入第 4 条
最终时间: 2023-08-05 15:45:25
共享内存已释放
非父子进程通信方案
非相关进程可通过路径名共享匿名内存(Linux特有方法):
#define SHM_NAME "/my_shm"
// 创建进程
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(fd, SHM_SIZE);
void *ptr = mmap(NULL, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 访问进程
int fd = shm_open(SHM_NAME, O_RDWR, 0);
void *ptr = mmap(NULL, SHM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// 最后清理
shm_unlink(SHM_NAME);
优势与适用场景
1.极低延迟通信
微秒级进程间通信
高频交易系统核心
2.零拷贝架构
避免数据复制开销
大数据处理系统
3.实时系统
工业控制系统
多媒体处理管道
4.安全敏感场景
敏感数据不落盘
安全算法实现
注意事项
1.同步机制
// 在共享数据中加入互斥锁
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&lock, &attr);
2.内存对齐
// 避免错误共享(false sharing)
__attribute__((aligned(64))) int counter;
3.资源清理
// 确保所有进程解除映射
atexit(cleanup_handler);
4.内存可见性
// 使用内存屏障保证写入可见
__sync_synchronize();
此代码展示了 mmap 共享匿名映射的核心机制。通过结构化的共享内存设计,进程可以高效交换复杂数据结构,适用于从简单计数器到复杂数据库的各种高性能场景。