Linux进程通信:存储映射mmap

1. 存储映射是什么?

        如上图,存储映射是将块设备中的文件映射到进程的虚拟地址空间。之后,进程可以直接使用指针操作其地址空间中映射的文件,对这块映射区操作就相当于操作文件。


2. 存储映射函数mmap的简单使用

(1)mmap函数:

#include<sys/mman.h>

void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
/*
功能:
    将文件fd从开头偏移offset个字节处开始,
    映射到调用该函数的进程的虚拟地址空间的addr地址开始的往后length个字节的虚拟地址空间长度。
    映射区的保护方式为prot,特性为flags。

参数:
    addr:映射到进程地址空间的起始地址,通常为NULL,由内核指定;

    length:映射到进程地址空间的大小;

    prot:映射区的保护方式:
        a)读:PROT_READ
        b)写:PROT_WRITE
        c)读写:PROT_READ | PROT_WRITE

    flags:映射区的特性:
        a)MAP_SHARED:写入映射区的数据会复制回文件,且允许共享给其他映射该文件的进程;
        b)MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write),
                        对此区域的修改不会写回原文件;

    fd:要映射的文件,即open返回的文件描述符;

    offset:从文件起始位置偏移offset开始映射,必须是4K的整数倍;
            通常为0,表示从文件起始位置开始映射;

返回值:
    成功:映射区的首地址
    失败:MAP_FAILED宏
*/

mmap使用总结:

(1)第一个参数:通常为 NULL;

(2)第二个参数:映射文件大小 > 0;

(3)第三个参数:PROT_READ、PROT_WRITE;

(4)第四个参数:MAP_SHARED、MAP_PRIVATE;

(5)第五个参数:要映射的文件描述符;

(6)第六个参数:4K的整数倍,通常为0;

mmap注意事项:

(1)创建映射区过程中,隐含一次对映射文件的读操作;

(2)当flags为MAP_SHARED时,要求:映射区权限 ≤ 文件打开的权限;而MAP_PRIVATE无要求;

(3)映射成功文件即可关闭;

(4)文件大小为0时不能创建映射区。使用mmap时常出错总线错误(bus error),通常是映射文件大小的问题;

(5)munmap的传入地址一定是mmap的返回值,禁止对其地址++操作;

(6)mmap调用出错几率高,一定要检查返回值。


(2)munmap函数 :

#include<sys/mman.h>

int munmap(void* addr, size_t length);
/*
功能:
    释放进程地址空间addr开始的length个字节的存储映射区
参数:
    addr:映射区的起始地址,即mmap函数的返回值;
    length:映射区大小,即mmap函数的第二个参数;
返回值:
    成功:0
    失败:-1
*/

(3)mmap使用示例:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>

int main(int argc, const char* argv[]) {

    int fd = -1;
    int ret = -1;
    void* addr = NULL;

    // 1.以读写的方式打开txt文件
    fd = open("txt", O_RDWR);
    if (-1 == fd) {
        perror("open");
        return 1;
    }

    // 2.将文件映射到进程的虚拟地址空间
    addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    printf("映射成功.\n");

    // 3.关闭文件
    close(fd);

    // 4.写文件
    memcpy(addr, "123456", 6);

    // 5.断开存储映射
    munmap(addr, 1024);

    return 0;
}

运行结果:


3. mmap实现父子进程通信

(1)原理:

a)父进程先创建存储映射区,得到映射区在其虚拟地址空间中的起始地址addr和映射长度length;

b)fork子进程后,子进程虚拟地址空间存在和父进程addr、length一样的存储映射区;

c)因为父子进程都映射同一个文件,因此可通过该文件进行通信。

(2)代码示例:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int main(int argc, const char* argv[]) {

    int fd = -1;
    int ret = -1;
    pid_t pid = -1;
    void* addr = NULL;

    // 1.以读写的方式打开txt文件
    fd = open("txt", O_RDWR);
    if (-1 == fd) {
        perror("open");
        return 1;
    }

    // 2.将文件映射到进程的虚拟地址空间
    addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    printf("映射成功.\n");

    // 3.关闭文件
    close(fd);

    // 4.创建子进程
    pid = fork();
    if (-1 == pid) {
        perror("fork");
        return 1;
    }

    if (0 == pid) { // 子进程写文件
        memcpy(addr, "ABCD", 4);
    } else { // 父进程读文件
        wait(NULL); // 等待子进程结束
        printf("子进程写的内容:%s\n", (char*)addr);
    }

    // 5.断开存储映射
    munmap(addr, 1024);

    return 0;
}

运行结果:


4. mmap实现无关系的进程通信

代码示例:

mmap_a.c文件:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int main(int argc, const char* argv[]) {

    int fd = -1;
    int ret = -1;
    pid_t pid = -1;
    void* addr = NULL;

    // 1.以读写的方式打开txt文件
    fd = open("txt", O_RDWR);
    if (-1 == fd) {
        perror("open");
        return 1;
    }

    // 2.将文件映射到进程的虚拟地址空间
    addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    printf("映射成功.\n");

    // 3.关闭文件
    close(fd);

    // 4. 读存储映射区
    printf("读存储映射区:%s\n", (char*)addr);

    // 5.断开存储映射
    munmap(addr, 1024);

    return 0;
}

mmap_b.c文件:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int main(int argc, const char* argv[]) {

    int fd = -1;
    int ret = -1;
    pid_t pid = -1;
    void* addr = NULL;

    // 1.以读写的方式打开txt文件
    fd = open("txt", O_RDWR);
    if (-1 == fd) {
        perror("open");
        return 1;
    }

    // 2.将文件映射到进程的虚拟地址空间
    addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    printf("映射成功.\n");

    // 3.关闭文件
    close(fd);

    // 4.写存储映射区
    memcpy(addr, "XYZ", 3);

    // 5.断开存储映射
    munmap(addr, 1024);

    return 0;
}

运行结果:


 5. 匿名映射

父子进程使用存储区映射进行通信的缺陷是需要依赖一个文件。

为了克服该缺陷,父子进程可使用匿名映射,无需依赖文件即可创建映射区,需要借助flags参数MAP_ANONYMOUS(或MAP_ANON)来指定为匿名映射,如下:

int* addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
/*
MAP_ANONYMOU需要和MAP_SHARED 一起使用
MAP_ANONYMOUS和MAP_ANON是Linux系统特有的宏,类unix系统中无该宏定义。
*/

匿名映射示例:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>

int main(int argc, const char* argv[]) {

    int ret = -1;
    pid_t pid = -1;
    void* addr = NULL;

    // 1.创建匿名映射
    addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (MAP_FAILED == addr) {
        perror("mmap");
        return 1;
    }

    // 2.创建子进程
    pid = fork();
    if (-1 == pid) {
        perror("fork");
        munmap(addr, 4096);
        return 1;
    }

    // 3.父子进程通信
    if (0 == pid) {
        memcpy(addr, "1234", 4); // 子进程写
    } else {
        wait(NULL); // 等待子进程结束
        printf("父进程读到:%s\n", (char*)addr);
    }

    // 4.断开映射
    munmap(addr, 4096);

    return 0;
}

运行结果:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的马师兄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值