C++ 使用 COW 创建了多少页面错误?

问题描述

我在网上看到过这个问题,不确定我的回答是否正确:

#include <unistd.h>
#include <sys/wait.h>

#define N 100*1024*1024
int a[N];

int main() {
    if (fork() > 0) {
        wait(nullptr);
    } else {
        for (unsigned int i = 0; i < N; ++i) {
            a[i] = 1;
        }
    }
    return 0;
}

问题是:

考虑到在 x86-64 操作系统(页面大小为 4KB = 4096B)中启用了 COW 技术,子进程在其整个运行时产生了多少页面错误。 (不是在寻找非常准确的答案)

  1. 1024*1
  2. 1024*10
  3. 1024*100
  4. 1024*1024
  5. 1024*1024*10
  6. 1024*1024*100

我认为正确的是 6,因为每次子进程尝试写入内存中的写保护位置时,我们都会尝试复制 N 元素。我说得对吗?

我试图用我的程序运行 strace,但似乎没有一个是正确的,因为输出不包含代表每个页面错误的任何数千行:

strace ./a.out
execve("./a.out",["./a.out"],0x7ffd84f9ace0 /* 50 vars */) = 0
brk(NULL)                               = 0x55c515202000
access("/etc/ld.so.nohwcap",F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload",R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD,"/etc/ld.so.cache",O_RDONLY|O_CLOEXEC) = 3
fstat(3,{st_mode=S_IFREG|0644,st_size=96020,...}) = 0
mmap(NULL,96020,PROT_READ,MAP_PRIVATE,3,0) = 0x7f93cbf68000
close(3)                                = 0
access("/etc/ld.so.nohwcap",F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD,"/lib/x86_64-linux-gnu/libc.so.6",O_RDONLY|O_CLOEXEC) = 3
read(3,"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"...,832) = 832
fstat(3,{st_mode=S_IFREG|0755,st_size=2030928,8192,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0) = 0x7f93cbf66000
mmap(NULL,4131552,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_DENYWRITE,0) = 0x7f93cb966000
mprotect(0x7f93cbb4d000,2097152,PROT_NONE) = 0
mmap(0x7f93cbd4d000,24576,MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE,0x1e7000) = 0x7f93cbd4d000
mmap(0x7f93cbd53000,15072,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,0) = 0x7f93cbd53000
close(3)                                = 0
arch_prctl(ARCH_SET_FS,0x7f93cbf674c0) = 0
mprotect(0x7f93cbd4d000,16384,PROT_READ) = 0
mprotect(0x55c4fb12b000,4096,PROT_READ) = 0
mprotect(0x7f93cbf80000,PROT_READ) = 0
munmap(0x7f93cbf68000,96020)           = 0
clone(child_stack=NULL,flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,child_tidptr=0x7f93cbf67790) = 3341
wait4(-1,NULL,NULL)                = 3341
--- SIGCHLD {si_signo=SIGCHLD,si_code=CLD_EXITED,si_pid=3341,si_uid=1000,si_status=0,si_utime=19,si_stime=35} ---
exit_group(0)                           = ?
+++ exited with 0 +++

解决方法

页面错误不会出现在strace中;它们甚至不是“明确”发生的事情。

每次访问分配的物理页面尚未创建或映射到地址空间的内存时,都会发生页面错误。

实际上,您程序的页面错误数是 sizeof(int)*N/pagesize。说明:

数组 a 在全局 .data 段中创建,但默认已初始化。 .data 在进程启动时被映射到 MAP_ANONYMOUS 一次,并且由于人口初始化,它的归零将发生在第一个页面错误上。由于父进程从不访问数组a,因此父进程不会发生a占用的地址空间的单页错误。

唯一会导致 a 页面错误的进程是子进程。然而,页面错误仅在第一次访问以前的非驻留页面时发生。由于 a 是按顺序遍历的,因此 a 上的页面错误只会发生在那些访问 0 == ((uintptr_t)&a[i]) % pagesize 的地方(假设 uintptr_t 强制转换指针与地址完全对应,在大多数平台上,它们确实如此)。>

当然,在内存压力较高的系统中,系统可能会在迭代器到达下一页之前发生抖动,从而换出页面,从而产生额外的页面错误。