版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/afei__/article/details/84257303
一、简介
正如其名(Memory Map),mmap
可以将某个设备或者文件映射到应用进程的内存空间中。通过直接的内存操作即可完成对设备或文件的读写。.
通过映射同一块物理内存,来实现共享内存,完成进程间的通信。由于减少了数据复制的次数,一定程度上提高了进程间通信的效率。
二、API 说明
1. 头文件
#include <sys/mman.h>
2. 创建内存映射
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
: 将文件映射到进程空间指定地址,可以为 NULL,此时系统将自动分配地址length
: 被映射到进程空间的内存块大小prot
: 被映射内存的访问权限PROT_EXEC
: 内存页可执行PROT_READ
: 内存页可读PROT_WRITE
: 内存页可写PROT_NONE
: 内存页不可访问
flags
: 指定程序对内存块所做的改变将造成的影响,通常有:MAP_SHARED
: 共享的形式,对内存块所做的修改将保存到文件中MAP_PRIVATE
: 私有的,对内存块的修改只在局部范围内有效MAP_FIXED
: 使用指定的映射起始地址MAP_ANONYMOUS
/MAP_ANON
: 匿名映射,即不和任何文件关联,同时将 fd 设置为 -1。通常需要进程间有一定关系才能使用这种映射方式
fd
: 文件描述符,即open()
函数返回的值offset
: 指定从文件的哪一部分开始映射,必须是内存页的整数倍,通常为 0- 返回值 : 成功返回指向映射内存的指针,失败返回 -1,并设置合适的 errno 值
3. 解除内存映射
int munmap(void *addr, size_t length);
addr
: 映射内存的起始指针,必须是 mmap 方法返回的那个值length
: 映射到进程空间的内存块大小- 返回值 : 成功返回 0,失败返回 -1,并设置合适的 errno 值
三、示例
1. 无血缘关系进程通信
写端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
typedef struct _data {
int a;
char b[64];
} Data;
int main() {
Data *addr;
Data data = { 10, "Hello World\n" };
int fd;
fd = open("mmap_temp_file", O_RDWR|O_CREAT|O_TRUNC, 0644);
if (fd == -1) {
perror("open failed\n");
exit(EXIT_FAILURE);
}
ftruncate(fd, sizeof(data));
// 使用fd创建内存映射区
addr = (Data *)mmap(NULL, sizeof(data), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed!\n");
exit(EXIT_FAILURE);
}
close(fd); // 映射完后文件就可以关闭了
memcpy(addr, &data, sizeof(data)); // 往映射区写数据
munmap(addr, sizeof(data)); // 释放映射区
return 0;
}
读端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
typedef struct _data {
int a;
char b[64];
} Data;
int main() {
Data *addr;
int fd;
fd = open("mmap_temp_file", O_RDONLY);
if (fd == -1) {
perror("open failed\n");
exit(EXIT_FAILURE);
}
// 使用fd创建内存映射区
addr = (Data *)mmap(NULL, sizeof(Data), PROT_READ, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap failed!\n");
exit(EXIT_FAILURE);
}
close(fd); // 映射完后文件就可以关闭了
printf("read form mmap: a = %d, b = %s\n", addr->a, addr->b); // 往映射区写数据
munmap(addr, sizeof(Data)); // 释放映射区
return 0;
}
执行结果:
2. 血缘关系进程通信
存在血缘关系的话,可以使用 匿名 的方式创建映射区,这样就不需要那个临时文件了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int m_var = 100;
int main() {
int *addr;
pid_t child_pid;
// 以匿名的方式创建内存映射区,适用于存在血缘关系的进程间
addr = (int *)mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap failed!\n");
exit(EXIT_FAILURE);
}
child_pid = fork(); // 创建子进程
if (child_pid == 0) {
*addr = 666; // 往内存映射区写数据
m_var = 200;
printf("child process: *addr = %d, m_var = %d\n", *addr, m_var);
} else {
sleep(1);
printf("parent process: *addr = %d, m_var = %d\n", *addr, m_var); // 读内存映射区的数据
wait(NULL);
int ret = munmap(addr, sizeof(int)); // 释放内存映射区
if (ret == -1) {
perror("munmap failed\n");
exit(EXIT_FAILURE);
}
}
return 0;
}
执行结果:
addr
的值,父子进程都成功改变了。全局变量 m_var
的值,父子进程遵从 读时共享,写时复制 原则。