Linux多进程11-内存映射

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

image

mmap

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    功能: 将一个文件或者设备的数据映射到内存中
    参数:
        - void* addr: NULL 由内核指定
        - length: 要映射的数据的长度, 这个值不能为0, 建议使用文件的长度
            (获取文件的长度: stat lseek)
        - prot: 对申请的内存映射区的操作权限
            - PROT_EXEC: 可执行的权限
            - PROT_READ: 读权限
            - PROT_WRITE: 写权限
            - PROT_NONE: 没有权限
            要操作映射内存, 必须要有读权限, 如: PROT_READ|PROT_WRITE
        - flags:
            - MAP_SHARED: 映射区的数据会自动和磁盘文件进行同步, 进程间通信,必须要设置这个选项
            - MAP_PRIVATE: 不同步, 内存映射区的数据改变了, 对原来的文件不会修改, 会重新创建一个新的文件(copy on write)
        - fd: 需要映射的那个文件的文件描述符
            - 通过open得到, open的是一个磁盘文件
            - 注意: 文件的大小不能为0, open指定的权限不能和prot参数有冲突(prot参数要小于等于open参数)
        - offset: 偏移量(一般不用), 必须指定的是4K的整数倍, 0表示不偏移
    返回:
        返回创建的内存的首地址
        失败返回 MAP_FAILED, (void*) -1

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

//使用内存映射实现进程间通信

    1. 有关系的进程(父子进程)
        - 还没有子进程的时候, 通过唯一的父进程, 先创建内存映射区
        - 有了内存映射区以后, 创建子进程
        - 父子进程共享创建的内存映射区

    2. 没有关系的进程间通信
        - 准备一个大小不是0的磁盘文件
        - 进程1 通过磁盘文件创建内存映射区, 得到一个操作这块内存的指针
        - 进程2 通过磁盘文件创建内存映射区, 得到一个操作这块内存的指针
        - 使用内存映射区通信

    注意: 内存映射区通信是非阻塞的

实例: 内存映射实现父子进程通信

test.txt

helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld,helloworld.

mmap-parent-child-ipc.c

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

int main(int argc, char const *argv[])
{
    //打开一个文件
    int fd = open("test.txt", O_RDWR);
    //获取文件大小
    int size = lseek(fd, 0, SEEK_END);

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

    //创建子进程
    pid_t pid = fork();
    if (pid > 0)
    {
        //回收完子进程的资源之后再读数据
        wait(NULL);
        //父进程 读数据  
        char buf[64];
        strcpy(buf, (char *)ptr);
        printf("read data: %s\n", buf);
    }
    else if (pid == 0)
    {
        //子进程  写数据
        strcpy((char *)ptr, "nihao a, son!"); 
    }

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

    return 0;
}

运行程序

$./mpci
read data: nihao a, son!

内存映射的注意事项

◼ 如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
void * ptr = mmap(...); ptr++; 可以对其进行++操作
但是 munmap(ptr, len);释放内存映射操作会产生错误, 要提前保存地址

◼ 如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误, 会返回MAP_FAILED; 建议open函数权限建议和prot参数权限保持一致

◼ 如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍, 返回MAP_FAILED

◼ mmap什么情况下会调用失败?

  • 第二个参数: length=0
  • 第三个参数: prot
    • 只指定了写权限
    • prot函数 PROT_READ | PROT_WRITE
  • 第五个参数 fd 通过open函数时指定的 O_RDONLY / O_WRONLY
  • 第六个参数偏移量必须是4K整数倍

◼ 可以open的时候O_CREAT一个新文件来创建映射区吗?
可以的, 但创建的文件大学不能为0; 可以使用 lseek() / truncate() 函数对新文件扩展

◼ mmap后关闭文件描述符,对mmap映射有没有影响?

没有任何影响, close(fd); 后映射区还存在, 内存还是没有释放(munmap)

◼ 对ptr越界操作会怎样?
void* ptr = mmap(NULL, 100, ,,,,);
越界操作是操作非法的内存 -> 段错误

内存映射完成文件复制

image-20221104145946378
//使用内存映射实现文件拷贝功能
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    // 1.对原始文件进行内存映射
    int fd = open("english.txt", O_RDWR);
    if (fd == -1)
    {
        perror("open err");
        exit(0);
    }
    //获取原始文件大小
    int len = lseek(fd, 0, SEEK_END);

    // 2.创建一个新文件
    int fd1 = open("cpy.txt", O_RDWR | O_CREAT, 0664);
    if (fd1 == -1)
    {
        perror("open err");
        exit(0);
    }
    //拓展该文件
    truncate("cpy.txt", len);
    write(fd1, " ", 1);

    // 3.把新文件数据映射到内存中
    void *ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    void *ptr1 = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd1, 0);

    if (ptr == MAP_FAILED)
    {
        perror("mmap err");
        exit(0);
    }

    // 4.通过内存拷贝将一个新的文件内存数据拷贝到新的文件内存中
    memcpy(ptr1, ptr, len);

    // 5.释放资源 (后打开先释放)
    munmap(ptr1, len);
    munmap(ptr, len);
    close(fd1);
    close(fd);

    return 0;
}

匿名映射

/*
    匿名映射: 不需要文件实体进行一个内存映射, 因此也只能做父子进程通信
*/

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

int main(int argc, char const *argv[])
{
    // 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 err");
        exit(0);
    }

    //父子进程间通信
    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 err");
        exit(0);
    }

    return 0;
}

运行程序

$./mmap-anon 
hello, world  #停1s后打印
posted @ 2023-05-17 18:39  言叶以上  阅读(43)  评论(0编辑  收藏  举报