Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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上で動作していた際、usleepnanosleep)が中断された場合に、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が提供するシステムコールを介して間接的にアクセスします。

usleepnanosleep

  • 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関数を呼び出すようになっていました。これは、nanosleepEINTRを返した場合でも、それを致命的なエラーとして扱ってしまうことを意味します。

コミットメッセージにある「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を呼び出さずに処理を続行するようになります。

この変更は、nanosleepEINTRを返した場合でも、それを正常な中断として扱い、Goランタイムがクラッシュしないようにするためのものです。Goランタイムは、nanosleepが中断された場合でも、内部的に待機を再開するロジックを持っているか、またはこの特定のコンテキストでは中断が許容されるべきであると判断されたと考えられます。

コアとなるコードの変更箇所

変更は、GoランタイムのFreeBSD向けアセンブリファイルに集中しています。

  1. src/pkg/runtime/sys_freebsd_386.s

    • TEXT runtime·usleep(SB) 関数内
    • INT $0x80 (システムコール実行) の直後から、以下の2行が削除されました。
      -	JAE	2(PC)
      -	CALL	runtime·notok(SB)
      
  2. 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関連のクラッシュが解消されます。

関連リンク

参考にした情報源リンク