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
  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
  3. 論文の構成 1. Introduction ◦ OSのカーネルを実装する言語の概略 ◦ Goで実装したカーネルBiscuitの性能をCのカーネルと比較 2. Related work

    ◦ 4つの観点から関連を紹介 3. Motivation ◦ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認 4. Overview ◦ Goで実装したカーネルBiscuitの概要を様々な観点から解説
  4. 論文の構成 (cont'd) ↓以下の紹介は次回以降です 5. Garbage collection ◦ ガベージコレクションについて 6. Avoiding

    heap exhaustion ◦ ヒープの枯渇問題に対処した話 7. Implementation ◦ 実装したコードの説明 8. Evaluation ◦ 詳細な評価結果 9. Discussion and future work 10. Conclusions
  5. Abstract • ガベージコレクションを持つハイレベル言語(High Level Language; HLL)を使用し て、モノリシックなPOSIXスタイルのカーネルを実装 ◦ 目的: 実行コスト・実装のチャレンジ・プログラマビリティ・安全性の検証

    • 論文では、十分にPOSIXを実装したカーネルBiscuitをGoで実装 ◦ POSIX: 仮想メモリ・`nmap`・TCP/IPソケット・ログファイルシステム・`poll`など ◦ GoのHLL機能を多用: クロージャ・チャネル・マップ・インターフェイス・ガベージコレクショ ン付きヒープ割当 ◦ 特に、カーネルのヒープメモリ枯渇問題 への対処が 最もchallengingなpuzzleだった
  6. Abstract (cont'd) • 評価: カーネルインテンシブなベンチマークを実施 ◦ NGINX・Redisなどを実行 ◦ 評価結果 ▪

    HLL機能 (主にガベージコレクションとスレッドスタックの拡張のチェック )によるカー ネルCPU時間のfraction (断片化?) = 最大13%増加 ▪ NGINXによるGC関連の最大の停止時間 = 115 μs ▪ クライアントから見たGCのディレイの合計の最大値 = 600 μs ▪ システムコール・ページフォルト・コンテキストスイッチの条件をほぼ同じにして CとGo のカーネルを比較した結果、5% - 15% Goの方が遅かった
  7. 論文の構成 1. Introduction ◦ OSのカーネルを実装する言語の概略 ◦ Goで実装したカーネルBiscuitの性能をCのカーネルと比較 2. Related work

    ◦ 4つの観点から関連を紹介 3. Motivation ◦ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認 4. Overview ◦ Goで実装したカーネルBiscuitの概要を様々な観点から解説 5. Garbage collection ◦ ガベージコレクションについて
  8. 1. Introduction • 現在の実用的なOSのカーネルはすべてCで書かれている ◦ Linux, macOS, Windows など ◦

    メモリへのアクセス性とメモリ管理の性能の高さ ◦ 一方でメモリ管理はバグの温床にもなっている (2017年の50個のバグの原因) • HLL (High-Level Language) ◦ 抽象化とメモリ安全性を提供 ◦ 新しいアイデアの検証に利用 ◦ 一方で実使用に耐える高性能なカーネルとしての使用は懐疑的に考えられている • しかし、CをHLLでリライトするのは無意味かもしれないが、新しく使用すべき言語を 検証するのは有意義だと考えられる
  9. 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/
  10. 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人
  11. 1. Introduction (cont'd) • 設計 ◦ 基本的には伝統的なモノリシックの POSIX/Unixカーネルと同じようなデザイン ◦ Biscuitの設計で優れている点

    ▪ カーネルヒープ枯渇問題に対処するしくみ • HLLの機能を利用した静的解析のおかげで、複雑なメモリ割り当ての失敗や デッドロックからのリカバリが可能になった
  12. 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
  13. 1. Introduction (cont'd) • 本研究の貢献 ◦ (1) Goで書いたカーネルBiscuitは優れた性能を発揮したこと ◦ (2)

    カーネルヒープ枯渇問題に対処する優れたスキームを提案したこと ◦ (3) カーネルの実装について HLL が役に立つ場合と役に立たない場合について、定性的 に議論したこと ◦ (4) HLLを使用したことによる性能の負債 (tex)を測定したこと ◦ (5) Go-vs-C の性能を、典型的なカーネルのコードで同等の条件で比較した
  14. 論文の構成 1. Introduction ◦ OSのカーネルを実装する言語の概略 ◦ Goで実装したカーネルBiscuitの性能をCのカーネルと比較 2. Related work

    ◦ 4つの観点から関連を紹介 3. Motivation ◦ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認 4. Overview ◦ Goで実装したカーネルBiscuitの概要を様々な観点から解説 5. Garbage collection ◦ ガベージコレクションについて
  15. 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
  16. 2. Related work (cont'd) • 4. カーネルヒープの枯渇問題 ◦ Linuxの場合 ▪

    メモリ確保が失敗するまで楽観的にシステムコールを実行 ▪ 失敗した場合はエラーリカバリーを行うが、バグの温床となっている ▪ システムコールが失敗するとほとんどのアプリは動作しなくなる ◦ Biscuitの場合 ▪ reservation approachによりコードがシンプルに ▪ カーネルヒープの割当は失敗しないため、複雑なエラーリカバリーを除去 ▪ 静的解析により利用メモリ量を特定できるため、システムコール発行時に必要な カーネルヒープメモリが確保できる ▪ カーネルヒープメモリが枯渇しても処理が遅延するだけ なので、アプリケーションにはエラーが返らない
  17. 論文の構成 1. Introduction ◦ OSのカーネルを実装する言語の概略 ◦ Goで実装したカーネルBiscuitの性能をCのカーネルと比較 2. Related work

    ◦ 4つの観点から関連を紹介 3. Motivation ◦ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認 4. Overview ◦ Goで実装したカーネルBiscuitの概要を様々な観点から解説 5. Garbage collection ◦ ガベージコレクションについて
  18. 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/
  19. 3. Motivation (cont'd) • HLLの利点 ◦ 様々な恩恵 ▪ メモリ管理から解放・メモリ解放のバグを回避・型安全性によるバグの発見・実行時 の型付けとメソッドディスパッチ

    ▪ スレッドと同期を言語がサポートしているため、 Cより並行プログラミングがずっと簡 単 ◦ バグの回避 ▪ 前スライドで挙げたCの欠点を回避できる ▪ CVEデータベースに2017年に登録されたLinuxカーネルの40個のバグの多くが、 HLLであれば回避できた
  20. 3. Motivation (cont'd) • HLLの欠点 ◦ 特に以下はCPU時間を消費し、遅延を引き起こす ▪ ガベージコレクション ▪

    型安全性のチェック ◦ 高レベルな機能は高く付く ◦ 言語ランタイムがメモリ管理の重要なメカニズムを隠してしまう ◦ 抽象と安全性の代償として、実装の選択肢が狭まる
  21. 論文の構成 1. Introduction ◦ OSのカーネルを実装する言語の概略 ◦ Goで実装したカーネルBiscuitの性能をCのカーネルと比較 2. Related work

    ◦ 4つの観点から関連を紹介 3. Motivation ◦ CとGo(高レベル言語)の利点と欠点を比較し、Goでカーネルを書く価値を確認 4. Overview ◦ Goで実装したカーネルBiscuitの概要を様々な観点から解説 5. Garbage collection ◦ ガベージコレクションについて
  22. 4. Overview • Biscuitの主目的 ◦ HLLでカーネルを書く実用性の評価 • UNIX-likeなモノリシックカーネル ◦ Cのカーネルと比較を簡単にするため

    • 64-bit x86 ハードウェアで実際に動作 • Go 1.10 + 以下のアセンブリで記述 ◦ bootのためのコード ◦ システムコールのentry/exit ◦ 割り込みコード
  23. 4. Overview (cont'd) • 以降、8つのポイントごとにBiscuitの設計について紹介する ◦ ブートとGoランタイム ◦ プロセスとカーネルのGoルーチン ◦

    割り込み処理 ◦ マルチコアと同期 ◦ 仮想メモリ ◦ ファイルシステム ◦ ネットワークスタック ◦ Biscuitの制限事項
  24. 4. Overview (cont'd) • ブートとGoランタイム ◦ ブートブロックがBiscuit・Goランタイム ・"Shim"の3つを読み込む ◦ 通常Goランタイムは、特にメモリ割り当て

    と実行コンテキスト(Goルーチン)の制御を カーネルに移譲するが、今はカーネルは 存在しないのでShimが処理を代行する ◦ Shimの処理の大部分は初期化段階で行 われる (Goカーネルのヒープの pre-allocate など)
  25. 4. Overview (cont'd) • プロセスとカーネルのGoルーチン ◦ BiscuitはPOSIXインターフェイス(forkや execなど)でユーザープロセスを提供 ◦ 1ユーザープロセス

    = 1アドレス空間 + 1つ 以上のスレッド ◦ ハードウェアレベルのページ保護でユー ザープロセスを隔離 ◦ ユーザープログラムはどんな言語でも記 述OK (論文ではCとC++のみを使用した not Go)
  26. 4. Overview (cont'd) • プロセスとカーネルのGoルーチン (cont’d) ◦ Biscuitはユーザースレッドごとに対応する Goルーチンを1つ生成 ◦

    ユーザースレッドのシステムコールと、 ページフォルトや例外のハンドラを実行す る ◦ (注釈: 「Goルーチン」とはGo特有のスレッ ドのこと。この論文では、Goルーチンとは、 カーネル内で実行されるスレッドのみを指 す)
  27. 4. Overview (cont'd) • プロセスとカーネルのGoルーチン (cont’d) ◦ Biscuitランタイムがユーザープロセスに対 応するカーネル内のGoルーチンをスケ ジューリングする

    ◦ タイマー割り込みを用いてユーザースレッ ドを先制して(pre-emptively)スイッチ ◦ Goコンパイラが生成した、カーネル Go ルーチンを先制(pre-emption)チェックす るコードを使用
  28. 4. Overview (cont'd) • 割り込み処理 ◦ Biscuit のデバイス割り込みハンドラが関連するデバイスドライバの Go ルーチンを「実行

    可能 (runnable)」とマークして return するだけ (先行研究による) ▪ Goルーチンのコンテキストスイッチなど、センシティブな操作中に Go ランタイムが止 められないため、それ以上のことをしようとするとデッドロックのリスクが生じる (?) ◦ ユーザー空間から渡されたシステムコールやフォールトのハンドラは任意の Goコードを実 行できる。Biscuitはユーザースレッドに対応するGoルーチンのコンテキストでGoコードを 実行する
  29. 4. Overview (cont'd) • マルチコアと同期 ◦ Biscuit はマルチコアハードウェア上で並列に実行される ▪ Go

    の mutex がデータ構造を保護し、 ▪ Go のチャネルと条件変数で同期を実現する ◦ 細かい粒度でロックが行われるため、別のコアのスレッドのシステムコールが多くのシ チュエーションで並列に実行される ▪ 例: 異なるファイル・パイプ・ソケットに対する操作、 別プロセス上にフォークして実行される場合など ◦ performance-critical なコードでは、後述の read-lock-free ルックアップを活用している
  30. 4. Overview (cont'd) • 仮想メモリ ◦ ページテーブルハードウェアを活用することで以下の機能を実現 ▪ zero-fill-on-demand なメモリ割り当て

    ▪ copy-on-write `fork()` ▪ プロセスがページフォルトしたときのみ PTE (Page Table Entry) を populate する、 ファイルの lazy mapping の機能 (例: `exec()`などの実行時) ▪ `mmap()`
  31. 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
  32. 4. Overview (cont'd) • ファイルシステム ◦ 主要な POSIX ファイルシステムコールに対応したファイルシステムを実装 ◦

    3種類のキャッシュを実装 ▪ ファイル名の lookup キャッシュ ▪ vnode cache ▪ ブロックキャッシュ ◦ 各 vnode を mutex でガードし、read-lock-free のディレクトリキャッシュの最初のルック アップでパスを解決する ◦ 各ファイルシステムコールをトランザクションとして処理し、コミットのジャーナルは自動的 にディスクに反映される
  33. 4. Overview (cont'd) • ファイルシステム (cont'd) ◦ ◦ AHCI ディスクドライバを利用して以下の機能を実行可能

    ▪ DMA (ダイレクトメモリアクセス) の使用 ▪ command coalescing (コマンドの結合)? ▪ ネイティブコマンドキュー ▪ MSI 割り込み?
  34. 4. Overview (cont'd) • ネットワークスタック ◦ 以下のネットワーク関連スタックを実装 ▪ TCP/IP スタック

    ▪ Intel PCI-Express Ethernet NIC に対するドライバ (Goで実装) ▪ DMA および MSI 割り込みを使用するドライバ ▪ POSIX ソケットを提供するシステムコール API
  35. 4. Overview (cont'd) • Biscuitの制限事項 ◦ 多くのCプログラムがソースコードそのままで実行できるが、研究用のプロトタイプなので 多くの機能が不足している ◦ Goランタイムのスケジューラに依存しているため、優先度付きスケジューリングに対応し

    ない ◦ 少数コアに最適化されているため、多数コアマシンや NUMAアーキテクチャには適さない ◦ ディスクへの swap out や page out を行わない ◦ reverse mapping (?) を実装していない ◦ セキュリティ機能の多くを提供しない ▪ ユーザー・ACL (Access Control List)・ アドレス空間ランダム化など
  36. 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回)
  37. 注意事項 • 今日紹介するのは、§5. Garbage collectionと §6. Avoiding heap exhaustion だ

    けです • スライドは 27 枚 (前回は 40 枚) • §7 - §10 は…?
  38. 5. Garbage collection • Goのコレクタの設計 ◦ Go1.10から並行・並列 mark-and-sweepGC が実装された ◦

    これにより、stop-the-world による停止 が最小化されるようになった 出典: Tracing garbage collection - Wikipedia 普通の mark-and-sweep の例
  39. 5. Garbage collection • 並列GCの動作のしくみ ◦ メモリ空き容量がしきい値以下になると GC が有効になり以下を実行 ▪

    1)ポインタのトレース ▪ 2)使用済みオブジェクトのマーク ◦ さらに、実行中にGC以外の通常の命令を インターリーブさせる ◦ トレース済みのオブジェクトに対して、コン パイラが「writeバリア」を生成するため、イ ンターリーブ中にポインタが新しくできても 区別してトレースが可能 出典: Tracing garbage collection - Wikipedia 普通の mark-and-sweep の例
  40. 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
  41. 5. Garbage collection (cont’d) • Go ランタイムのヒープメモリ ◦ Biscuit では

    RAM の 1/32 を固定で割り当てている • Go の GC ◦ デフォルトでは、空き容量が 1/2 以下になるとヒープが自動拡 張されるが、Biscuit ではこの機能を無効化している ◦ 次の §6 では、ヒープメモリの空き容量が枯渇してきたときに、 Biscuit がどのように対処しているかを詳しく解説する 出典: Craftprokits Clock Insert 69mm | Axminster Tools & Machinery
  42. 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
  43. 6.1 Approach: reservation • Biscuit では、カーネルヒープが枯渇しても動作し続 けるように設計されている ◦ カーネルリソースを過剰に消費する悪い市民 ("bad

    citizen" process) が発見されると kill する ◦ ヒープの空きを確保することで良い市民 ("good citizen") が動作し続けられるようにする 出典: Smushie Ranch: Gunther taken out by killer gopher
  44. 6.1 Approach: reservation (cont’d) • ヒープ枯渇の対処の3つのアプローチ ◦ 1) キャッシュと soft

    state をパージする ◦ 2) システムコールの実行前に必要なヒープが確保で きるまで wait する ◦ 3) killer thread がヒープを過剰消費している悪い市 民を kill する 出典: Smushie Ranch: Gunther taken out by killer gopher
  45. 6.1 Approach: reservation (cont’d) • このアプローチの利点 ◦ ヒープ枯渇によるシステムコールの失敗の可能性が 0、アプリケーションがクラッシュしない ◦

    (基本的には)カーネル内ではヒープ・アロケーション の失敗に対処する必要がない ◦ システムコールの途中で失敗からリカバリーするコー ドが不要になる 出典: Smushie Ranch: Gunther taken out by killer gopher
  46. 6.1 Approach: reservation (cont’d) • killer による良い市民/悪い市民の判別問題 ◦ たとえば killer

    が init を kill したら困る ◦ Kernel が安全にリソースを取り消す (gracefully revoke) 手段が POSIX で定義されていないせいで、 特定のメモリ不足の状況下で適切な解決方法が見つ からない 出典: Smushie Ranch: Gunther taken out by killer gopher
  47. 6.2 How Biscuit reserves • システムコールで必要なカーネルヒープメモリ容量について考える ◦ M: Biscuit が固定で確保するカーネルヒープメモリの容量

    ◦ s: 全データがヒープメモリを同時に使用する最大値 ▪ システムコールの実行時に計算される ◦ 理想的には、利用可能な領域は M - s で求まる ▪ しかし、使用中の領域の GC 後の正確な値が不明! 使用中の領域 必要なヒープメモリの最 大値 s カーネルヒープメモリ M GC後の正確な値??
  48. 6.2 How Biscuit reserves (cont’d) • 使用中の領域を多く見積もるために g, c, n

    という3つのパラメータを管理 ◦ g: 前回のGCで mark されたデータ (= また使用中の可能性が残っているデータ ) ◦ c: 完了済みのシステムコールで予約されたデータの合計 ◦ n: 実行中で未完了のシステムコールで予約されたデータの合計 • L をこれらの合計として次のようにおく。 ◦ L = g + c + n 使用中の領域 必要なヒープメモリの最 大値 s カーネルヒープメモリ M GC後の正確な値??
  49. 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 が悪い市民を殺すまで待機する
  50. // カーネルヒープメモリの開放 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
  51. 6.3 Static analysis to find s • MaxLive というツールを作成した ◦

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

    様々な作業が実行されたあと、return するというスタイル ◦ システムコールもこのように実装されている • 解析しやすいように追加の修正も行った ◦ 再帰がなくなるように数カ所の関数を修正 ◦ Goパッケージを一部修正 (time と fmt)
  53. 6.3.1 Basic MaxLive operation • MaxLive ◦ コールグラフをチェック ▪ システムコールが実行する可能性のあるすべてのメモリ割り当てを検出

    • Go の ssa および callgraph パッケージを活用 ◦ エスケープとポインタ解析を利用 ▪ エスケープしない := コールグラフでそれ以上先まで呼び出しがないこと • Go の pointer パッケージを活用
  54. 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
  55. 6.3.1 Basic MaxLive operation (cont'd) • MaxLive ではいくつかの割り当てを特別扱いしている ◦ go

    (goroutine を作る) ▪ カーネルスタックサイズの最大値を割り当てる ◦ defer ▪ 普通の呼び出しだが、SSA では object として表現されるた め、メモリ割り当てとみなす ◦ map & slice への insert ▪ 一般に挿入前のサイズが検知できないため、メモリ割り当て 料が予測できない ▪ この問題に対処するため、map と slice の最大値のアノテー ションを Biscuit のソースコード70箇所に追加した
  56. 6.3.2 Handling loops • MaxLive が実用的なループ回数を特定できない箇所 ◦ ソースの78箇所にアノテーションを追加した ◦ さらに20箇所はアノテーションが困難

    ▪ 例1) wakeup race を扱う retry が必要な poll() システムコール ▪ 例2) path のルックアップをしながらディレクトリのデータブロックのイテレーションを する部分 ▪ 例3) ユーザーバッファのページをイテレーションする必要がある write() システム コール
  57. 6.3.2 Handling loops (cont'd) • 対処方法: deep reservation ◦ 1回のイテレーションに必要なだけのヒープのみ

    reserve する ◦ メモリが確保できなかった場合、一度 abort してメモリ確保を待機、システムコールの最 初からリトライする ◦ exec() と rename() のみ、割り当て失敗時に undo を行うコードを書かざるを得なかった が、他のシステムコールはリカバリコードは不要になった
  58. 6.3.2 Handling loops (cont'd) • exit()、fork()、exec() の問題 ◦ 多数のファイルディスクリプタを close()

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

    以外のすべてのシステムコールのヒープ使用量の上限を 500kB 以下に抑えることに成功 ▪ rename() = 1MB ▪ fork() = 641kB ◦ MaxLive を使用して手動で解析が必要だったのは close() だけ ▪ 残りはすべて自動で解析ができた!
  60. 6.3.3 Kernel threads • カーネル領域 ◦ 長時間実行が継続されるカーネルスレッドそれぞれに対しても、カーネルヒープの reservation が必要 ▪

    exit() はkiller スレッドがプロセスを kill するときに動作する必要があるため、 exit() が依存しているカーネルスレッドは、自分が持っているヒープを勝手に解放できない ◦ 例) exit はファイルやブロックを削除するため、ファイルシステムロギンススレッドが必要
  61. 6.3.4 Killer thread • システムコールメモリ割り当て失敗時に目覚める a. ガベージコレクションの実行 b. 不十分な場合、キャッシュなどを開放し、再度、ガベージ コレクションを実行

    c. それでも不十分な場合 ▪ メモリ領域・ファイルディスクリプタ・スレッドを最も 多く持っているプロセスを探し出す ▪ このプロセスを「真に悪い市民 (genuine bad citizen)」であると推定する ▪ その市民を kill する ▪ 再度、ガベージコレクションを実行 d. メモリ割り当てに満足したら、眠る
  62. 6.4 Limitations • 1) Biscuit のヒープ枯渇問題への対処には、メモリ枯渇時にGCが正常に動作する ことが必要 ◦ Goのガベージコレクタは動作時に新規メモリ割り当てが必要なため問題 ◦

    まだ未実装だが、Biscuit はこの状況から回復可能 (ただし、遅くなることが予想される ) ◦ そもそもこの状況は稀であり、実験によれば、 GC は 最大でも 0.8% のヒープメモリを work stack に割り当てれば十分 • 2) Go の GC はオブジェクトを移動させないため、フラグメンテーションが削減されな い ◦ MaxLive でシステムコール時のヒープの割り当て時にオブジェクトの種類ごとに s を推定 することでリスクを減らせるが、まだ未実装
  63. 6.5 Heap exhaustion summary • Linux のアイデアを採用した ◦ killer thread

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