繁体   English   中英

linux kernel /proc/$PID/maps 中的脏页记帐

[英]Dirty page accounting in linux kernel /proc/$PID/maps

TLDR; kernel 究竟是如何在 /proc/$PID/maps 中进行脏页统计的。

考虑 C 中的以下程序语句

static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE)))

现在未初始化的变量在开始时为零。 我的理解是,在程序开始时,kernel 将未初始化的变量映射到零页,并进行页的写时复制惰性分配。 很好,这样 kernel 可以在页面错误发生时解释未初始化部分的脏页。

现在考虑语句

static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {'c'}

在这里,加载器将在程序初始化时加载 page1 的值,并将页面标记为 RW。 因此,程序所做的任何写入都必须对 kernel 不可见,因为不会触发页面错误。

这是我为实验编写的程序。

#define PAGE_SIZE (4*1024)
static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {'c'};
int main()
{
  char c; int i; int *d;
  scanf("%c", &c);                // --------- tag 1
  for(i = 0; i < PAGE_SIZE; i++)
    {
      page1[i] = c;               // --------- tag 2
    }
  d = malloc(sizeof(int));
  while(1);
  return 0;
}

现在,在标签 1 之前和标签 2 之后(代码中的注释),包含page1的部分的/proc/$PID/smaps的 output 粘贴在下表中

地图 TAG-1 之前 TAG-2 之后
尺寸: 8 KB 8 KB
内核页面大小: 4 KB 4 KB
MMU页面大小: 4 KB 4 KB
回复: 8 KB 8 KB
附: 8 KB 8 KB
Shared_Clean: 0 KB 0 KB
Shared_Dirty: 0 KB 0 KB
私人清洁 4 KB 0 KB
Private_Dirty 4 KB 8 KB
参考: 8 KB 8 KB
匿名 4 KB 8 KB

如您所见,粗体颜色的参数发生了变化。

问题

  1. kernel 到底是怎么知道我写了这个页面的?
  2. 这个匿名字段是什么以及它为什么会改变?

任何其他详细解释所有工作的页面/博客/手册都会有所帮助。

我猜:

也许 kernel 将页面标记为 RO,即使它是 RW 以便触发页面错误并且它可以进行计算。 或者也许有一些其他进程不断地遍历进程的页表,但这太昂贵了。

现在考虑语句

static char page1[PAGE_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {'c'}

在这里,加载器将在程序初始化时加载 page1 的值,并将页面标记为 RW。

您似乎相信加载程序会为该语句写入 memory,但事实并非如此。

在这种情况下发生的不是mmap RW + 字节'c'写入。 该字节已经在编译时嵌入到您的可执行文件中,因此唯一发生的就是mmap RW,仅此而已。 像这样的东西:

mmap(0, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd_of_your_elf, offset_of_data_section);

或者,最有可能的是mmap(...entire file...)后跟一系列mprotect() ,对 ELF 的不同部分具有正确的权限。

实际上,执行此操作的甚至不是加载程序,而是 kernel 本身将可执行文件映射到 memory 在这种情况下,假设您以./exe启动程序。 加载程序仅在以/path/to/loader./exe调用时自行映射程序。 另请参阅我的另一个答案,其中我有更详细的解释。


kernel 到底是怎么知道我写了这个页面的?

您可能已经知道,当您的程序最初映射到 memory (包括包含page1的页面)时,即使该页面的映射是 RW,也没有真正需要 kernel 实际分配 ZCD69B4957F06CDDE298D7 到 a3BF6198发生读或写。 这种技术称为需求分页 最初(在映射之后)该页面甚至不存在于进程的页表中:它仅作为任务的 memory map 中的许多vm_area_struct条目之一存在。

当发生页面错误(由读取或写入引起)时,kernel 然后根据映射的性质决定要做什么。 在这种情况下,映射是文件支持的(整个page1数组的实际初始值在编译时写入您的 ELF 文件),因此两种可能的情况如下:

  1. 当发生 memory读取时,会发生页面错误,并将页面内容从文件读取到 memory。 新分配的页面现在被标记为只读,即使它被映射为 RW(kernel 仍然知道这个 VMA 是 RW)。

  2. 当发生 memory写入时,有两种情况:( A )页面已经存在于 memory 中,因为之前的读取(并标记为 RO),或者( B )页面根本不在 ZCD69B4957F0190CD818D7BFED3 中,因为第一个 memory 访问它。 在这两种情况下,都会发生页面错误,kernel 检查是否允许写入(是的),并发生写入时复制

    由于页面是文件支持的,但不是共享的(即不使用MAP_SHARED映射),因此不需要将数据写回文件,因此 kernel 只需分配一个新的匿名页面,然后将内容从前一个页面复制过来页(案例A )或在应用写入之前将页面从文件读取到 memory (案例B )。 这就是为什么您会看到Anonymous go 从 0kB 到 4kB。

    此外,由于旧页面在写时复制之前只有一个用户,因此可以将其释放,这就是您看到Rss保持不变的原因。 最后,在写入之前页面是干净的(不脏),而在写入之后它变得脏(不干净),所以这就是为什么您会看到Private_CleanPrivate_Dirty更改值的原因。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM