当我们使用 mmap 函数时,OS 做了什么工作?

What jobs OS does when we use mmap function?

这是一个使用 mmap 函数将给定文件映射到内存的示例。在这个例子中,我没有使用 fwritewrite 函数将内容写入磁盘文件(只是将内容打印到标准输出),但修改后的内存内容实际上反映在磁盘文件上。我猜 OS 跟踪映射内存并在修改映射内存时写入磁盘。我想知道 OS 的详细信息。

example.c

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

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

    if(argc < 2){
        printf("File path not mentioned\n");
        exit(0);
    }

    const char *filepath = argv[1];
    int fd = open(filepath, O_RDWR);
    if(fd < 0){
        printf("\n\"%s \" could not open\n",
               filepath);
        exit(1);
    }

    struct stat statbuf;
    int err = fstat(fd, &statbuf);
    if(err < 0){
        printf("\n\"%s \" could not open\n",
                       filepath);
        exit(2);
    }

    char *ptr = mmap(NULL,statbuf.st_size,
            PROT_READ|PROT_WRITE,MAP_SHARED,
            fd,0);
    if(ptr == MAP_FAILED){
        printf("Mapping Failed\n");
        return 1;
    }
    close(fd);

    ptr[statbuf.st_size - 2] = '@';
    ssize_t n = write(1,ptr,statbuf.st_size);
    if(n != statbuf.st_size){
        printf("Write failed\n");
    }
    while(1);
}

mytext.txt

hello world!

编译和运行

gcc example.c && ./a.out mytext.txt

由于最后一行,程序没有退出,我可以通过cat mytext.txt

在另一个终端看到修改后的内容
hello world@

I'm guessing that OS tracks mapped memory and writes to disk when mapped memory is modified.

是也不是。当然,操作系统将内存记录映射到磁盘上的文件,并确保对该内存的更改写入磁盘。但是,我不希望立即写入更改。

根据常识(我已经很久没看过操作系统的这些特定内部结构了),可能发生的情况是管理文件系统的软件记录了磁盘上的某些块已映射到内存,一旦它们被更改,它们就是“脏的”,这意味着它们已经被修改并且最终需要写回磁盘。

操作系统在某些情况下可能会将脏页写入磁盘。例如,当磁盘上的文件已关闭(这也意味着内存页已被取消映射,因为将内存映射到文件包括保持文件打开)并且有卸载磁盘的请求,脏页必须是写入磁盘。作为一种策略选择,它们也可能会定期写入磁盘。

但是,这些更改可能看起来在您的文件中,即使它们没有写入磁盘!当您读取文件时,文件系统会确定磁盘上的哪些块包含您请求的文件部分。然后它看到那些块已经在内存中。因此,为了满足您的读取请求,它为您提供内存中的数据而不是从磁盘读取数据。因此,对于作为用户的您来说,即使数据尚未在磁盘上,更改的数据实际上也在文件中。

这种行为实际上超出了管理文件系统的一般设计。如果您打开一个文件并读取 100 个字节,文件系统软件首先必须在磁盘上归档正确的块,将其读入内存,然后将您请求的 100 个字节复制到您的进程。该块可能是 512 字节或 4096 字节或用于磁盘操作的其他一些大小。文件系统将如何处理该块?在请求一个文件的 100 字节后,程序很可能会再请求 100 字节。再次从磁盘读取它会很可惜。所以文件系统软件会保留它。这意味着它保留了一个数据库,其中包含当前内存中的哪些磁盘块。

接下来,假设其他一些程序读取同一个文件。显然,如果所需的块已经在内存中,我们将从内存中的块中为该程序提供数据,而不是再次从磁盘中读取它。所以文件系统共享所有这些块(受适当的权限)。如果一个程序更改了文件中的数据,它会转到内存中的块,因此即使它尚未写入磁盘,其他程序也会立即看到它。

内存映射文件与这种在内存中缓存磁盘块的方案密切相关。进程的虚拟内存只是映射到文件系统软件保存文件块的物理内存,每个进程都有相同的文件视图,无论它是使用内存映射还是读写操作。

(这是一般信息;特定操作系统的行为可能不同。)

您的代码似乎是在 Linux.

上编译的

我假设是。

你的处理器的mmap(2) system call is related to the virtual address space, and as explained in this OS textbook, the virtual address space is managed by the MMU。 2021 年,MMU 将成为您处理器芯片的一部分。

所以 mmap 以不同的方式询问 OS kernel to reconfigure the MMU. The CPU mode is changed to kernel mode as in every syscalls(2), and the kernel code will reconfigure the MMU and some internal kernel data to handle future page faults

另见 kernelnewbies, the OSDEV wiki, Advanced Linux Programming, Linux Assembly HOWTO and study the source code of your Linux kernel and/or GNU libc

您正在使用 gcc example.c 进行编译。考虑编译 with gcc -Wall -Wextra -g example.c (to get useful warnings and DWARF debug information) then using GDB and strace(1) or ltrace(1) 以了解可执行文件的行为。

PS。您可能需要使用 perror(3), or errno(3) with strerror(3)