不使用mmap可以防止文件页面被逐出吗? 使用mmap / mlock进行的实验

问题描述

我的理解是,可以通过在文件上执行mmap然后在映射的内存上调用mlock来将文件保留在内存中。

有没有一种方法可以在不执行mmap的情况下将文件数据保留在页面缓存中?具体来说,我想确保在将数据追加到文件中时,我正在写入的页面不会被驱逐。

我意识到这种情况很少见,但是在某些情况下,我相信它会发生。例如,应用程序写入数据,等待的时间长于dirty_writeback_centisecs(此后页面将变得干净并且可以被驱逐),然后写入更多数据。

解决方法

我相信您在理解mlock的工作上有点不对。它的预期用途是:

  1. 声明由于没有从磁盘加载数据或将数据换出(由于性能原因而有用,在实时应用程序中至关重要),因此无需等待从内存中读取数据。
  2. 断言这些页面不会被换出(对于私密数据(如明文形式的密码或私钥)至关重要)。

因此它断言页面已加载到RAM中并阻止了页面被换出。没有任何保证可以防止回写从文件映射的脏页(实际上没有,请参见下面的实验)。

要暗示内核您将很快从fd进行一些读取,所以

posix_fadvise(fd,offset,len,POSIX_FADV_RANDOM);

可能会将文件的请求部分加载到页面缓存中。


我不能肯定地说,但是我想到目前为止,实际上没有办法禁止写回特定文件的脏页。可能有某种方式可以提示它,但我也看不到。


使用mmap / mlock进行的实验

alexander@goblin ~/tmp $ cat mmap.c
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)

int main(int argc,char *argv[]) {
    char *addr;
    int fd;
    struct stat sb;
    size_t length;

    if (argc != 2) {
        fprintf(stderr,"%s file\n",argv[0]);
        exit(EXIT_FAILURE);
    }

    fd = open(argv[1],O_RDWR);
    if (fd == -1) {
        handle_error("open");
    }
    if (fstat(fd,&sb) == -1) {          /* To obtain file size */
        handle_error("fstat");
    }

    length = sb.st_size;

    addr = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if (addr == MAP_FAILED) {
        handle_error("mmap");
    }

    if(mlock(addr,length)<0) {
        handle_error("mlock");
    }

    strcpy(addr,"hello world!");

    sleep(100);

    munmap(addr,length);
    close(fd);

    exit(EXIT_SUCCESS);
}
alexander@goblin ~/tmp $ grep . /proc/sys/vm/dirty_{expire,writeback}_centisecs
/proc/sys/vm/dirty_expire_centisecs:1000
/proc/sys/vm/dirty_writeback_centisecs:500
alexander@goblin ~/tmp $ dd if=/dev/zero of=foo bs=4k count=1
1+0 records in
1+0 records out
4096 bytes (4.1 kB,4.0 KiB) copied,8.1296e-05 s,50.4 MB/s
alexander@goblin ~/tmp $ fallocate -l 4096 foo
alexander@goblin ~/tmp $ sync foo
alexander@goblin ~/tmp $ sudo hdparm --fibmap foo

foo:
 filesystem blocksize 4096,begins at LBA 0; assuming 512 byte sectors.
 byte_offset  begin_LBA    end_LBA    sectors
           0  279061632  279061639          8
alexander@goblin ~/tmp $ sudo dd if=/dev/mapper/vg_main-gentoo_home count=8 skip=279061632 iflag=nocache 2>/dev/null | hexdump -C
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000
alexander@goblin ~/tmp $ gcc mmap.c
alexander@goblin ~/tmp $ ./a.out foo &
[1] 26450
alexander@goblin ~/tmp $ sudo hdparm --fibmap foo

foo:
 filesystem blocksize 4096,begins at LBA 0; assuming 512 byte sectors.
 byte_offset  begin_LBA    end_LBA    sectors
           0  279061632  279061639          8
alexander@goblin ~/tmp $ sudo dd if=/dev/mapper/vg_main-gentoo_home count=8 skip=279061632 iflag=nocache 2>/dev/null | hexdump -C
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000
alexander@goblin ~/tmp $ sleep 10
alexander@goblin ~/tmp $ sudo hdparm --fibmap foo

foo:
 filesystem blocksize 4096,begins at LBA 0; assuming 512 byte sectors.
 byte_offset  begin_LBA    end_LBA    sectors
           0  279061632  279061639          8
alexander@goblin ~/tmp $ sudo dd if=/dev/mapper/vg_main-gentoo_home count=8 skip=279061632 iflag=nocache 2>/dev/null | hexdump -C
00000000  68 65 6c 6c 6f 20 77 6f  72 6c 64 21 00 00 00 00  |hello world!....|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000
alexander@goblin ~/tmp $ fg
./a.out foo
^C