原文来自CRIU官网的文章《Memory pre dump》
翻译此文,用于学习CRIU的dump和pre-dump机制。翻译时没有用翻译软件,不求字字准确,能表达出核心含义,语句通顺就行。甚至会按照我对CRIU的研究拓展一下。
本文作者按个人理解加以描述的文字用(__)来标记。

概述

当我们使用CRIU执行pre-dump时,CRIU现将内存存放在一个pipe管道中,然后再保存到image文件里。这会使用大量的pipe(当要pre-dump的进程使用的内存过大时),并将所有的内存用RAM存放。建议使用sys_read_process_vm()系统调用来解决这个问题。
如果你阅读源码,看到这个函数cr_pre_dump_tasks(),你会看到:

  1. 在一个for循环中调用cr_pre_dump_one_task()函数来处理pre-dump的进程以及它所有的子进程。
  2. 调用cr_pre_dump_finish(),在这个函数里面调用pstree_switch_state()来解冻进程,然后循环调用page_xfer_dump_pages()函数处理上述处理的进程

这就是问题所在。第一个函数(也就是cr_pre_dump_one_task())注入了一个寄生虫代码到被pre-dump的进程中,用于收集所有它使用的内存。mem_dump_ctl结构体的pre_dump成员变量被置为true,让转储内存的代码保存了与进程描述符fd(即pstree_item对象)相关的在page-pipes中的所有页面。在pre_dump_finish()函数处理所有进程和子进程之后,把内存保存到image文件中。
我们需要做的是调整两个调用。(指cr_pre_dump_one_task()和cr_pre_dump_finish())
首先,pre_dump_one_task函数不应该注入寄生虫代码到被pre-dump的进程,以及保存这个进程的内存页。他只需要暂停这个进程,然后获取VMA-S列表即可。在此之后,pre_dump_finish函数应该将进程解冻(因为之前被暂停/冻结了),然后遍历VMAs列表,在每个VMA上调用sys_read_process_vm()函数用于拷贝内存页内容到image文件中。或者对于page-server来说,page-xfer会处理它。(这里应该指的是对于page-server的pre-dump,在获取内存页时额外用page-xfer来处理)
后面部分的棘手之处在于,在进程被解冻之后,sys_read_process_vm函数会和进程修复他们的VMAs的任务竞争。因此,你必须小心的处理潜在部分read操作甚至报错,并据此分别调整生成的image文件。
另一个棘手之处是内存区域的读保护。你需要检查sys_read_process_vm函数是否允许root用户(即CRIU以root身份运行)持续读内存区域,如果没有,也可以通过跳过这些读操作来处理。现在,pre-dump读取未被内存保护的内存页,然后抓取他们,之后再去读受内存保护的内存页。但是这不是新算法的一个选项。(即CRIU应该没有实现这样的特性)

优化

上述所提到的系统调用是复制内存的,所以这里有优化空间。如果我们能有一个sys_splice_process_vm系统调用,它的行为就像普通的splice系统调用,但sys_splice_process_vm能在其他进程的内存上工作,我们可以用内存vm-splicing(splice中文翻译为“拼接”)来代替内存拷贝,并进一步拼接到图像或页面服务器套接字文件描述符中。

更多信息