読者です 読者をやめる 読者になる 読者になる

tkokamoの日記

HPCの研究開発なのでそんなことをかきたい

カーネルスタックがvmalloc領域になったことの確認

Linux 4.9からカーネルスタックにvmalloc領域が用いられるようになった。
v3.10系とv4.10系で以下のモジュールをロードしてみて、実際に確認してみた。
ディストリはCentOS7.2

stack_test.c

#include <linux/module.h>
#include <linux/kernel.h>

static int __init stack_test_module_init( void )
{
        int sp; 
        printk( KERN_INFO "int sp is @ %p\n", &sp );
        return 0;
}


static void __exit stack_test_module_exit( void )
{
}

module_init( stack_test_module_init );
module_exit( stack_test_module_exit );

MODULE_DESCRIPTION( "stack_test" );
MODULE_LICENSE( "GPL2" );

Makefile

obj-m := stack_test.o

ROOTDIR  := /usr/src/kernels/`uname -r`/
PWD   := $(shell pwd)

default:
    $(MAKE) -C $(ROOTDIR) M=$(PWD) modules

clean:
    rm -f *.o *.ko

上記コードをmakeして、ロードした結果それぞれ以下のメッセージが/var/log/messagesに出力される。

v3.10系

Mar 30 21:00:24 localhost kernel: int sp is @ ffff88018c22fd4c


v4.10系

Mar 30 20:37:21 localhost kernel: int sp is @ ffffc90003607c7c


x86_64のメモリマップは、LinuxのソースのDocumentation/x86/x86_64/mm.txtによると

Virtual memory map with 4 level page tables:

0000000000000000 - 00007fffffffffff (=47 bits) user space, different per mm
hole caused by [48:63] sign extension
ffff800000000000 - ffff87ffffffffff (=43 bits) guard hole, reserved for hypervisor
ffff880000000000 - ffffc7ffffffffff (=64 TB) direct mapping of all phys. memory ★v3.10系のスタックはここ
ffffc80000000000 - ffffc8ffffffffff (=40 bits) hole
ffffc90000000000 - ffffe8ffffffffff (=45 bits) vmalloc/ioremap space ★v4.10系のスタックはここ
ffffe90000000000 - ffffe9ffffffffff (=40 bits) hole
ffffea0000000000 - ffffeaffffffffff (=40 bits) virtual memory map (1TB)
... unused hole ... 
ffffec0000000000 - fffffc0000000000 (=44 bits) kasan shadow memory (16TB)
... unused hole ... 
ffffff0000000000 - ffffff7fffffffff (=39 bits) %esp fixup stacks
... unused hole ... 
ffffffef00000000 - ffffffff00000000 (=64 GB) EFI region mapping space
... unused hole ... 
ffffffff80000000 - ffffffffa0000000 (=512 MB)  kernel text mapping, from phys 0
ffffffffa0000000 - ffffffffff5fffff (=1526 MB) module mapping space
ffffffffff600000 - ffffffffffdfffff (=8 MB) vsyscalls
ffffffffffe00000 - ffffffffffffffff (=2 MB) unused hole

となり領域が異なることが分かる。

何がうれしいか?

v3.10系のカーネルスタックが置かれていた領域は開始アドレスの違いこそあれ、物理メモリがそのまま連続してマッピングされている。
この領域の良い点の1つは、メモリ割り当て/解放の速度が高速であることである。(仮想アドレスに対応する物理アドレスが予めわかっているため、ページテーブルの設定やTLBのフラッシュが必要ない)
一方で、物理的に連続したメモリを確保出来無い時はメモリ割り当てが失敗してしまうなどの欠点も存在する。

従来のカーネルでは、カーネルスタックとしてこの領域を用いており、各タスクに対し一定(確かx86_64では16KiB)のスタックを割り当てていた。
恐らく、カーネルの開発者はカーネルスタックを食い尽くさないように身長にコードを設計する必要があった。
というよりは、必要となるデータ構造のメモリ領域をスラブを用いてプールしておいたり、動的に確保することでスタックを食い尽すことを回避していたのだろう。


一方、v4.10系のカーネルスタックが置かれているvmalloc領域は、物理メモリが非連続的に仮想アドレスにマッピングされている。
非連続的なマッピングであるため、物理連続なメモリを確保できないからと言って、メモリ割り当てが失敗することはない。(基本的には)
しかし、割り当て/解放のたびにページテーブルをいじったりするので、vmalloc領域のメモリ確保/解放は遅い。(頻繁に呼ばれる処理で毎回この領域の獲得/解放を行うのはやめなければならない)
この領域をカーネルスタックに用いた場合、v3.10まで存在したスタックの限界というものが、かなり緩和されることとなる。
カーネル開発者は、スタックの枯渇に神経質になることもなく、また割り込みがネストしてしまいスタックを食い尽すという心配から解放されるようだ。

おわりに

思い立って10分ほどで記事を書いたので、大分大雑把な説明になってしまっている。
今後は、v4.9以降スタックオーバーフローが起きたときにどう回復しているのかなど、もう少し見ていきたい。