roto11fonの日記

技術系の話を書いたりしてます。

CRIUのメモリ取得について

こんにちは。お久しぶりです。

本日はCRIU(Checkpoint / Restore in Userspace)が行なっているメモリ取得に関する部分を書いていきます。

 

CRIUとは

ユーザ空間からプロセスの状態を保存し、復元することができるプログラムです。

これは動作しているプログラムを別のマシンに移動する技術である「マイグレーション」に利用されます。

 

CRIUのメモリ取得

プロセスの状態にはプロセスが利用しているメモリの状態が含まれます。

メモリには大きく分けて以下の領域が存在します。

テキスト領域:プログラム本体 ELF
データ領域:staticがつけられた変数を保持
ヒープ領域:mallocなどで動的に増したメモリを保持
スタック領域:変数や関数の呼び出し順番などを保持
ライブラリ:libcなど
メモリの状態を取得するわけですが、CRIUは割り当てられたメモリを全て取得しているわけではありません。 

CRIUのメモリ取得条件

CRIUはページ単位(4KB, 0x1000)でメモリを取得しています。取得条件の判定はshould_dump_page()で行なっています。
mem.c#L181

for (pfn = 0; pfn < nr_to_scan; pfn++) {
		unsigned long vaddr;
		unsigned int ppb_flags = 0;
		int st;

		if (!should_dump_page(vma->e, at[pfn]))
			continue;

		vaddr = vma->e->start + *off + pfn * PAGE_SIZE;

		if (vma_entry_can_be_lazy(vma->e) && !is_stack(item, vaddr))
			ppb_flags |= PPB_LAZY;

should_dump_page()の関数内部は以下になってます。returnがtrueになるページが取得すべきページです。
mem.c#L102

bool should_dump_page(VmaEntry *vmae, u64 pme)
{
	/*
	 * vDSO area must be always dumped because on restore
	 * we might need to generate a proxy.
	 */
	if (vma_entry_is(vmae, VMA_AREA_VDSO))
		return true;
	/*
	 * In turn VVAR area is special and referenced from
	 * vDSO area by IP addressing (at least on x86) thus
	 * never ever dump its content but always use one provided
	 * by the kernel on restore, ie runtime VVAR area must
	 * be remapped into proper place..
	 */
	if (vma_entry_is(vmae, VMA_AREA_VVAR))
		return false;

	/*
	 * Optimisation for private mapping pages, that haven't
	 * yet being COW-ed
	 */
	if (vma_entry_is(vmae, VMA_FILE_PRIVATE) && (pme & PME_FILE))
		return false;
	if (vma_entry_is(vmae, VMA_AREA_AIORING))
		return true;
	if ((pme & (PME_PRESENT | PME_SWAP)) && !__page_is_zero(pme))
		return true;

	return false;
}

上から順番に見ていきます。

if (vma_entry_is(vmae, VMA_AREA_VDSO))
		return true;


if (vma_entry_is(vmae, VMA_AREA_VVAR))
		return false;

そのページがvDSOの領域なら取得すべきページです。
vDSOとは、システムコールの実行を早くするライブラリです。
通常のシステムコールカーネルモードへ切り替え(コンテキストスイッチ)が必要ですが、頻繁に行われるとパフォーマンスに影響するので、出来るだけコンテキストスイッチを行わないでシステムコールを実行するようにしてます。

VVARは取得しないページです。
VVARはvDSOの一部で、vDSOで利用する変数の格納場所です。
CRIUは復元時にカーネルから提供されるVVARをそのまま使うので取得しないようです。
vdso(7) - Linux manual page
Implementing virtual system calls [LWN.net]

if (vma_entry_is(vmae, VMA_FILE_PRIVATE) && (pme & PME_FILE))
	return false;
if (vma_entry_is(vmae, VMA_AREA_AIORING))
	return true;
if ((pme & (PME_PRESENT | PME_SWAP)) && !__page_is_zero(pme))
	return true;

そのページがファイルをマップした領域であれば取得しない。
AIOについては詳しくないのですが、AIOで利用するためにバッファエリアは取得するようです。

そしてそれ以外の部分です。
PME_PRESENTはカーネルから物理ページを与えられている状態、
PME_SWAPはスワップとして利用されている状態です。
そしてこれらのページがゼロページ(書き込みのない状態のページ)であれば取得はしません。

pmeはpagemapの略ですが、Linuxのprocfsにはpagemapという仮想ファイルがあります。
このファイルはそのプロセスの持つ仮想メモリが物理メモリに、どんな状態でどこにあるかというページマップエントリの情報を保持しています。ページマップエントリは一つ8Kで表現されます。procfs/pagemapには仮想メモリの0x0から0x8000000000000000をページで分割した数のページマップエントリがあります。
Pagemap Interface of Linux Explained - Jeff Li
f:id:roto11fon:20190713161207p:plain
ページマップエントリは上の図のようになっていて、PME_PRESENTは63bit目の値で表現されます。

ゼロページの判定はこのページマップエントリのPageFrameNumerを比較することで行なっています。
ゼロページのPFNは一定になるため、CRIUははじめの方にmmapで新しくメモリを確保し、書き込みを行わず、すぐにprocfs/pagemapで対象のページマップエントリを取得しています。
mem.#87
kerndat.c#409

CRIUのメモリ取得方法

また次回