Linux 进程通信之:内存映射(Memory Map)

一、简介

正如其名(Memory Map),mmap 可以将某个设备或者文件映射到应用进程的内存空间中。通过直接的内存操作即可完成对设备或文件的读写。.

通过映射同一块物理内存,来实现共享内存,完成进程间的通信。由于减少了数据复制的次数,一定程度上提高了进程间通信的效率。

二、API 说明

1. 头文件

#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
 
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 
#include 
#include 
#include 
#include 
#include 
#include 
 
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;
}

执行结果:

Linux 进程通信之:内存映射(Memory Map)_第1张图片

2. 血缘关系进程通信

存在血缘关系的话,可以使用 匿名 的方式创建映射区,这样就不需要那个临时文件了。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
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 的值,父子进程遵从 读时共享,写时复制 原则。

你可能感兴趣的:(Linux,C/C++,内存映射,mmap)