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

16 篇文章 13 订阅

一、简介

正如其名(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 的值,父子进程遵从 读时共享,写时复制 原则。

  • 5
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值