【Linux】Linux进程间通信——内存映射

发布于:2022-12-07 ⋅ 阅读:(1263) ⋅ 点赞:(1)


内存映射实现进程间通信

内存映射,Memory-mapped I/O,是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。

内存映射对应的虚拟地址空间是动态库/共享库所在的区域;映射的内存数据修改,文件数据也会对应修改,如果文件也映射到另一个进程,那么另一个进程就可以获取到修改后的数据。

在这里插入图片描述

1. 内存映射相关系统函数

内存映射相关系统函数在头文件 #include <sys/mman.h>中。

相关的函数有:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);  // 映射文件到内存中
int munmap(void *addr, size_t length);  // 解除映射,释放映射的内存

mmap()函数:

  • 功能:将文件或设备数据映射到内存中
  • 参数:
    • void *addr:传递NULL,由内核指定该地址

    • size_t length:映射的数据的长度,值不能为0,建议使用文件的长度,单位字节;stat()或>lseek()函数获取文件长度;最少是分页的整数倍;

    • int prot:对申请的内存映射区的操作权限,可以使用按位或|操作;要操作映射内存,需要有读的权限;

      • PROT_NONE:没有权限
      • PROT_EXEC:可执行权限
      • PROT_READ:读权限
      • PROT_WRITE:写权限
      • 推荐使用的权限:PROT_READPROT_READ|PROT_WRITE
    • int flags

      • MAP_SHARED:映射区的数据自动和磁盘文件同步,进程间通信需要设置>MAP_SHARED
      • MAP_PRIVATE:不同步,内存映射区的数据改变不会修改原来的文件,底层会重新创建一个文件,copy on write
    • int fd:需要映射的文件的文件描述符,通过open()打开磁盘文件得到;需要注意文件大小不能为0、open()指定的权限不能和prot参数冲突(prot的权限 < fd的权限);

    • off_t offset:文件映射时的偏移量,必须指定为4k的整数倍,一般不用设置为0,表示不偏移,从文件开头开始映射;

  • 返回值:成功返回创建的内存的地址,失败返回宏MAP_FAILED

munmap()函数:

  • 功能:释放内存映射
  • 参数:
    • void *addr:要释放的内存的首地址
    • size_t length:要释放的内存的大小,和mmap()函数中的length参数设置一样

内存映射可以实现有亲缘关系的进程间通信:

  • 首先创建内存映射区;
  • 然后创建子进程;
  • 父子进程间共享创建的内存映射区;

也可以实现没有亲缘关系的进程间通信:

  • 首先准备一个大小不是0的磁盘文件;
  • 进程1通过磁盘文件创建内存映射区,得到操作内存的指针;
  • 进程2通过磁盘文件创建内存映射区,得到操作内存的指针;
  • 使用内存映射区通信;

内存映射区通信是非阻塞的。

2. 内存映射使用示例

  • 使用内存映射实现父子进程之间的通信
#include <stdio.h>
#include <sys/mman.h> // 内存映射相关头文件
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int main()
{

    char *filename = "test.txt";
    // 打开文件
    int fd = open(filename, O_RDWR);
    if (fd == -1)
    {
        perror("open");
        exit(-1);
    }

    // 获取文件大小
    off_t len = lseek(fd, 0, SEEK_END);

    // 创建内存映射区
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }

    //  创建子进程
    pid_t pid = fork();
    if (pid > 0)
    {
        wait(NULL);
        // 父进程 读数据

        char buf[1024] = {0};
        strcpy(buf, (char *)ptr);
        printf("receive: %s \n", buf);
    }
    else if (pid == 0)
    {
        // 子进程  写数据
        strcpy((char *)ptr, "hello,father!!");
    }

    //  关闭内存映射区
    munmap(ptr, len);

    // 关闭文件
    close(fd);

    return 0;
}

运行结果:

zoya@zoya-virtual-machine:~/Linux/chap2/lesson12$ ./mmap
receive: hello,father!! 
  • 使用内存映射实现没有亲缘关系的进程间通信

mmap-read.c

// 内存映射实现进程间通信的读数据

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

int main()
{
    // 1. 打开文件获取文件描述符,获取文件大小
    char *filename = "test.txt";
    int fd = open(filename, O_RDWR);
    if (fd == -1)
    {
        perror("open");
        exit(-1);
    }
    // 2. 获取文件长度
    off_t len = lseek(fd, 0, SEEK_END);
    if (len == -1)
    {
        perror("lseek");
        exit(-1);
    }

    // 3. 内存映射 mmap
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }

    // 4. 读取数据
    char buf[1024] = {0};
    while (1)
    {
        sleep(1);
        memset(buf, 0, sizeof(buf));
        strcpy(buf, (char*)ptr);
        printf("read : %s \n", buf);
    }

    // 5. 释放内存映射
    munmap(ptr, len);

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

    return 0;
}

mmap-write.c

// 内存映射实现进程间通信的写数据

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

int main()
{
    // 1. 打开文件获取文件描述符,获取文件大小
    char *filename = "test.txt";
    int fd = open(filename, O_RDWR);
    if (fd == -1)
    {
        perror("open");
        exit(-1);
    }

    // 2. 获取文件长度
    off_t len = lseek(fd, 0, SEEK_END);
    if (len == -1)
    {
        perror("lseek");
        exit(-1);
    }

    // 3. 内存映射 mmap
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }

    // 4. 写数据
    char buf[1024] = {0};
    int nIndex = 0;
    while (1)
    {
        sleep(1);
        memset(buf, 0, sizeof(buf));
        sprintf(buf, "%d hello!", nIndex++);
        printf("write : %s \n", buf);
        strcpy((char*)ptr, buf);
    }

    // 5. 释放内存映射
    munmap(ptr, len);

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

    return 0;
}

程序运行结果:
在这里插入图片描述
运行后test.txt文件中的内容:
在这里插入图片描述

3. 内存映射函数使用注意事项

  1. 调用mmap()函数后错误,错误信息:Permission denied
    原因是打开文件时使用的 O_RDONLY | O_WRONLY权限,应该使用 O_RDWR替换。
    参考链接:https://www.cnblogs.com/AD-milk/p/14541555.html

  2. 文件偏移量必须是4k的整数倍,如果不是整数倍,一般会错误,函数会返回MAP_FAILED,会提示错误Invalid argument

  3. mmap()函数调用失败的情况:

  • 参数len=0;
  • 参数prot仅指定了写权限;
  • 参数prot指定了PROT_READ|PROT_WRITE,但是参数fd打开的权限指定的是O_RDONLY或O_WRONLY;
  1. mmap()函数调用后可以关闭文件描述符,调用mmap()后,映射区仍然存在,fd被关闭没有影响;
  2. 对mmap()返回值prt操作越界,会导致段错误。

4. 匿名映射

匿名映射是不需要文件实体进行的内存映射,可以用来进行父子间的通信,如下:

// 匿名映射

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

int main()
{
    // 1. 创建匿名内存映射区
    int len = 4096;
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); // 匿名映射
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }

    // 父子进程间通信
    pid_t pid = fork();
    if (pid > 0)
    {
        // 父进程
        // 写数据
        strcpy((char *)ptr, "hello,world!");
        wait(NULL);
    }
    else if (pid == 0)
    {
        // 子进程
        // 读取数据
        sleep(1);
        printf("%s\n", (char *)ptr);
    }

    // 释放内存映射区
    int ret = munmap(ptr, len);
    if (ret == -1)
    {
        perror("munmap");
        exit(-1);
    }

    return 0;
}

文章参考:https://www.nowcoder.com/study/live/504/2/18

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到