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

[インデックス 15275] ファイルの概要

このコミットは、GoランタイムがLinuxシステムコールrt_sigactionを使用する際に、その戻り値をチェックする変更を導入しています。具体的には、rt_sigaction呼び出しが失敗した場合(例えば、シグナルマスクのサイズが不正な場合など)に、ランタイムがパニックを起こすように修正されています。これにより、シグナルハンドリングの設定における潜在的な問題を早期に検出し、デバッグを容易にすることが目的です。

コミット

commit d3d89ae7d21d8253626aebf212a63d670561f659
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 15 13:13:19 2013 -0500

    runtime: check rt_sigaction return values on linux
    
    (If the mask size is wrong the system call fails.)
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/7305097

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d3d89ae7d21d8253626aebf212a63d670561f659

元コミット内容

このコミットの元のメッセージは以下の通りです。

runtime: check rt_sigaction return values on linux

(If the mask size is wrong the system call fails.)

R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7305097

これは、Linux上でのrt_sigactionシステムコールの戻り値を確認する変更であり、特にシグナルマスクのサイズが不正な場合にシステムコールが失敗する可能性があることを示唆しています。

変更の背景

Goランタイムは、プログラムの実行環境を管理し、ガベージコレクション、スケジューリング、そしてシグナルハンドリングといった低レベルの操作を処理します。シグナルハンドリングは、オペレーティングシステムからのイベント(例えば、プログラムの終了要求、セグメンテーション違反など)をGoプログラムがどのように処理するかを決定する重要な部分です。

Linuxシステムでは、シグナルハンドリングの設定にはsigactionまたはrt_sigactionシステムコールが使用されます。これらのシステムコールは、シグナルハンドラの設定、シグナルマスクの変更、およびその他のシグナル関連の動作を制御します。通常、システムコールは成功した場合は0を返し、失敗した場合は-1を返し、errnoにエラーコードを設定します。

このコミット以前のGoランタイムでは、rt_sigactionシステムコールの戻り値が適切にチェックされていませんでした。これは、システムコールが何らかの理由で失敗した場合(例えば、不正な引数やリソース不足など)、Goランタイムはその失敗を検知できず、予期せぬ動作やクラッシュにつながる可能性がありました。特に、コミットメッセージで言及されているように「マスクサイズが間違っているとシステムコールが失敗する」というケースは、Goランタイムが内部的にsigaction構造体のサイズを誤って計算したり、カーネルとのABI(Application Binary Interface)の不一致があったりする場合に発生しうる問題です。

この変更の背景には、Goランタイムの堅牢性を向上させ、シグナルハンドリングのセットアップにおける潜在的なエラーを早期に捕捉するという目的があります。エラーを検知せずに処理を続行するのではなく、即座にパニックを発生させることで、開発者が問題を特定しやすくなります。

前提知識の解説

1. シグナル (Signals)

シグナルは、Unix系オペレーティングシステムにおいて、プロセスに対して非同期的にイベントを通知するメカニズムです。例えば、ユーザーがCtrl+Cを押すとSIGINTシグナルが、プログラムが不正なメモリアクセスをするとSIGSEGVシグナルが、端末が切断されるとSIGHUPシグナルが送信されます。プロセスはこれらのシグナルを受信し、デフォルトの動作を実行するか、カスタムのシグナルハンドラを呼び出すことができます。

2. rt_sigaction システムコール

rt_sigactionは、Linuxカーネルが提供するシステムコールの一つで、シグナルハンドラを設定するために使用されます。これは古いsigactionシステムコールの拡張版であり、より大きなシグナルセット(シグナルマスク)を扱うことができます。

rt_sigactionの基本的なプロトタイプは以下のようになります(C言語の観点から):

int rt_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact, size_t sigsetsize);
  • signum: 設定するシグナルの番号(例: SIGHUP, SIGINT)。
  • act: 新しいシグナルハンドラの設定を含むsigaction構造体へのポインタ。
  • oldact: 以前のシグナルハンドラの設定を保存するためのsigaction構造体へのポインタ(不要な場合はNULL)。
  • sigsetsize: sigaction構造体内のシグナルマスク(sa_mask)のサイズをバイト単位で指定します。これは通常、sizeof(sigset_t)に設定されます。この値がカーネルの期待する値と異なる場合、システムコールは失敗します。

システムコールは成功すると0を返し、エラーが発生すると-1を返します。

3. SIGHUP シグナル

SIGHUP (Signal Hang Up) は、通常、制御端末が切断されたときにプロセスに送信されるシグナルです。デーモンプロセスなどでは、設定ファイルを再読み込みするためにこのシグナルを利用することがよくあります。このコミットでは、SIGHUPシグナルに対するデフォルトの動作(通常はプロセス終了)をチェックし、もしSIG_IGN(シグナルを無視する)に設定されている場合は、Goランタイムが特別な処理を行わないようにしています。

4. SIG_IGN

SIG_IGNは、シグナルハンドラとして設定できる特殊な値で、対応するシグナルを無視するようにプロセスに指示します。

5. runtime·throw

Goランタイム内部で使用される関数で、致命的なエラーが発生した場合にプログラムを終了させます。これはGoのpanicメカニズムの低レベルな実装の一部であり、回復不可能な状態に陥ったことを示します。

技術的詳細

このコミットは、GoランタイムがLinux上でシグナルハンドリングを設定する際の堅牢性を高めるためのものです。具体的には、src/pkg/runtime/signal_linux_386.csrc/pkg/runtime/signal_linux_arm.cの2つのファイルに修正が加えられています。これらはそれぞれ、Intel 386アーキテクチャとARMアーキテクチャにおけるLinux固有のシグナルハンドリングロジックを含んでいます。

Goランタイムは、runtime·setsig関数内で特定のシグナル(この場合はSIGHUP)の現在のハンドラ設定を読み取るためにrt_sigactionシステムコールを使用しています。このシステムコールは、act引数にnil(C言語のNULLに相当)を渡し、oldact引数にsigaction構造体へのポインタを渡すことで、現在のシグナルハンドラ設定を照会するモードで動作します。

変更前は、このrt_sigaction呼び出しの戻り値がチェックされていませんでした。これは、システムコールが失敗した場合でも、Goランタイムはあたかも成功したかのように処理を続行し、不正なsigaction構造体の内容に基づいて判断を下す可能性がありました。特に、コミットメッセージが指摘するように、sigsetsize引数(この場合はsizeof(sa.sa_mask))がカーネルの期待する値と一致しない場合にシステムコールが失敗することがあります。このような状況は、Goランタイムがコンパイルされた環境と実行されるカーネルのABIに微妙な不一致がある場合に発生しえます。

変更後は、rt_sigactionの戻り値がチェックされ、0以外(つまりエラー)の場合にはruntime·throw("rt_sigaction read failure")が呼び出されるようになりました。これにより、シグナルハンドリングの初期設定段階で問題が発生した場合に、Goプログラムが即座にパニックを起こし、開発者が問題を診断できるようになります。これは、サイレントな失敗を防ぎ、システムの安定性とデバッグ可能性を向上させるための重要な変更です。

この修正は、特にnohupコマンドの下で実行されるプログラムのSIGHUPシグナル処理に関連しています。nohupは、端末が切断されてもプロセスが実行を継続できるようにするコマンドです。Goランタイムは、SIGHUPSIG_IGNに設定されているかどうかを確認することで、nohupのような環境下での挙動を適切に処理しようとします。このチェックの前にrt_sigactionが失敗すると、誤った情報に基づいて処理が進む可能性があったため、戻り値のチェックはより重要になります。

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

変更は以下の2つのファイルで行われています。

  1. src/pkg/runtime/signal_linux_386.c
  2. src/pkg/runtime/signal_linux_arm.c

それぞれのファイルで、runtime·setsig関数内のSIGHUPシグナルに関する処理部分が修正されています。

変更前:

// src/pkg/runtime/signal_linux_386.c (同様の変更がARM版にも適用)
if(i == SIGHUP) {
	runtime·memclr((byte*)&sa, sizeof sa);
	runtime·rt_sigaction(i, nil, &sa, sizeof(sa.sa_mask)); // ここで戻り値がチェックされていない
	if(sa.k_sa_handler == SIG_IGN)
		return;
}

変更後:

// src/pkg/runtime/signal_linux_386.c (同様の変更がARM版にも適用)
if(i == SIGHUP) {
	runtime·memclr((byte*)&sa, sizeof sa);
	if(runtime·rt_sigaction(i, nil, &sa, sizeof(sa.sa_mask)) != 0) // 戻り値のチェックを追加
		runtime·throw("rt_sigaction read failure"); // エラー時にパニック
	if(sa.k_sa_handler == SIG_IGN)
		return;
}

コアとなるコードの解説

変更の核心は、runtime·rt_sigactionシステムコールの呼び出しをif文で囲み、その戻り値を評価する点にあります。

  • runtime·rt_sigaction(i, nil, &sa, sizeof(sa.sa_mask))

    • i: シグナル番号(この場合はSIGHUP)。
    • nil: 新しいシグナルハンドラを設定しないことを示す(現在の設定を照会するため)。
    • &sa: 現在のシグナルハンドラ設定を格納するためのsigaction構造体saへのポインタ。
    • sizeof(sa.sa_mask): シグナルマスクのサイズ。これはsigset_tのサイズに相当します。
  • != 0: rt_sigactionシステムコールは成功した場合に0を返します。したがって、0以外が返された場合はエラーが発生したことを意味します。

  • runtime·throw("rt_sigaction read failure"): rt_sigaction呼び出しが失敗した場合に、Goランタイムがこの関数を呼び出してプログラムを終了させます。引数の文字列は、エラーメッセージとして表示され、問題の原因を特定するのに役立ちます。

この変更により、Goランタイムはシグナルハンドリングの初期設定時に発生する可能性のある低レベルのエラーを検出し、それらをサイレントに無視するのではなく、明示的に報告するようになりました。これにより、Goプログラムの信頼性が向上し、デバッグが容易になります。

関連リンク

参考にした情報源リンク

  • 上記のsigaction(2) man page
  • Go言語のソースコード(特にsrc/pkg/runtimeディレクトリ内の関連ファイル)
  • Unix/Linuxシグナルハンドリングに関する一般的な知識
  • nohupコマンドの動作に関する知識
  • Go言語のpanicメカニズムに関する知識
  • Go言語のコミット履歴とコードレビューシステム (Gerrit) の利用方法
  • rt_sigactionシステムコールに関する情報 (Web検索)