[インデックス 16338] ファイルの概要
このコミットは、GoランタイムのPlan 9オペレーティングシステム向け実装において、usleep
関数にNOSPLIT
フラグを付与する変更です。これにより、usleep
関数がスタックチェックを行わないようになり、lockextra
関数からの呼び出し時に安全性が確保されます。
コミット
commit 8b85a3d480d63c2700ac556f183fc05280380fab
Author: Anthony Martin <ality@pbrane.org>
Date: Sat May 18 15:47:49 2013 -0700
runtime: mark usleep as NOSPLIT on Plan 9
Usleep is called from lockextra, also marked NOSPLIT.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/9258043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8b85a3d480d63c2700ac556f183fc05280380fab
元コミット内容
runtime: mark usleep as NOSPLIT on Plan 9
Usleep is called from lockextra, also marked NOSPLIT.
このコミットは、GoランタイムのPlan 9固有のコードにおいて、usleep
関数をNOSPLIT
としてマークするものです。その理由は、usleep
が既にNOSPLIT
としてマークされているlockextra
関数から呼び出されるためです。
変更の背景
Goランタイムは、低レベルの同期プリミティブやスケジューリングなど、OSと密接に連携する部分でC言語やアセンブリ言語で書かれたコードを含んでいます。これらの低レベル関数の中には、スタックの拡張(スタックチェック)が許されない、あるいは望ましくないものがあります。スタックチェックは、関数呼び出し時に現在のスタックが十分なサイズを持っているかを確認し、必要であればスタックを拡張する処理です。しかし、非常にクリティカルなセクションや、ロックを保持している間など、スタックの拡張によって予期せぬ割り込みやデッドロックが発生する可能性がある場合には、このスタックチェックを無効にする必要があります。
このコミットの背景には、usleep
関数がGoランタイムの内部関数であるlockextra
から呼び出されるという事実があります。lockextra
は、Goランタイムのロック機構に関連する非常に低レベルな関数であり、既にNOSPLIT
としてマークされています。これは、lockextra
の実行中にスタックチェックによる割り込みが発生すると、ランタイムの整合性が損なわれる可能性があるためです。
lockextra
がNOSPLIT
であるにもかかわらず、そこから呼び出されるusleep
がNOSPLIT
でない場合、usleep
の内部でスタックチェックが発生し、予期せぬ動作やデッドロックを引き起こす可能性があります。このコミットは、この潜在的な問題を解消し、ランタイムの安定性と安全性を向上させることを目的としています。
前提知識の解説
Plan 9オペレーティングシステム
Plan 9 from Bell Labsは、Unixの後継としてベル研究所で開発された分散オペレーティングシステムです。1980年代後半に開発が始まり、Unixの「全てはファイルである」という哲学をさらに推し進めました。
主な特徴は以下の通りです。
- 分散システム設計: ネットワーク上の異なるコンピュータが単一のシステムとして機能するように設計されており、ファイルストレージ、計算能力、アプリケーションなどのリソースを異なるサーバーが提供できます。
- 「全てはファイルである」メタファー: ネットワークやユーザーインターフェースを含む全てのシステムインターフェースをファイルとして表現します。
- 9Pプロトコル: ローカルおよびリモートのリソースにファイルとしてアクセスするためのシンプルなメッセージ指向ファイルシステムプロトコルです。
- プロセスごとの名前空間: 各プロセスがシステムリソースの独自のビュー(名前空間)を持ちます。
- UTF-8エンコーディング: 内部的にUTF-8をテキスト表現として完全にサポートし、使用した最初のOSの一つです。
Go言語は、Plan 9の開発者であるロブ・パイクやケン・トンプソンらが関わっており、GoランタイムにはPlan 9への実験的な移植が含まれています。
NOSPLIT
ディレクティブ
NOSPLIT
は、Goコンパイラに対するディレクティブ(指示)の一つで、特定の関数にスタックオーバーフローチェックを挿入しないように指示します。通常、Goの関数は呼び出し時にスタックが十分なサイズを持っているかを確認し、不足していればスタックを自動的に拡張します。これはGoのゴルーチンが非常に軽量であり、スタックサイズが動的に変化するためです。
しかし、Goランタイムの非常に低レベルな部分、特にOSとのインタラクションやロック機構に関わるコードでは、スタックチェックによる割り込みやスタックの拡張が許されない場合があります。例えば、ロックを保持している間にスタックチェックが発生し、スタック拡張のためにスケジューラが介入すると、デッドロックやランタイムの破損につながる可能性があります。
NOSPLIT
が適用された関数は、スタックチェックのオーバーヘッドがないため、非常に高速に実行されますが、スタックが不足した場合にスタックオーバーフローを引き起こす可能性があります。そのため、このディレクティブは、スタック使用量が非常に少なく、かつスタックチェックが許されないクリティカルな関数にのみ慎重に適用されます。
Goのソースコードでは、//go:nosplit
というコメント形式でGoのソースファイルに直接記述されることもありますが、C言語やアセンブリ言語で書かれたランタイムコードでは、#pragma textflag 7
のようなディレクティブを通じてNOSPLIT
フラグが設定されることがあります。
lockextra
関数
lockextra
は、Goランタイムの内部にある低レベルな関数で、Goのロック機構(ミューテックスなど)に関連しています。これはGoの標準ライブラリのsync
パッケージで提供されるようなユーザー向けの関数ではなく、Goランタイムのコア部分でゴルーチンのスケジューリングや同期を管理するために使用されます。
lockextra
の正確な内部動作はGoランタイムの実装詳細に依存しますが、一般的には、ミューテックスの取得や解放、あるいはその他の低レベルな同期プリミティブの操作中に呼び出される可能性があります。この関数は、Goランタイムの整合性を保つ上で非常に重要であり、その実行中に予期せぬ割り込みやスタックの拡張が発生することは望ましくありません。そのため、lockextra
自体もNOSPLIT
としてマークされていることが多いです。
#pragma textflag 7
#pragma textflag 7
は、Goのツールチェイン(コンパイラ、アセンブラ、リンカ)に対して、続くコードブロックに特定のフラグを適用するアセンブリディレクティブです。7
という数値はビットマスクであり、複数のフラグの組み合わせを表します。Goランタイムのsrc/runtime/textflag.h
で定義されているフラグを組み合わせたものです。
7
は以下のフラグの合計です。
- 1 (NOPROF): プロファイリングの対象外とすることを示します(現在は非推奨)。
- 2 (DUPOK): リンカが同じ名前のシンボルを複数検出することを許可します。リンカは重複の中から一つを選択します。
- 4 (NOSPLIT): 関数にスタックチェックのプリアンブルを挿入しないことを示します。これは、スタックチェックのオーバーヘッドが望ましくない、非常に小さいパフォーマンスクリティカルな関数で通常使用されます。
したがって、#pragma textflag 7
は、続くコードにNOPROF
、DUPOK
、NOSPLIT
の3つのフラグを適用することを意味します。このコミットでは、特にNOSPLIT
フラグがusleep
関数に適用されることが重要です。
技術的詳細
このコミットの技術的詳細の中心は、usleep
関数にNOSPLIT
属性を付与することです。Goランタイムでは、usleep
のような低レベルな時間遅延関数は、OSのスケジューリングや同期プリミティブと密接に関連しています。
lockextra
関数は、Goランタイムの内部ロックメカニズムの一部であり、その性質上、非常に短い時間で完了し、かつ実行中にゴルーチンのプリエンプション(横取り)やスタックの拡張が発生しないことが求められます。そのため、lockextra
自体はNOSPLIT
としてマークされています。
もしlockextra
がNOSPLIT
であるにもかかわらず、そこから呼び出されるusleep
がNOSPLIT
でない場合、usleep
の内部でスタックチェックが発生する可能性があります。スタックチェックは、現在のゴルーチンのスタックが不足している場合に、より大きなスタックを割り当ててコピーする処理をトリガーします。この処理は、Goスケジューラを呼び出し、現在のゴルーチンを一時停止させる可能性があります。
lockextra
がロックを保持している間にこのようなスタック拡張とスケジューリングが発生すると、以下のような問題が起こりえます。
- デッドロック:
lockextra
が保持しているロックが解放されないまま、別のゴルーチンがそのロックを待機し、結果としてシステム全体が停止する可能性があります。 - ランタイムの破損: クリティカルなデータ構造がロックによって保護されているにもかかわらず、スタック拡張による割り込みでそのデータ構造が不整合な状態になる可能性があります。
- パフォーマンスの低下: 意図しないスタックチェックと拡張は、低レベルな同期操作のパフォーマンスに悪影響を与えます。
usleep
にNOSPLIT
を適用することで、usleep
の実行中にはスタックチェックが行われなくなります。これにより、lockextra
からusleep
が呼び出されても、スタック拡張による予期せぬスケジューリングや割り込みが発生するリスクが排除され、ランタイムの安定性と安全性が向上します。
この変更は、GoランタイムがPlan 9のような特定のOS環境で、その低レベルな特性を考慮して適切に動作するための調整の一例です。OS固有のコードでは、このような細かなチューニングがシステムの安定性に大きく寄与します。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/os_plan9.c
+++ b/src/pkg/runtime/os_plan9.c
@@ -122,6 +122,7 @@ runtime·osyield(void)\n runtime·sleep(0);\n }\n \n+#pragma textflag 7
void
runtime·usleep(uint32 µs)\n {\n```
## コアとなるコードの解説
変更は`src/pkg/runtime/os_plan9.c`ファイルに対して行われています。このファイルは、GoランタイムのPlan 9オペレーティングシステム固有の実装を含んでいます。
追加された行は以下の通りです。
```c
#pragma textflag 7
この一行がruntime·usleep
関数の定義の直前に追加されています。
#pragma
: コンパイラに対する特別な指示を与えるプリプロセッサディレクティブです。textflag 7
: これはGoのツールチェインに特有のディレクティブで、続く関数(この場合はruntime·usleep
)に特定のフラグを適用します。7
はビットマスクで、NOPROF
(1),DUPOK
(2),NOSPLIT
(4) の合計です。
この変更により、runtime·usleep
関数はNOSPLIT
属性を持つことになります。つまり、この関数が呼び出される際には、Goランタイムはスタックのサイズチェックを行わず、スタックの拡張も試みません。
これは、コミットメッセージにあるように、usleep
が既にNOSPLIT
としてマークされているlockextra
関数から呼び出されるためです。lockextra
のようなクリティカルな関数がスタックチェックを伴う関数を呼び出すと、ランタイムの整合性が損なわれる可能性があるため、呼び出し元と呼び出し先の両方がNOSPLIT
であることが望ましいと判断された結果の変更です。
関連リンク
- Go CL 9258043: https://golang.org/cl/9258043 (このCLは
cmd/go: add -C flag to 'go run'
という別のコミットにマージされているようです。元のコミットメッセージに記載されているCL番号が、このコミットの変更内容と直接一致しない可能性があります。これはGoのコードレビューシステムとGitの統合の特性によるものかもしれません。)
参考にした情報源リンク
- Go runtime NOSPLIT directive: https://go.dev/src/runtime/textflag.h
- Plan 9 from Bell Labs: https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs
- Go runtime
lockextra
discussions (e.g., related to CGo and signals): https://github.com/golang/go/issues/14353 (これはlockextra
に関する一般的な議論の例であり、このコミットに直接関連するものではありませんが、lockextra
が低レベルなランタイム関数であることを示しています。) #pragma textflag
explanation: https://go.dev/doc/asm (Goのアセンブリに関するドキュメント)