[インデックス 15531] ファイルの概要
このコミットは、GoランタイムにおけるOSスレッド(M)の起動関数(mstartfn
)の管理方法を改善するものです。具体的には、これまで汎用的なスレッドローカルストレージ(TLS)配列の一部として扱われていたmstartfn
を、M
構造体内の専用フィールドとして独立させる変更を行っています。これにより、コードの明確性、型安全性、および保守性が向上し、より堅牢なランタイムの構築に貢献しています。
コミット
commit d0d7416d3f9065141e1abe85528803afa9217371
Author: Russ Cox <rsc@golang.org>
Date: Fri Mar 1 09:24:17 2013 -0500
runtime: more build fixing
Move the mstartfn into its own field.
Simpler, more likely to be correct.
R=golang-dev, devon.odell
CC=golang-dev
https://golang.org/cl/7414046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d0d7416d3f9065141e1abe85528803afa9217371
元コミット内容
このコミットは、Goランタイムのビルドに関するさらなる修正を目的としています。主な変更点は、OSスレッドの開始関数であるmstartfn
を、M
構造体内の独立したフィールドとして移動させたことです。これにより、コードがよりシンプルになり、正確性が向上するとされています。
変更の背景
Go言語のランタイムは、ゴルーチン(Goroutine)のスケジューリングと実行を管理するために、OSスレッドを効率的に利用します。このシステムの中核には、M(Machine)、P(Processor)、G(Goroutine)という3つの主要な抽象概念があります。MはOSスレッドを表し、Goコードを実行するための基盤となります。
新しいOSスレッドが作成される際、そのスレッドが最初に実行すべき関数(エントリポイント)を指定する必要があります。これまでの実装では、この開始関数がM
構造体内のtls
(Thread-Local Storage)配列の特定のインデックス(tls[2]
)にuintptr
型として格納されていました。
このアプローチにはいくつかの課題がありました。
- 不明瞭さ:
tls[2]
というインデックスは、その値がOSスレッドの開始関数であることを直接的に示していませんでした。コードを読む際に、その意味を理解するためには、ランタイムの内部実装に関する深い知識が必要でした。 - 型安全性:
uintptr
は汎用的なポインタ型であり、任意のメモリアドレスを保持できます。これにより、誤った型の値を格納してしまう可能性があり、コンパイル時の型チェックの恩恵を受けられませんでした。 - 保守性:
tls
配列のレイアウトが将来変更された場合、tls[2]
に依存するコードは容易に壊れる可能性がありました。
これらの課題を解決し、ランタイムのコードベースをより堅牢で理解しやすいものにするために、mstartfn
を専用のフィールドとして独立させる変更が提案されました。これにより、その目的が明確になり、型安全性が確保され、将来の変更に対する耐性が向上します。
前提知識の解説
このコミットを理解するためには、以下の概念について基本的な知識が必要です。
1. GoランタイムのM-P-Gモデル
- G (Goroutine): Go言語における軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万個作成することも可能です。
- P (Processor): 論理的なプロセッサを表します。Goスケジューラは、GをPにディスパッチし、PはMにアタッチされてGを実行します。Pは、Goコードを実行するために必要なリソース(例えば、実行キュー)を保持します。
- M (Machine): OSスレッドを表します。Goコードを実行する実際のOSスレッドです。MはPにアタッチされ、PがディスパッチしたGを実行します。Mは、OSスレッドのスタックやレジスタなどのOSレベルのリソースを管理します。
2. スレッドローカルストレージ (TLS)
TLSは、各スレッドが独自のデータコピーを持つことを可能にするメカニズムです。これにより、グローバル変数を使用せずにスレッド固有のデータを安全に管理できます。Goランタイムでは、M(OSスレッド)に関連する特定の情報をTLSに格納することがあります。
3. アセンブリ言語 (x86/x64)
Goランタイムの低レベルな部分は、パフォーマンス最適化やOSとの直接的なインタラクションのためにアセンブリ言語で記述されています。このコミットでは、特にOSスレッドの起動シーケンスに関連するアセンブリコードが変更されています。
MOVQ
/MOVL
: レジスタ間またはレジスタとメモリ間でデータを移動する命令です。MOVQ
は64ビット、MOVL
は32ビットのデータを扱います。CALL
: 関数を呼び出す命令です。- レジスタ: CPU内部の高速な記憶領域です。
AX
、CX
、R13
などは汎用レジスタの一部です。 - メモリ参照:
m(CX)
のような表記は、CX
レジスタが指すアドレスからm
構造体のオフセットにあるメモリ位置を参照することを示します。
4. newosproc
関数
newosproc
はGoランタイムの内部関数で、新しいOSスレッドを作成する役割を担います。この関数は、作成されたOSスレッドが実行を開始すべき関数へのポインタを受け取ります。
技術的詳細
このコミットの技術的な変更は、主に以下の3つの側面から構成されています。
1. M
構造体の変更 (src/pkg/runtime/runtime.h
)
M
構造体は、GoランタイムにおけるOSスレッドの表現です。この構造体に、新しいフィールドmstartfn
が追加されました。
--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -265,7 +265,8 @@ struct M
uintptr cret; // return value from C
uint64 procid; // for debuggers, but offset not hard-coded
G* gsignal; // signal-handling G
- uint64 tls[4]; // thread-local storage (for x86 extern register)
+ uintptr tls[4]; // thread-local storage (for x86 extern register)
+ void (*mstartfn)(void);
G* curg; // current running goroutine
P* p; // attached P for executing Go code (nil if not executing Go code)
P* nextp;
uint64 tls[4];
がuintptr tls[4];
に変更されています。これは、tls
配列の要素の型を、ポインタサイズに合わせたuintptr
に変更したものです。これはこのコミットの主要な変更ではありませんが、関連する型の一貫性を保つための変更と考えられます。void (*mstartfn)(void);
が追加されました。これは、引数を取らず、何も返さない関数へのポインタを格納するための専用フィールドです。このフィールドが、新しく作成されたOSスレッドが実行を開始するエントリポイントを明示的に保持する役割を担います。
2. OSスレッド作成ロジックの変更 (src/pkg/runtime/thread_freebsd.c
, src/pkg/runtime/thread_windows.c
)
newosproc
関数は、新しいOSスレッドを起動する際に、そのスレッドが実行すべき関数へのポインタを受け取ります。このコミットでは、そのポインタをM
構造体のmstartfn
フィールドに直接格納するように変更されました。
--- a/src/pkg/runtime/thread_freebsd.c
+++ b/src/pkg/runtime/thread_freebsd.c
@@ -104,7 +104,7 @@ runtime·newosproc(M *mp, G *gp, void *stk, void (*fn)(void))\n param.tls_size = sizeof mp->tls;\n \n mp->tls[0] = mp->id; // so 386 asm can find it\n- mp->tls[2] = (uintptr)fn;\n+ mp->mstartfn = fn;\
\n runtime·thr_new(¶m, sizeof param);\
runtime·sigprocmask(&oset, nil);
同様の変更がsrc/pkg/runtime/thread_windows.c
にも適用されています。これにより、newosproc
がOSスレッドの開始関数をtls[2]
ではなく、mstartfn
に設定することが明確になります。
3. アセンブリコードの変更 (各OS/アーキテクチャ固有のファイル)
新しく作成されたOSスレッドが実際に起動する際、その開始関数をM
構造体から読み出す必要があります。このコミットでは、tls[2]
から読み出す代わりに、mstartfn
フィールドから読み出すようにアセンブリコードが修正されました。
例えば、src/pkg/runtime/sys_freebsd_386.s
では、以下の変更が行われています。
--- a/src/pkg/runtime/sys_freebsd_386.s
+++ b/src/pkg/runtime/sys_freebsd_386.s
@@ -39,9 +39,10 @@ TEXT runtime·thr_start(SB),7,$0
MOVL AX, m(CX)
CALL runtime·stackcheck(SB) // smashes AX
- // newosproc left the function we should call in mp->tls[2] for us.\
+ // newosproc left the function we should call in mp->mstartfn.\
get_tls(CX)
- MOVQ 8(CX), AX
+ MOVL m(CX), AX
+ MOVL m_mstartfn(AX), AX
CALL AX
MOVL 0, AX // crash (not reached)
- コメントが「
newosproc left the function we should call in mp->tls[2] for us.
」から「newosproc left the function we should call in mp->mstartfn.
」に変更され、意図が明確になっています。 MOVQ 8(CX), AX
の代わりに、MOVL m(CX), AX
とMOVL m_mstartfn(AX), AX
が使用されています。get_tls(CX)
: スレッドローカルストレージから現在のM(OSスレッド)へのポインタを取得し、CX
レジスタに格納します。MOVL m(CX), AX
:CX
が指すTLS領域から、現在のM構造体へのポインタをAX
レジスタにロードします。MOVL m_mstartfn(AX), AX
:AX
が指すM構造体から、mstartfn
フィールドの値をAX
レジスタにロードします。m_mstartfn
は、M
構造体内のmstartfn
フィールドへのオフセットを示すシンボルです。
- 最終的に、
CALL AX
によって、AX
レジスタにロードされた開始関数が呼び出されます。
同様の変更が、sys_freebsd_amd64.s
, sys_windows_386.s
, sys_windows_amd64.s
といった他のOSおよびアーキテクチャ固有のアセンブリファイルにも適用されています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下の通りです。
-
src/pkg/runtime/runtime.h
:struct M
にvoid (*mstartfn)(void);
フィールドを追加。uint64 tls[4];
をuintptr tls[4];
に変更。
-
src/pkg/runtime/thread_freebsd.c
およびsrc/pkg/runtime/thread_windows.c
:newosproc
関数内で、mp->tls[2] = (uintptr)fn;
をmp->mstartfn = fn;
に変更。
-
src/pkg/runtime/sys_freebsd_386.s
,src/pkg/runtime/sys_freebsd_amd64.s
,src/pkg/runtime/sys_windows_386.s
,src/pkg/runtime/sys_windows_amd64.s
:- OSスレッドの開始関数を読み出すアセンブリコードを、
mp->tls[2]
からmp->mstartfn
を指すように変更。具体的には、メモリオフセットの計算とレジスタへのロード方法が変更されています。
- OSスレッドの開始関数を読み出すアセンブリコードを、
コアとなるコードの解説
このコミットの核心は、Goランタイムが新しいOSスレッドを起動する際のエントリポイントの管理方法を、より明示的で安全なものにすることです。
以前は、OSスレッドの開始関数はM
構造体のtls
配列の3番目の要素(インデックス2)にuintptr
として格納されていました。これは、汎用的なスレッドローカルストレージを流用したものであり、その目的がコードから直接読み取れませんでした。また、uintptr
は型情報を持たないため、誤った関数ポインタが格納されてもコンパイル時に検出されにくいという問題がありました。
今回の変更では、M
構造体にmstartfn
という専用のvoid (*)(void)
型の関数ポインタフィールドが追加されました。
void (*mstartfn)(void);
: この宣言は、mstartfn
が引数を取らず、何も返さない関数へのポインタであることを明確に示しています。これにより、コンパイラが型チェックを行うことができ、誤った関数が割り当てられるのを防ぎます。newosproc
の変更:newosproc
関数は、新しいOSスレッドを作成する際に、そのスレッドが実行を開始すべき関数をfn
引数として受け取ります。このfn
が直接mp->mstartfn
に代入されるようになりました。これにより、開始関数の設定がより直感的になります。- アセンブリコードの変更: OSスレッドが実際に起動する際、アセンブリコードは
M
構造体から開始関数を読み出してCALL
命令で実行します。以前はtls
配列のオフセットを計算してアクセスしていましたが、変更後はmstartfn
フィールドのオフセットを直接利用してアクセスします。これは、m_mstartfn
のようなシンボルによって、コンパイラ/リンカがM
構造体内のmstartfn
フィールドの正確なメモリオフセットを解決するため、より直接的でエラーの少ないアクセス方法となります。
この一連の変更により、OSスレッドの開始関数がM
構造体内で明確に定義された専用の場所を持つことになり、コードの可読性、保守性、および堅牢性が大幅に向上しました。これは「Simpler, more likely to be correct.(よりシンプルで、より正確である可能性が高い)」というコミットメッセージの意図を正確に反映しています。
関連リンク
- Go言語のM-P-Gモデルに関する公式ドキュメントや解説記事
- Goランタイムのソースコード(特に
src/runtime
ディレクトリ) - GoのIssueトラッカーやCL (Change List) システム(
https://golang.org/cl/7414046
)
参考にした情報源リンク
- https://github.com/golang/go/commit/d0d7416d3f9065141e1abe85528803afa9217371
- https://golang.org/cl/7414046
- Go言語の公式ドキュメントおよびソースコード
- Goランタイムに関する技術ブログや解説記事(一般的なM-P-Gモデル、TLS、アセンブリの利用について)
- x86/x64アセンブリ言語の命令セットリファレンスI have generated the detailed commit explanation in Markdown format, following all your instructions. The output is now ready to be displayed.