vm.overcommit_memoryの挙動をソースを見ながら何となく追ってみたメモ書き

この記事は2018年3月21日に公開したものです。情報が古い可能性がありますのでご注意ください。

Linuxのカーネルパラメータvm.overcommit_memory=2(メモリのオーバーコミットをさせない設定)の時、vm.overcommit_ratioの値を99とかではなく、カーネルが使うメモリを確保するために80とかにして余裕を持たせておく必要があるとかないとかの話になって、結局のところどうなっているのか、現時点での最新カーネルをなんとなく読みながら調べてみたので書いておく。

ソースコードリーディング

とりあえずGitHubに上がっているLinuxカーネルのソースコードを読んでみることにした。
この時のLinuxカーネルのバージョンは Linux Kernel 4.16 RC のものだったと思う。

overcommit_memoryとかでgrepしながら追ってみると該当部分の処理はlinux/mm/util.cにあるっぽい。
以下の__vm_enough_memory()というところ。
コメントの記載をみると、新しいプロセスの割り当て時に十分なメモリがあるかどうかチェックしているようだ。

__vm_enough_memory()関数の最初の vm_acct_memory(pages);で Committed_AS が加算され、カーネルパラメータvm.overcommit_memoryの値によって処理を分岐させている模様。

OVERCOMMIT_NEVER(vm.overcommit_memory=2つまりオーバーコミットしない)の場合は同一ファイル内のvm_commit_limit()が呼び出されている。

/*
 * Check that a process has enough memory to allocate a new virtual
 * mapping. 0 means there is enough memory for the allocation to
 * succeed and -ENOMEM implies there is not.
 *
 * We currently support three overcommit policies, which are set via the
 * vm.overcommit_memory sysctl.  See Documentation/vm/overcommit-accounting
 *
 * Strict overcommit modes added 2002 Feb 26 by Alan Cox.
 * Additional code 2002 Jul 20 by Robert Love.
 *
 * cap_sys_admin is 1 if the process has admin privileges, 0 otherwise.
 *
 * Note this is a helper function intended to be used by LSMs which
 * wish to use this logic.
 */
int __vm_enough_memory(struct mm_struct *mm, long pages, int cap_sys_admin)
{
	long allowed;

	VM_WARN_ONCE(percpu_counter_read(&vm_committed_as) <
			-(s64)vm_committed_as_batch * num_online_cpus(),
			"memory commitment underflow");

	vm_acct_memory(pages);

	/*
	 * Sometimes we want to use more memory than we have
	 */
	if (sysctl_overcommit_memory == OVERCOMMIT_ALWAYS)
		return 0;

	if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) {
		free = global_zone_page_state(NR_FREE_PAGES);
		free += global_node_page_state(NR_FILE_PAGES);

		/*
		 * shmem pages shouldn't be counted as free in this
		 * case, they can't be purged, only swapped out, and
		 * that won't affect the overall amount of available
		 * memory in the system.
		 */
		free -= global_node_page_state(NR_SHMEM);

		free += get_nr_swap_pages();

		/*
		 * Any slabs which are created with the
		 * SLAB_RECLAIM_ACCOUNT flag claim to have contents
		 * which are reclaimable, under pressure.  The dentry
		 * cache and most inode caches should fall into this
		 */
		free += global_node_page_state(NR_SLAB_RECLAIMABLE);

		/*
		 * Part of the kernel memory, which can be released
		 * under memory pressure.
		 */
		free += global_node_page_state(NR_KERNEL_MISC_RECLAIMABLE);

		/*
		 * Leave reserved pages. The pages are not for anonymous pages.
		 */
		if (free <= totalreserve_pages)
			goto error;
		else
			free -= totalreserve_pages;
		
		
		/*
		 * Reserve some for root
		 */
		if (!cap_sys_admin)
			free -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10);

		if (free > pages)
			return 0;

		goto error;
	}


	allowed = vm_commit_limit();
	/*
	 * Reserve some for root
	 */
	if (!cap_sys_admin)
		allowed -= sysctl_admin_reserve_kbytes >> (PAGE_SHIFT - 10);

	/*
	 * Don't let a single process grow so big a user can't recover
	 */
	if (mm) {
		reserve = sysctl_user_reserve_kbytes >> (PAGE_SHIFT - 10);
		allowed -= min_t(long, mm->total_vm / 32, reserve);
	}

	if (percpu_counter_read_positive(&vm_committed_as) < allowed)
		return 0;
error:
	vm_unacct_memory(pages);

	return -ENOMEM;

}
    :
    :
    :
/*
 * Committed memory limit enforced when OVERCOMMIT_NEVER policy is used
 */
unsigned long vm_commit_limit(void)
{
	unsigned long allowed;

	if (sysctl_overcommit_kbytes)
		allowed = sysctl_overcommit_kbytes >> (PAGE_SHIFT - 10);
	else
		allowed = ((totalram_pages - hugetlb_total_pages())
			   * sysctl_overcommit_ratio / 100);
	allowed += total_swap_pages;

	return allowed;
}

vm_acct_memory()関数はinclude/linux/mman.hに定義されている。

static inline void vm_acct_memory(long pages)
{
	percpu_counter_add_batch(&vm_committed_as, pages, vm_committed_as_batch);
}

static inline void vm_unacct_memory(long pages)
{
	vm_acct_memory(-pages);
}

カーネルパラメータvm.overcommit_memoryの値を各条件分岐で見てovercommitできるなら0を、できないようなら-ENOMEMを返すようだ。
overcommitができない場合というのは、設定で許可されていないかovercommitできるかどうか計算した結果NGだった場合、のいずれかの模様。
vm.overcommit_memory=2の場合、vm.overcommit_kbytes>vm.overcommit_ratioのどちらかの値が有効になる。(両方指定した場合にどちらが有効になるかは未検証)
sysctl_overcommit_kbytesが設定されていない場合はallowed = ((totalram_pages - hugetlb_total_pages()) * sysctl_overcommit_ratio / 100);で割り当て可能なメモリ量が算出されている。
ここで何故hugetlb_total_pages()の値を引いているのかわかっていない…

vm.overcommit_memory=0 or vm.overcommit_memory=2の場合は、if(!cap_sys_admin)でプロセスに管理者権限があるかどうかチェックされており、cap_sys_adminが0の時、つまり管理者権限がない時にはadmin_reserve_kbytes分のメモリがallowからマイナスされて確保されるようになっているっぽい。
(vm.overcommit_memory=0のときは free として、vm.overcommit_memory=2のときは予約ページとして確保されている?)
また、その後、reserve = sysctl_user_reserve_kbytes >> (PAGE_SHIFT - 10);で1つユーザプロセスが肥大化しないようにvm.user_reserve_kbytes分の値を確保していた。

最後のif (percpu_counter_read_positive(&vm_committed_as) < allowed)の部分で、committed_asの値がallowed(≒CommitLimit)を越えなければ大丈夫ということらしい。

いろいろたどっていくと__vm_enough_memory()security/security.c内のsecurity_vm_enough_memory_mm()で呼ばれていた。

int security_vm_enough_memory_mm(struct mm_struct *mm, long pages)
{
	struct security_hook_list *hp;
	int cap_sys_admin = 1;
	int rc;

	/*
	 * The module will respond with a positive value if
	 * it thinks the __vm_enough_memory() call should be
	 * made with the cap_sys_admin set. If all of the modules
	 * agree that it should be set it will. If any module
	 * thinks it should not be set it won't.
	 */
	hlist_for_each_entry(hp, &security_hook_heads.vm_enough_memory, list) {
		rc = hp->hook.vm_enough_memory(mm, pages);
		if (rc <= 0) {
			cap_sys_admin = 0;
			break;
		}
	}
	return __vm_enough_memory(mm, pages, cap_sys_admin);
}

admin_reserve_kbytesはドキュメント(/Documentation/sysctl/vm.txt)に記載があった。
vm.overcommit_memory=2とした時、何かあった場合に管理者がログインしてプロセスを強制終了したりできるようにするためには、sshdまたはlogin + bashあたりが実行できるサイズを確保できるよう十分な値に調整する必要がある値のようだ。

admin_reserve_kbytes

The amount of free memory in the system that should be reserved for users
with the capability cap_sys_admin.

admin_reserve_kbytes defaults to min(3% of free pages, 8MB)

That should provide enough for the admin to log in and kill a process,
if necessary, under the default overcommit 'guess' mode.

Systems running under overcommit 'never' should increase this to account
for the full Virtual Memory Size of programs used to recover. Otherwise,
root may not be able to log in to recover the system.

How do you calculate a minimum useful reserve?

sshd or login + bash (or some other shell) + top (or ps, kill, etc.)

For overcommit 'guess', we can sum resident set sizes (RSS).
On x86_64 this is about 8MB.

For overcommit 'never', we can take the max of their virtual sizes (VSZ)
and add the sum of their RSS.
On x86_64 this is about 128MB.

Changing this takes effect whenever an application requests memory.

==============================================================

user_reserve_kbytesはドキュメント(/Documentation/sysctl/vm.txt)を見ると、ユーザープロセスがメモリーを占有しないようにあらかじめ予約しておくためのもののようだった。

- user_reserve_kbytes

When overcommit_memory is set to 2, "never overcommit" mode, reserve
min(3% of current process size, user_reserve_kbytes) of free memory.
This is intended to prevent a user from starting a single memory hogging
process, such that they cannot recover (kill the hog).

user_reserve_kbytes defaults to min(3% of the current process size, 128MB).

If this is reduced to zero, then the user will be allowed to allocate
all free memory with a single process, minus admin_reserve_kbytes.
Any subsequent attempts to execute a command will result in
"fork: Cannot allocate memory".

Changing this takes effect whenever an application requests memory.

==============================================================

ちなみにUbuntu16.04で見ると、デフォルト値のパラメータはvm.overcommit_memory=0vm.admin_reserve_kbytes=8192でドキュメントの通りだった。

vm.admin_reserve_kbytes = 8192
vm.overcommit_kbytes = 0
vm.overcommit_memory = 0
vm.overcommit_ratio = 50

まとめ

vm.overcommit_memoryの値における挙動についてソースコードを読みながら調べてみた。

  1. __vm_enough_memory()の最初の vm_acct_memory(pages);で Committed_AS が加算される。ようだが、厳密にどういう処理が行われているのかはまだ見ていない。
  2. user_reserve_kbytesで1つのプロセスが肥大化しないような制御や、admin_reserve_kbytesで管理者用のメモリ領域が確保される動きになっているように思う。(が、その理解で合っているのかわからない。)

ということで、overcommit_ratioの値は、特にカーネルで使用するメモリのことを考える必要はないのでは?と思っている。

ただ、ドキュメントによると、オーバーコミットできないときにメモリ不足等になった場合に備えるにはvm.admin_reserve_kbytesの値を、場合によってはsshdやbashが実行できる程度のメモリを確保できるように確認して必要に応じて調整しておいた方が良いかもしれない。

参考

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です