【问题标题】:Change an mmap'd memory region from MAP_SHARED to MAP_PRIVATE将 mmap 的内存区域从 MAP_SHARED 更改为 MAP_PRIVATE
【发布时间】:2023-03-30 14:04:01
【问题描述】:

所以我有一个使用 mmap() 分配的内存区域,类似于下面的代码:

void * ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

这里的关键是我使用了 MAP_SHARED 标志。与this 相关问题不同,只需再次调用 mmap() 即可获得 MAP_PRIVATE 和 Copy-on-Write 语义,我不能让内核为我分配不同范围的虚拟地址。此外,在再次调用 mmap() 之前,我不想调用 munmap() 并冒着内核将部分/全部地址范围提供给进程内其他东西的风险。

是否有现有机制可以将 mmap'd 内存区域从 MAP_SHARED 切换到 MAP_PRIVATE 以获得写入时复制语义,而无需取消映射块?

【问题讨论】:

  • 也许带有MAP_FIXED | MAP_PRIVATE 标志的mmap(ptr, ...) 会起作用?不过不确定(这就是为什么我没有把这个放在答案中)。
  • 有趣。手册页提到了一些关于映射的重叠部分(在 ptr 和 len 之间)被丢弃的内容。不知道是旧的还是新的。此外,如果不能使用 MAP_FIXED 指定的地址,则 mmap 会失败。
  • 嗯,一个选择是取消映射,然后再次mmap,这次是MAP_FIXED——但是你会遇到一个问题,内核可能会在进程中将该范围分配给其他东西。除非您可以确保 munmap/mmap 调用是原子的。顺便说一下,为什么新的mmap 需要完全相同的地址?
  • 它是修补 QEMU/KVM 的更大项目的一部分。在来宾设置期间,它会将内存块注册为 RAMBlock,等等。所以基本上 QEMU 期望特定的基地址是有效的内存,我不能冒险改变它。

标签: c mmap


【解决方案1】:

使用MAP_PRIVATE | MAP_FIXED 再次调用mmap() 将起作用。 MMAP(2) 手册页指出,使用 MAP_FIXED 时:

如果指定的地址不能使用,mmap()会失败。

所以,只需使用一个临时指针来存储mmap() 结果。如果mmap() 失败,则不会造成任何伤害。如果mmap() 成功,则您已成功将内存映射区域从MAP_SHARED 切换到MAP_PRIVATE。 (见例子)

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

int main()
{
    int fd;
    void *shared_0, *shared_1;
    void *private_0;
    struct stat st;

    if((fd = open("filename", O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
        fprintf(stderr, "Failed to open(): %s\n", strerror(errno));
    }
    else if(fstat(fd, &st) < 0) {
        fprintf(stderr, "Failed fstat(): %s\n", strerror(errno));
    }
    else if((shared_0 = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, 0)) == MAP_FAILED) {
        fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
    }
    else if((shared_1 = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, 0)) == MAP_FAILED) {
        fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
    }
    else if((private_0 = mmap(shared_0, st.st_size, PROT_READ | PROT_WRITE,
            MAP_FIXED | MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
        fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
    }
    else if(shared_0 != private_0) {
        fprintf(stderr, "Error: mmap() didn't map to the same region");
    }
    else {
        printf("shared_0: %p == private_0: %p\n", shared_0, private_0);
        printf("shared_1: %p\n", shared_1);

        printf("Shared mapping before write: %d\n", (*(char *)shared_1));
        printf("Private mapping before write: %d\n", (*(char *)private_0));

        /* write to the private COW mapping and sync changes */
        (*(char*)private_0) = 42;
        if(msync(private_0, 1, MS_SYNC | MS_INVALIDATE) < 0) {
            fprintf(stderr, "Failed msync(): %s\n", strerror(errno));
            return(1);
        }

        printf("Shared mapping after write: %d\n", (*(char *)shared_1));
        printf("Private mapping after write: %d\n", (*(char *)private_0));
    }

    return(0);
}

【讨论】:

  • 根据 POSIX.1-2008,寻址器只是被覆盖了,但是当您将属性从共享更改为私有时会发生什么情况就有点模糊了。
猜你喜欢
  • 2016-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-12
  • 1970-01-01
  • 2018-11-10
  • 1970-01-01
  • 2018-07-26
相关资源
最近更新 更多