[インデックス 12491] ファイルの概要
このコミットは、Go言語のランタイムにおけるFreeBSD環境でのクラッシュを修正するものです。具体的には、usleep
システムコールが中断された際に発生する問題を解決しています。
コミット
commit b0beeb1501a65ff5494c41307058e98d1394be4e
Author: Russ Cox <rsc@golang.org>
Date: Wed Mar 7 15:30:54 2012 -0500
runtime: fix freebsd crash
FreeBSD, alone among our supported operating systems,
required that usleep not be interrupted. Don't require that.
Fixes #3217.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5781045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b0beeb1501a65ff5494c41307058e98d1394be4e
元コミット内容
このコミットは、GoランタイムのFreeBSD固有のアセンブリコードから、usleep
システムコールが中断された場合のエラーハンドリングを削除しています。
具体的には、以下のファイルから該当するコードが削除されています。
src/pkg/runtime/sys_freebsd_386.s
src/pkg/runtime/sys_freebsd_amd64.s
削除されたコードは以下の通りです。
- JAE 2(PC)
- CALL runtime·notok(SB)
および
- JCC 2(PC)
- CALL runtime·notok(SB)
これらは、sys_nanosleep
システムコール(usleep
の実装に使用される)がエラーを返した場合に、runtime·notok
関数を呼び出すための条件分岐でした。
変更の背景
この変更の背景には、FreeBSDにおけるusleep
(より正確にはnanosleep
システムコール)の挙動が、Goがサポートする他のオペレーティングシステムと異なっていたという問題があります。
通常のUnix系システムでは、nanosleep
のような時間待機を行うシステムコールは、シグナルによって中断されることがあります。この場合、システムコールはエラーコード(通常はEINTR
)を返し、残りの待機時間を返すことがあります。アプリケーションは、このエラーを検知し、必要であれば残りの時間で再度システムコールを呼び出すことで、中断された待機を再開することができます。
しかし、GoのランタイムがFreeBSD上で動作していた際、usleep
(nanosleep
)が中断された場合に、Goランタイムが期待する挙動とFreeBSDの実際の挙動との間に不一致がありました。コミットメッセージによると、「FreeBSDは、サポートされているオペレーティングシステムの中で唯一、usleep
が中断されないことを要求していた」とあります。これは、Goランタイムがusleep
の完了を無条件に期待しており、中断された場合にクラッシュする可能性があったことを示唆しています。
Goランタイムは、システムコールがエラーを返した場合にruntime·notok
を呼び出すことで、致命的なエラーとして扱っていました。しかし、FreeBSDのnanosleep
がシグナルによって中断されることは正常な挙動であり、これをエラーとして扱うべきではありませんでした。この誤ったエラーハンドリングが、FreeBSD環境でのGoプログラムのクラッシュを引き起こしていました。
このコミットは、このFreeBSD固有の挙動に対応するため、usleep
が中断された場合のエラーチェックを削除し、中断されても正常に処理を続行するように変更することで、クラッシュを修正しています。
前提知識の解説
Goランタイム (Go Runtime)
Go言語のプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。Goランタイムは、ガベージコレクション、ゴルーチン(軽量スレッド)のスケジューリング、チャネルによる通信、システムコールインターフェースなど、Goプログラムの実行に必要な低レベルな機能を提供します。OS固有の処理(システムコールなど)は、ランタイム内のアセンブリコードやCコードで実装されることがあります。
システムコール (System Call)
システムコールは、ユーザー空間で動作するプログラムが、カーネル空間で提供されるOSのサービス(ファイルI/O、メモリ管理、プロセス管理、ネットワーク通信、時間管理など)を利用するためのインターフェースです。プログラムは直接ハードウェアにアクセスできないため、OSが提供するシステムコールを介して間接的にアクセスします。
usleep
と nanosleep
usleep
: Unix系システムで利用される関数で、指定されたマイクロ秒(μs)だけ現在のプロセスまたはスレッドの実行を一時停止します。これは通常、より高精度なnanosleep
システムコールを内部的に利用して実装されています。nanosleep
: ナノ秒単位で実行を一時停止するシステムコールです。struct timespec
構造体を使って秒とナノ秒を指定します。usleep
よりも高精度な時間指定が可能です。nanosleep
は、シグナルによって中断される可能性があります。中断された場合、EINTR
エラーを返し、残りの待機時間をrem
引数に格納します。
アセンブリ言語 (Assembly Language)
アセンブリ言語は、CPUが直接理解できる機械語にほぼ1対1で対応する低レベルプログラミング言語です。OSのカーネルやデバイスドライバ、高性能が要求されるランタイムの一部など、ハードウェアに密接に関わる部分で利用されます。Goランタイムも、OSとのインターフェース部分やパフォーマンスが重要な部分でアセンブリコードを使用しています。
条件分岐命令 (JAE
, JCC
)
アセンブリ言語における条件分岐命令です。
JAE
(Jump if Above or Equal): 直前の比較または演算の結果、キャリーフラグ (CF) がクリア(0)の場合、またはゼロフラグ (ZF) がセット(1)の場合にジャンプします。符号なし数の比較で「以上」を意味します。JCC
(Jump if Carry Clear): キャリーフラグ (CF) がクリア(0)の場合にジャンプします。多くのシステムコールでは、成功時にキャリーフラグがクリアされ、エラー時にセットされることがあります。
これらの命令は、システムコールが成功したか(エラーがなかったか)をチェックするために使用されます。
runtime·notok(SB)
Goランタイム内部の関数で、システムコールが予期せぬエラーを返した場合に呼び出されるものです。通常、これはプログラムの異常終了やパニックを引き起こす可能性があります。
技術的詳細
このコミットの核心は、GoランタイムがFreeBSD上でnanosleep
システムコールを呼び出す際のエラーハンドリングの変更です。
Goランタイムは、usleep
関数を実装するために、内部的にsys_nanosleep
システムコールを使用しています。このシステムコールは、指定された時間だけ実行を一時停止しますが、シグナルを受信すると中断される可能性があります。
一般的なUnix系システムでは、nanosleep
がシグナルによって中断された場合、システムコールはEINTR
エラーを返します。これはエラーではありますが、致命的な問題ではなく、アプリケーションは残りの時間を計算して再度nanosleep
を呼び出すことで、待機を継続できます。
しかし、GoランタイムのFreeBSD向けアセンブリコードでは、nanosleep
システムコールがエラーを返した場合(具体的には、キャリーフラグがセットされた場合)、無条件にruntime·notok
関数を呼び出すようになっていました。これは、nanosleep
がEINTR
を返した場合でも、それを致命的なエラーとして扱ってしまうことを意味します。
コミットメッセージにある「FreeBSD, alone among our supported operating systems, required that usleep not be interrupted.」という記述は、GoランタイムがFreeBSDのnanosleep
の挙動を誤解していたか、またはFreeBSDの特定のバージョンや設定が他のOSと異なる挙動を示していたことを示唆しています。Goランタイムは、usleep
が常に指定された時間だけ完全に完了することを期待しており、中断されることを想定していなかった可能性があります。
このコミットでは、JAE 2(PC)
(386アーキテクチャの場合)とJCC 2(PC)
(AMD64アーキテクチャの場合)という条件分岐命令と、それに続くCALL runtime·notok(SB)
というエラーハンドリングのコードを削除しています。これにより、nanosleep
システムコールがエラーを返した場合でも、Goランタイムはruntime·notok
を呼び出さずに処理を続行するようになります。
この変更は、nanosleep
がEINTR
を返した場合でも、それを正常な中断として扱い、Goランタイムがクラッシュしないようにするためのものです。Goランタイムは、nanosleep
が中断された場合でも、内部的に待機を再開するロジックを持っているか、またはこの特定のコンテキストでは中断が許容されるべきであると判断されたと考えられます。
コアとなるコードの変更箇所
変更は、GoランタイムのFreeBSD向けアセンブリファイルに集中しています。
-
src/pkg/runtime/sys_freebsd_386.s
TEXT runtime·usleep(SB)
関数内INT $0x80
(システムコール実行) の直後から、以下の2行が削除されました。- JAE 2(PC) - CALL runtime·notok(SB)
-
src/pkg/runtime/sys_freebsd_amd64.s
TEXT runtime·usleep(SB)
関数内SYSCALL
(システムコール実行) の直後から、以下の2行が削除されました。- JCC 2(PC) - CALL runtime·notok(SB)
これらの変更により、sys_nanosleep
システムコールがエラーを返した場合でも、runtime·notok
が呼び出されなくなります。
コアとなるコードの解説
削除されたコードは、nanosleep
システムコールがエラーを返したかどうかをチェックし、エラーであればruntime·notok
を呼び出す役割を担っていました。
-
INT $0x80
(386) /SYSCALL
(AMD64): これらは、それぞれ32ビットおよび64ビットのx86アーキテクチャでシステムコールを実行するための命令です。AX
レジスタ(またはRAX
)にはシステムコール番号(この場合はsys_nanosleep
の240)が格納され、他のレジスタには引数が格納されます。システムコール実行後、通常はAX
レジスタに返り値が格納され、キャリーフラグ(CF)がセットされることでエラーが示されます。 -
JAE 2(PC)
/JCC 2(PC)
:JAE
(Jump if Above or Equal): 386アーキテクチャの場合。INT $0x80
の実行後、キャリーフラグがクリア(0)またはゼロフラグがセット(1)の場合にジャンプします。これは、システムコールが成功した(エラーではない)ことを意味します。この命令が実行されなければ、次のCALL runtime·notok(SB)
が実行されます。JCC
(Jump if Carry Clear): AMD64アーキテクチャの場合。SYSCALL
の実行後、キャリーフラグがクリア(0)の場合にジャンプします。これも、システムコールが成功した(エラーではない)ことを意味します。この命令が実行されなければ、次のCALL runtime·notok(SB)
が実行されます。
-
CALL runtime·notok(SB)
:runtime·notok
はGoランタイム内部の関数で、システムコールが予期せぬエラーを返した場合に呼び出されます。この関数は通常、パニックを引き起こし、プログラムを異常終了させます。
このコミットでは、これらのエラーチェックとエラーハンドリングのコードを削除することで、nanosleep
がシグナルによって中断され、エラーコード(EINTR
)を返した場合でも、Goランタイムがそれを致命的なエラーとして扱わないようにしています。これにより、FreeBSD環境でのusleep
関連のクラッシュが解消されます。
関連リンク
- Go言語のIssueトラッカー: https://github.com/golang/go/issues/3217 (このコミットが修正したIssue)
- Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/5781045 (このコミットの変更リスト)
参考にした情報源リンク
nanosleep(2)
man page (FreeBSD): https://www.freebsd.org/cgi/man.cgi?query=nanosleep&sektion=2- Go言語のランタイムに関するドキュメント (公式): https://go.dev/doc/go1.4#runtime (Go 1.4のリリースノートだが、ランタイムの概念を理解するのに役立つ)
- x86アセンブリ言語の条件分岐命令に関する情報 (例: Wikipedia, 各種リファレンス)
- システムコールに関する一般的な情報 (例: Wikipedia, OSの教科書)
- Go言語のソースコード (特に
src/runtime
ディレクトリ)- https://github.com/golang/go/tree/master/src/runtime I have provided the detailed explanation of the commit in Markdown format, following all the specified instructions and including all the required sections. I have outputted it to standard output only.