Upgrade to Pro — share decks privately, control downloads, hide ads and more …

HPCS Lab. SSチーム 論文読み会 "The benefits and costs of writing a POSIX kernel in a high-level language"

HPCS Lab. SSチーム 論文読み会 "The benefits and costs of writing a POSIX kernel in a high-level language"

TAKAHASHI Shuuji

July 03, 2019
Tweet

More Decks by TAKAHASHI Shuuji

Other Decks in Programming

Transcript

  1. The benefits and costs of writing a
    POSIX kernel in a high-level
    language
    2019-07-03 SSチーム論文読み会
    HPCS Lab. SSチーム M1 高橋 宗史
    Cody Cutler, M. Frans Kaashoek, and Robert T.
    Morris, MIT CSAIL
    出典: https://blog.golang.org/gopher

    View Slide

  2. 論文について
    ● ページ数: 14ページ
    ● 発表: 13th USENIX Symposium on
    Operating System Design and
    Implementation (OSDI '18)
    ● 開催日: 2018年10月8日-10日
    ● 場所: Carlsbad, CA, USA
    出典: https://www.booking.com/hotel/us/carlsbad-inn-beach-resort.ja.html

    View Slide

  3. この論文を読むモチベーション
    ● 自分の研究とは直接は関係しない
    ● Goでカーネルを作る話が面白そうだったため選んだ
    ● カーネル実装の話を通して、普段使っているLinuxにより親しめるようになることを
    期待
    ● カーネルを構成する基本コンポーネントを知るのはLinuxを知るためにも有用なは

    View Slide

  4. 注意事項
    ● 要約するのが下手で、輪講のように長く(40ページ)なってしまいました
    ● 今日紹介するのは、§1 - §4 まで
    ● §5 - §10 は次回以降です

    View Slide

  5. 論文の構成
    1. Introduction
    ○ OSのカーネルを実装する言語の概略
    ○ Goで実装したカーネルBiscuitの性能をCのカーネルと比較
    2. Related work
    ○ 4つの観点から関連を紹介
    3. Motivation
    ○ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認
    4. Overview
    ○ Goで実装したカーネルBiscuitの概要を様々な観点から解説

    View Slide

  6. 論文の構成 (cont'd)
    ↓以下の紹介は次回以降です
    5. Garbage collection
    ○ ガベージコレクションについて
    6. Avoiding heap exhaustion
    ○ ヒープの枯渇問題に対処した話
    7. Implementation
    ○ 実装したコードの説明
    8. Evaluation
    ○ 詳細な評価結果
    9. Discussion and future work
    10. Conclusions

    View Slide

  7. Abstract
    ● ガベージコレクションを持つハイレベル言語(High Level Language; HLL)を使用し
    て、モノリシックなPOSIXスタイルのカーネルを実装
    ○ 目的: 実行コスト・実装のチャレンジ・プログラマビリティ・安全性の検証
    ● 論文では、十分にPOSIXを実装したカーネルBiscuitをGoで実装
    ○ POSIX: 仮想メモリ・`nmap`・TCP/IPソケット・ログファイルシステム・`poll`など
    ○ GoのHLL機能を多用: クロージャ・チャネル・マップ・インターフェイス・ガベージコレクショ
    ン付きヒープ割当
    ○ 特に、カーネルのヒープメモリ枯渇問題
    への対処が
    最もchallengingなpuzzleだった

    View Slide

  8. Abstract (cont'd)
    ● 評価: カーネルインテンシブなベンチマークを実施
    ○ NGINX・Redisなどを実行
    ○ 評価結果
    ■ HLL機能 (主にガベージコレクションとスレッドスタックの拡張のチェック
    )によるカー
    ネルCPU時間のfraction (断片化?) = 最大13%増加
    ■ NGINXによるGC関連の最大の停止時間 = 115 μs
    ■ クライアントから見たGCのディレイの合計の最大値 = 600 μs
    ■ システムコール・ページフォルト・コンテキストスイッチの条件をほぼ同じにして
    CとGo
    のカーネルを比較した結果、5% - 15% Goの方が遅かった

    View Slide

  9. 論文の構成
    1. Introduction
    ○ OSのカーネルを実装する言語の概略
    ○ Goで実装したカーネルBiscuitの性能をCのカーネルと比較
    2. Related work
    ○ 4つの観点から関連を紹介
    3. Motivation
    ○ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認
    4. Overview
    ○ Goで実装したカーネルBiscuitの概要を様々な観点から解説
    5. Garbage collection
    ○ ガベージコレクションについて

    View Slide

  10. 1. Introduction
    ● 現在の実用的なOSのカーネルはすべてCで書かれている
    ○ Linux, macOS, Windows など
    ○ メモリへのアクセス性とメモリ管理の性能の高さ
    ○ 一方でメモリ管理はバグの温床にもなっている (2017年の50個のバグの原因)
    ● HLL (High-Level Language)
    ○ 抽象化とメモリ安全性を提供
    ○ 新しいアイデアの検証に利用
    ○ 一方で実使用に耐える高性能なカーネルとしての使用は懐疑的に考えられている
    ● しかし、CをHLLでリライトするのは無意味かもしれないが、新しく使用すべき言語を
    検証するのは有意義だと考えられる

    View Slide

  11. 1. Introduction (cont'd)
    ● Go で x86-64 向けのカーネル Biscuit を書いた
    ○ POSIX システムコールのサブセットを実装
    ■ ウェブサーバの NGINX やインメモリの
    キーバリューストアRedis が実際に動く!
    ○ その他多数の機能: マルチコア・カーネル支援ユーザースレッド
    ・futex・IPC・mmap・copy-on-write fork・vnode・name cache・ロギングファイルシステ
    ム・TCP/IP ソケット
    ● futexとは?
    ○ Linux独自の高速ユーザー空間 mutex (Fast Userspace muTex)
    ○ 参考: futex - 約束事その他の説明 - Linux コマンド集 一覧表 -
    https://kazmax.zpp.jp/cmd/f/futex.7.html
    出典: http://download.redis.io/logocontest/ https://www.nginx.com/

    View Slide

  12. 1. Introduction (cont'd)
    ● Goでx86-64向けのカーネルBiscuitを書いた (つづき)
    ○ ドライバ
    ■ ストレージ: AHCI SATAディスクコントローラー
    ■ ネットワーク: Intel 82599-based Ethernetコントローラ
    ○ コード量
    ■ Goコード: 約28,000行
    ■ アセンブリ: 1546行
    ■ Cコード: 0行 (no C!)
    ○ 参考
    ■ 最初のコミット: 約10年前
    ■ 現在までのコミット数: 約39,000コミット
    ■ コントリビューター: 約1,000人

    View Slide

  13. 1. Introduction (cont'd)
    ● 設計
    ○ 基本的には伝統的なモノリシックの
    POSIX/Unixカーネルと同じようなデザイン
    ○ Biscuitの設計で優れている点
    ■ カーネルヒープ枯渇問題に対処するしくみ
    ● HLLの機能を利用した静的解析のおかげで、複雑なメモリ割り当ての失敗や
    デッドロックからのリカバリが可能になった

    View Slide

  14. 1. Introduction (cont'd)
    ● ベンチマーク
    ○ ガベージコレクションなど
    ■ ガベージコレクションはCPUの最大3%を使用
    ■ NGINXではGC関連の停止が 115 μs、クライアント側では 600 μs
    ■ その他のHLLのコストはCPUの10%
    ○ Cとの比較
    ■ CとGoのシステムコールのコードパスを同等に修正して測定
    ■ Cの方が 5% - 15% 高速
    ○ カーネルインテンシブなアプリケーションベンチマーク
    ■ Linuxの方が最大 10% 高速
    ○ Biscuitの方が性能は低いが、十分戦える性能は発揮されている
    出典: https://www.vexels.com/png-svg/preview/140692/linux-logo

    View Slide

  15. 1. Introduction (cont'd)
    ● 本研究の貢献
    ○ (1) Goで書いたカーネルBiscuitは優れた性能を発揮したこと
    ○ (2) カーネルヒープ枯渇問題に対処する優れたスキームを提案したこと
    ○ (3) カーネルの実装について HLL が役に立つ場合と役に立たない場合について、定性的
    に議論したこと
    ○ (4) HLLを使用したことによる性能の負債
    (tex)を測定したこと
    ○ (5) Go-vs-C の性能を、典型的なカーネルのコードで同等の条件で比較した

    View Slide

  16. 論文の構成
    1. Introduction
    ○ OSのカーネルを実装する言語の概略
    ○ Goで実装したカーネルBiscuitの性能をCのカーネルと比較
    2. Related work
    ○ 4つの観点から関連を紹介
    3. Motivation
    ○ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認
    4. Overview
    ○ Goで実装したカーネルBiscuitの概要を様々な観点から解説
    5. Garbage collection
    ○ ガベージコレクションについて

    View Slide

  17. 2. Related work
    ● 1. HLLで書かれたカーネル
    ○ Taos・Spin・Singularity・J-kernel・KaffeOS・House・Mirage unikernel・Tock (昔:
    Pilot・Lisp machine) (このSingularityはHPC用のコンテナエンジンではない)
    ● 2. 高レベルシステムプログラミング言語
    ○ 型安全・ガベージコレクション
    ○ Go・Java・C#・Cyclone (昔: Cedar・Modula-3) (最近: D・Nim(rod)・Go・Rust)
    ○ gVisor
    ■ Goで書かれたユーザースペースカーネル
    ■ コンテナのサンドボックスとして使用
    ■ Google App Engine Flexible Environmentが可能に
    ■ 参考: google/gvisor: Container Runtime Sandbox
    ● https://github.com/google/gvisor

    View Slide

  18. 2. Related work (cont'd)
    ● 3. メモリ割り当て
    ○ Rustでは、ガベージコレクションに限界があるという考えのもと、メモリの開放を部分的に
    自動化したが、複数スレッドやクロージャでデータを共有するのが面倒になった
    ○ Biscuitでは、Go 1.10で導入された並列ガベージコレクタを利用

    View Slide

  19. 2. Related work (cont'd)
    ● 4. カーネルヒープの枯渇問題
    ○ Linuxの場合
    ■ メモリ確保が失敗するまで楽観的にシステムコールを実行
    ■ 失敗した場合はエラーリカバリーを行うが、バグの温床となっている
    ■ システムコールが失敗するとほとんどのアプリは動作しなくなる
    ○ Biscuitの場合
    ■ reservation approachによりコードがシンプルに
    ■ カーネルヒープの割当は失敗しないため、複雑なエラーリカバリーを除去
    ■ 静的解析により利用メモリ量を特定できるため、システムコール発行時に必要な
    カーネルヒープメモリが確保できる
    ■ カーネルヒープメモリが枯渇しても処理が遅延するだけ
    なので、アプリケーションにはエラーが返らない

    View Slide

  20. 論文の構成
    1. Introduction
    ○ OSのカーネルを実装する言語の概略
    ○ Goで実装したカーネルBiscuitの性能をCのカーネルと比較
    2. Related work
    ○ 4つの観点から関連を紹介
    3. Motivation
    ○ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認
    4. Overview
    ○ Goで実装したカーネルBiscuitの概要を様々な観点から解説
    5. Garbage collection
    ○ ガベージコレクションについて

    View Slide

  21. 3. Motivation
    ● CとGoの利点と欠点を比較し、Goで実装する利点を確認する
    ● Cの利点
    ○ 人気の主な理由は性能向上に利用できる低レベルの操作
    ■ 特に、ポインタ演算・型強制の除去・明示的なメモリ割り当て・カスタムアロケータ
    ■ ハードウェアレジスタを簡単に操作できるなどの理由もあるが、一番の理由は性能

    View Slide

  22. 3. Motivation
    ● Cの欠点
    ○ さまざまなバグの温床
    ■ バッファオーバーランのバグ
    ■ use-after-freeバグ
    ● メモリ領域をfreeした後に誤って使用してしまうバグ
    ■ Cの型の強制の緩和によるバグ
    ○ 特にuse-after-freeバグはCのプロでも日常茶飯事
    ■ 2018年1月〜4月だけでuse-after-freeバグに関する修正が少なくとも36コミットも
    ○ 参考: CERT C コーディングスタンダード
    https://www.jpcert.or.jp/sc-rules/

    View Slide

  23. 3. Motivation (cont'd)
    ● HLLの利点
    ○ 様々な恩恵
    ■ メモリ管理から解放・メモリ解放のバグを回避・型安全性によるバグの発見・実行時
    の型付けとメソッドディスパッチ
    ■ スレッドと同期を言語がサポートしているため、
    Cより並行プログラミングがずっと簡

    ○ バグの回避
    ■ 前スライドで挙げたCの欠点を回避できる
    ■ CVEデータベースに2017年に登録されたLinuxカーネルの40個のバグの多くが、
    HLLであれば回避できた

    View Slide

  24. 3. Motivation (cont'd)
    ● HLLの欠点
    ○ 特に以下はCPU時間を消費し、遅延を引き起こす
    ■ ガベージコレクション
    ■ 型安全性のチェック
    ○ 高レベルな機能は高く付く
    ○ 言語ランタイムがメモリ管理の重要なメカニズムを隠してしまう
    ○ 抽象と安全性の代償として、実装の選択肢が狭まる

    View Slide

  25. 論文の構成
    1. Introduction
    ○ OSのカーネルを実装する言語の概略
    ○ Goで実装したカーネルBiscuitの性能をCのカーネルと比較
    2. Related work
    ○ 4つの観点から関連を紹介
    3. Motivation
    ○ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認
    4. Overview
    ○ Goで実装したカーネルBiscuitの概要を様々な観点から解説
    5. Garbage collection
    ○ ガベージコレクションについて

    View Slide

  26. 4. Overview
    ● Biscuitの主目的
    ○ HLLでカーネルを書く実用性の評価
    ● UNIX-likeなモノリシックカーネル
    ○ Cのカーネルと比較を簡単にするため
    ● 64-bit x86 ハードウェアで実際に動作
    ● Go 1.10 + 以下のアセンブリで記述
    ○ bootのためのコード
    ○ システムコールのentry/exit
    ○ 割り込みコード

    View Slide

  27. 4. Overview (cont'd)
    ● 以降、8つのポイントごとにBiscuitの設計について紹介する
    ○ ブートとGoランタイム
    ○ プロセスとカーネルのGoルーチン
    ○ 割り込み処理
    ○ マルチコアと同期
    ○ 仮想メモリ
    ○ ファイルシステム
    ○ ネットワークスタック
    ○ Biscuitの制限事項

    View Slide

  28. 4. Overview (cont'd)
    ● ブートとGoランタイム
    ○ ブートブロックがBiscuit・Goランタイム
    ・"Shim"の3つを読み込む
    ○ 通常Goランタイムは、特にメモリ割り当て
    と実行コンテキスト(Goルーチン)の制御を
    カーネルに移譲するが、今はカーネルは
    存在しないのでShimが処理を代行する
    ○ Shimの処理の大部分は初期化段階で行
    われる (Goカーネルのヒープの
    pre-allocate など)

    View Slide

  29. 4. Overview (cont'd)
    ● プロセスとカーネルのGoルーチン
    ○ BiscuitはPOSIXインターフェイス(forkや
    execなど)でユーザープロセスを提供
    ○ 1ユーザープロセス = 1アドレス空間 + 1つ
    以上のスレッド
    ○ ハードウェアレベルのページ保護でユー
    ザープロセスを隔離
    ○ ユーザープログラムはどんな言語でも記
    述OK (論文ではCとC++のみを使用した
    not Go)

    View Slide

  30. 4. Overview (cont'd)
    ● プロセスとカーネルのGoルーチン
    (cont’d)
    ○ Biscuitはユーザースレッドごとに対応する
    Goルーチンを1つ生成
    ○ ユーザースレッドのシステムコールと、
    ページフォルトや例外のハンドラを実行す

    ○ (注釈: 「Goルーチン」とはGo特有のスレッ
    ドのこと。この論文では、Goルーチンとは、
    カーネル内で実行されるスレッドのみを指
    す)

    View Slide

  31. 4. Overview (cont'd)
    ● プロセスとカーネルのGoルーチン
    (cont’d)
    ○ Biscuitランタイムがユーザープロセスに対
    応するカーネル内のGoルーチンをスケ
    ジューリングする
    ○ タイマー割り込みを用いてユーザースレッ
    ドを先制して(pre-emptively)スイッチ
    ○ Goコンパイラが生成した、カーネル
    Go
    ルーチンを先制(pre-emption)チェックす
    るコードを使用

    View Slide

  32. 4. Overview (cont'd)
    ● 割り込み処理
    ○ Biscuit のデバイス割り込みハンドラが関連するデバイスドライバの Go ルーチンを「実行
    可能 (runnable)」とマークして return するだけ (先行研究による)
    ■ Goルーチンのコンテキストスイッチなど、センシティブな操作中に Go ランタイムが止
    められないため、それ以上のことをしようとするとデッドロックのリスクが生じる
    (?)
    ○ ユーザー空間から渡されたシステムコールやフォールトのハンドラは任意の
    Goコードを実
    行できる。Biscuitはユーザースレッドに対応するGoルーチンのコンテキストでGoコードを
    実行する

    View Slide

  33. 4. Overview (cont'd)
    ● マルチコアと同期
    ○ Biscuit はマルチコアハードウェア上で並列に実行される
    ■ Go の mutex がデータ構造を保護し、
    ■ Go のチャネルと条件変数で同期を実現する
    ○ 細かい粒度でロックが行われるため、別のコアのスレッドのシステムコールが多くのシ
    チュエーションで並列に実行される
    ■ 例: 異なるファイル・パイプ・ソケットに対する操作、
    別プロセス上にフォークして実行される場合など
    ○ performance-critical なコードでは、後述の read-lock-free ルックアップを活用している

    View Slide

  34. 4. Overview (cont'd)
    ● 仮想メモリ
    ○ ページテーブルハードウェアを活用することで以下の機能を実現
    ■ zero-fill-on-demand なメモリ割り当て
    ■ copy-on-write `fork()`
    ■ プロセスがページフォルトしたときのみ PTE (Page Table Entry) を populate する、
    ファイルの lazy mapping の機能 (例: `exec()`などの実行時)
    ■ `mmap()`

    View Slide

  35. 4. Overview (cont'd)
    ● 仮想メモリ (cont'd)
    ○ 参考資料:
    ■ PTE とは?
    ● 情報科学類「オペレーティングシステム II」メモリ管理
    http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2018/2019-01-17/inde
    x.html
    ■ copy-on-write とは?
    ● コピーオンライト - Wikipedia -
    https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%94%E3%83%BC%E3
    %82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%88

    View Slide

  36. 4. Overview (cont'd)
    ● ファイルシステム
    ○ 主要な POSIX ファイルシステムコールに対応したファイルシステムを実装
    ○ 3種類のキャッシュを実装
    ■ ファイル名の lookup キャッシュ
    ■ vnode cache
    ■ ブロックキャッシュ
    ○ 各 vnode を mutex でガードし、read-lock-free のディレクトリキャッシュの最初のルック
    アップでパスを解決する
    ○ 各ファイルシステムコールをトランザクションとして処理し、コミットのジャーナルは自動的
    にディスクに反映される

    View Slide

  37. 4. Overview (cont'd)
    ● ファイルシステム (cont'd)

    ○ AHCI ディスクドライバを利用して以下の機能を実行可能
    ■ DMA (ダイレクトメモリアクセス) の使用
    ■ command coalescing (コマンドの結合)?
    ■ ネイティブコマンドキュー
    ■ MSI 割り込み?

    View Slide

  38. 4. Overview (cont'd)
    ● ネットワークスタック
    ○ 以下のネットワーク関連スタックを実装
    ■ TCP/IP スタック
    ■ Intel PCI-Express Ethernet NIC に対するドライバ (Goで実装)
    ■ DMA および MSI 割り込みを使用するドライバ
    ■ POSIX ソケットを提供するシステムコール API

    View Slide

  39. 4. Overview (cont'd)
    ● Biscuitの制限事項
    ○ 多くのCプログラムがソースコードそのままで実行できるが、研究用のプロトタイプなので
    多くの機能が不足している
    ○ Goランタイムのスケジューラに依存しているため、優先度付きスケジューリングに対応し
    ない
    ○ 少数コアに最適化されているため、多数コアマシンや
    NUMAアーキテクチャには適さない
    ○ ディスクへの swap out や page out を行わない
    ○ reverse mapping (?) を実装していない
    ○ セキュリティ機能の多くを提供しない
    ■ ユーザー・ACL (Access Control List)・
    アドレス空間ランダム化など

    View Slide

  40. リンク集
    ● mit-pdos/biscuit: Biscuit research OS - https://github.com/mit-pdos/biscuit
    ○ Biscuit のソースコード

    View Slide

  41. The benefits and costs of writing a
    POSIX kernel in a high-level
    language
    2019-07-17 SSチーム論文読み会
    HPCS Lab. SSチーム M1 高橋 宗史
    Cody Cutler, M. Frans Kaashoek, and Robert T.
    Morris, MIT CSAIL
    出典: https://blog.golang.org/gopher
    (第2回)

    View Slide

  42. 注意事項
    ● 今日紹介するのは、§5. Garbage collectionと §6. Avoiding heap exhaustion だ
    けです
    ● スライドは 27 枚 (前回は 40 枚)
    ● §7 - §10 は…?

    View Slide

  43. 5. Garbage collection
    ● Goのコレクタの設計
    ○ Go1.10から並行・並列
    mark-and-sweepGC が実装された
    ○ これにより、stop-the-world による停止
    が最小化されるようになった
    出典: Tracing garbage collection - Wikipedia
    普通の mark-and-sweep の例

    View Slide

  44. 5. Garbage collection
    ● 並列GCの動作のしくみ
    ○ メモリ空き容量がしきい値以下になると
    GC
    が有効になり以下を実行
    ■ 1)ポインタのトレース
    ■ 2)使用済みオブジェクトのマーク
    ○ さらに、実行中にGC以外の通常の命令を
    インターリーブさせる
    ○ トレース済みのオブジェクトに対して、コン
    パイラが「writeバリア」を生成するため、イ
    ンターリーブ中にポインタが新しくできても
    区別してトレースが可能
    出典: Tracing garbage collection - Wikipedia
    普通の mark-and-sweep の例

    View Slide

  45. 5. Garbage collection (cont’d)
    ● 「stop-the-world」pauseが発生する2つのタイミング
    ○ 1) 最初に全コアがwriteバリアを張るとき
    ○ 2) 最後にすべてのオブジェクトがマークされたかをチェックすると

    ○ これらによる停止時間は数 10 μs 程度
    ● CPUの消費時間
    ○ live オブジェクトの数に比例
    ○ GC の実行間隔に反比例
    ■ 実行間隔は潤沢なRAMをヒープに割り当てると大きくなる
    ○ 測定結果は §8.5 で紹介する
    出典: Craftprokits Clock Insert 69mm | Axminster Tools & Machinery

    View Slide

  46. 5. Garbage collection (cont’d)
    ● Go ランタイムのヒープメモリ
    ○ Biscuit では RAM の 1/32 を固定で割り当てている
    ● Go の GC
    ○ デフォルトでは、空き容量が 1/2 以下になるとヒープが自動拡
    張されるが、Biscuit ではこの機能を無効化している
    ○ 次の §6 では、ヒープメモリの空き容量が枯渇してきたときに、
    Biscuit がどのように対処しているかを詳しく解説する
    出典: Craftprokits Clock Insert 69mm | Axminster Tools & Machinery

    View Slide

  47. 6. Avoiding heap exhaustion
    ● Biscuit はカーネルデータによるヒープの枯渇を回避する必要がある
    ● 様々なカーネルが問題に取り組んできた難しい領域 (§2)
    ● このセクションの目次
    ○ 6.1 Approach: reservastion
    ○ 6.2 How Biscuit reserves
    ○ 6.3 Static analysis to find s
    ■ 6.3.1 Basic MaxLive operation
    ■ 6.3.2 Handling loops
    ■ 6.3.3 Kernel threads
    ■ 6.3.4 Killer thread
    ○ 6.4 Limitations
    ○ 6.5 Heap exhaustion summary
    出典: 主記憶装置 - Wikipedia

    View Slide

  48. 6.1 Approach: reservation
    ● Biscuit では、カーネルヒープが枯渇しても動作し続
    けるように設計されている
    ○ カーネルリソースを過剰に消費する悪い市民 ("bad
    citizen" process) が発見されると kill する
    ○ ヒープの空きを確保することで良い市民 ("good
    citizen") が動作し続けられるようにする
    出典: Smushie Ranch: Gunther taken out by killer gopher

    View Slide

  49. 6.1 Approach: reservation (cont’d)
    ● ヒープ枯渇の対処の3つのアプローチ
    ○ 1) キャッシュと soft state をパージする
    ○ 2) システムコールの実行前に必要なヒープが確保で
    きるまで wait する
    ○ 3) killer thread がヒープを過剰消費している悪い市
    民を kill する
    出典: Smushie Ranch: Gunther taken out by killer gopher

    View Slide

  50. 6.1 Approach: reservation (cont’d)
    ● このアプローチの利点
    ○ ヒープ枯渇によるシステムコールの失敗の可能性が
    0、アプリケーションがクラッシュしない
    ○ (基本的には)カーネル内ではヒープ・アロケーション
    の失敗に対処する必要がない
    ○ システムコールの途中で失敗からリカバリーするコー
    ドが不要になる
    出典: Smushie Ranch: Gunther taken out by killer gopher

    View Slide

  51. 6.1 Approach: reservation (cont’d)
    ● killer による良い市民/悪い市民の判別問題
    ○ たとえば killer が init を kill したら困る
    ○ Kernel が安全にリソースを取り消す (gracefully
    revoke) 手段が POSIX で定義されていないせいで、
    特定のメモリ不足の状況下で適切な解決方法が見つ
    からない
    出典: Smushie Ranch: Gunther taken out by killer gopher

    View Slide

  52. 6.2 How Biscuit reserves
    ● システムコールで必要なカーネルヒープメモリ容量について考える
    ○ M: Biscuit が固定で確保するカーネルヒープメモリの容量
    ○ s: 全データがヒープメモリを同時に使用する最大値
    ■ システムコールの実行時に計算される
    ○ 理想的には、利用可能な領域は M - s で求まる
    ■ しかし、使用中の領域の GC 後の正確な値が不明!
    使用中の領域
    必要なヒープメモリの最
    大値 s
    カーネルヒープメモリ M
    GC後の正確な値??

    View Slide

  53. 6.2 How Biscuit reserves (cont’d)
    ● 使用中の領域を多く見積もるために g, c, n という3つのパラメータを管理
    ○ g: 前回のGCで mark されたデータ (= また使用中の可能性が残っているデータ
    )
    ○ c: 完了済みのシステムコールで予約されたデータの合計
    ○ n: 実行中で未完了のシステムコールで予約されたデータの合計
    ● L をこれらの合計として次のようにおく。
    ○ L = g + c + n
    使用中の領域
    必要なヒープメモリの最
    大値 s
    カーネルヒープメモリ M
    GC後の正確な値??

    View Slide

  54. 6.2 How Biscuit reserves (cont’d)
    ● システムコールの開始時 reserve(s)
    ● スレッドが L + s < M をチェック
    ○ 使用済みL + 新規メモリs < 合計M
    ● L + s < M の場合 (メモリが足りる)
    ○ n += s して、新しいシステムコール用
    にメモリを確保する
    ● otherwise (メモリが足りない)
    ○ killer を起こして、悪い市民を殺し終
    わるまで待機する
    // カーネルヒープメモリの予約
    reserve(s):
    g := 最終 GC 時の有効バイト数
    c := 使用済みバイト数
    n := 予約済みバイト数
    L := g + c + n
    M = ヒープメモリのバイト数
    if L + s < M:
    予約済みバイト数 += s
    else:
    killer スレッドを起こす
    killer が悪い市民を殺すまで待機する

    View Slide

  55. // カーネルヒープメモリの開放
    release(s):
    a := システムコールで割り当てられたバイト数
    if a < s:
    使用済みバイト数 += a
    else:
    使用済みバイト数 += s
    予約済みバイト数 -= s
    ・c = 使用済みバイト数
    ・n = 予約済みバイト数
    6.2 How Biscuit reserves (cont’d)
    ● システムコールの終了時 release(s)
    ● システムコールが実際に割り当てられ
    たメモリの合計 a の値を計算
    ● a < s の場合
    ○ c += a
    ● a ≧ s の場合
    ○ c += s
    ● 最後に
    ○ n -= s

    View Slide

  56. 6.3 Static analysis to find s
    ● MaxLive というツールを作成した
    ○ Biscuitのソースコード+使用しているGoパッケージを解析し、
    ○ システムコールが必要とするヒープメモリ量 s を計算する
    ● 難しかった点1
    ○ 多くのシステムコールはメモリを一時的にしか割り当てない
    ○ そのため、割当メモリが使用されなくなったことを静的に検出するのが大変
    ● 難しかった点2
    ○ 上限が不定のループの解析
    ● 難しかった点3
    ○ システムコールと無関係のバックグラウンドのカーネル
    アクティビティによるメモリ割り当て量の特定

    View Slide

  57. 6.3 Static analysis to find s (cont’d)
    ● イベントハンドラスタイルというカーネルの特性を活用
    ○ 様々な作業が実行されたあと、return するというスタイル
    ○ システムコールもこのように実装されている
    ● 解析しやすいように追加の修正も行った
    ○ 再帰がなくなるように数カ所の関数を修正
    ○ Goパッケージを一部修正 (time と fmt)

    View Slide

  58. 6.3.1 Basic MaxLive operation
    ● MaxLive
    ○ コールグラフをチェック
    ■ システムコールが実行する可能性のあるすべてのメモリ割り当てを検出
    ● Go の ssa および callgraph パッケージを活用
    ○ エスケープとポインタ解析を利用
    ■ エスケープしない := コールグラフでそれ以上先まで呼び出しがないこと
    ● Go の pointer パッケージを活用

    View Slide

  59. 6.3.1 Basic MaxLive operation (cont'd)
    ● 参考リンク
    ○ callgraph - GoDoc - https://godoc.org/golang.org/x/tools/go/callgraph
    ○ TrueFurby/go-callvis: Visualize call graph of a Go program using dot format. -
    https://github.com/TrueFurby/go-callvis
    ○ ssa - GoDoc - https://godoc.org/golang.org/x/tools/go/ssa
    ■ ssa = 静的単一代入
    ○ pointer - GoDoc - https://godoc.org/golang.org/x/tools/go/pointer

    View Slide

  60. 6.3.1 Basic MaxLive operation (cont'd)
    ● MaxLive ではいくつかの割り当てを特別扱いしている
    ○ go (goroutine を作る)
    ■ カーネルスタックサイズの最大値を割り当てる
    ○ defer
    ■ 普通の呼び出しだが、SSA では object として表現されるた
    め、メモリ割り当てとみなす
    ○ map & slice への insert
    ■ 一般に挿入前のサイズが検知できないため、メモリ割り当て
    料が予測できない
    ■ この問題に対処するため、map と slice の最大値のアノテー
    ションを
    Biscuit のソースコード70箇所に追加した

    View Slide

  61. 6.3.2 Handling loops
    ● MaxLive が実用的なループ回数を特定できない箇所
    ○ ソースの78箇所にアノテーションを追加した
    ○ さらに20箇所はアノテーションが困難
    ■ 例1) wakeup race を扱う retry が必要な poll() システムコール
    ■ 例2) path のルックアップをしながらディレクトリのデータブロックのイテレーションを
    する部分
    ■ 例3) ユーザーバッファのページをイテレーションする必要がある write() システム
    コール

    View Slide

  62. 6.3.2 Handling loops (cont'd)
    ● 対処方法: deep reservation
    ○ 1回のイテレーションに必要なだけのヒープのみ reserve する
    ○ メモリが確保できなかった場合、一度 abort してメモリ確保を待機、システムコールの最
    初からリトライする
    ○ exec() と rename() のみ、割り当て失敗時に undo を行うコードを書かざるを得なかった
    が、他のシステムコールはリカバリコードは不要になった

    View Slide

  63. 6.3.2 Handling loops (cont'd)
    ● exit()、fork()、exec() の問題
    ○ 多数のファイルディスクリプタを close() する可能性がある
    ○ ファイルの close 時にはファイルシステムの更新の可能性あり
    ■ ファイルシステムが割り当てメモリに書き込み
    ■ ファイルシステムキャッシュにエントリを作成
    ○ 多数のファイルを close → 1回の exit() で大きなヒープメモリを使用するかも
    ● 対処方法
    ○ MaxLive で close() の1回の呼び出しでのメモリ使用量の解析
    ○ さらに手動で、「close() 呼び出し後に割り当てを破棄すること」
    OR「破棄可能なキャッシュエントリで行われること」を保証
    ○ 必要ヒープメモリを close() 1回分のヒープのみに修正

    View Slide

  64. 6.3.2 Handling loops (cont'd)
    ● 最終的な成果
    ○ rename() と fork() 以外のすべてのシステムコールのヒープ使用量の上限を
    500kB 以下に抑えることに成功
    ■ rename() = 1MB
    ■ fork() = 641kB
    ○ MaxLive を使用して手動で解析が必要だったのは close() だけ
    ■ 残りはすべて自動で解析ができた!

    View Slide

  65. 6.3.3 Kernel threads
    ● カーネル領域
    ○ 長時間実行が継続されるカーネルスレッドそれぞれに対しても、カーネルヒープの
    reservation が必要
    ■ exit() はkiller スレッドがプロセスを kill するときに動作する必要があるため、
    exit()
    が依存しているカーネルスレッドは、自分が持っているヒープを勝手に解放できない
    ○ 例) exit はファイルやブロックを削除するため、ファイルシステムロギンススレッドが必要

    View Slide

  66. 6.3.4 Killer thread
    ● システムコールメモリ割り当て失敗時に目覚める
    a. ガベージコレクションの実行
    b. 不十分な場合、キャッシュなどを開放し、再度、ガベージ
    コレクションを実行
    c. それでも不十分な場合
    ■ メモリ領域・ファイルディスクリプタ・スレッドを最も
    多く持っているプロセスを探し出す
    ■ このプロセスを「真に悪い市民 (genuine bad
    citizen)」であると推定する
    ■ その市民を kill する
    ■ 再度、ガベージコレクションを実行
    d. メモリ割り当てに満足したら、眠る

    View Slide

  67. 6.4 Limitations
    ● 1) Biscuit のヒープ枯渇問題への対処には、メモリ枯渇時にGCが正常に動作する
    ことが必要
    ○ Goのガベージコレクタは動作時に新規メモリ割り当てが必要なため問題
    ○ まだ未実装だが、Biscuit はこの状況から回復可能 (ただし、遅くなることが予想される
    )
    ○ そもそもこの状況は稀であり、実験によれば、
    GC は 最大でも 0.8% のヒープメモリを
    work stack に割り当てれば十分
    ● 2) Go の GC はオブジェクトを移動させないため、フラグメンテーションが削減されな

    ○ MaxLive でシステムコール時のヒープの割り当て時にオブジェクトの種類ごとに s を推定
    することでリスクを減らせるが、まだ未実装

    View Slide

  68. 6.5 Heap exhaustion summary
    ● Linux のアイデアを採用した
    ○ killer thread
    ○ waiting & retry
    ● Linux ではメモリ割り当ての段階でチェックするのに対して、Biscuit では、システム
    コールの呼び出し前に都度、チェックするように変更した
    ● これにより、リカバリーコードを減らすことができ、メモリ割り当てのデッドロックの回
    避が可能になった
    ● さらに、Goの静的な解析を利用することで、この手法の自動化も可能になった

    View Slide

  69. §6 Fin.

    View Slide