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

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

このコミットは、Go言語のosパッケージにおけるプロセス状態情報の扱いに関する変更です。具体的には、ProcessState構造体のstatusフィールドの型を、Unix系システム(Linux, macOSなど)およびWindowsにおいてはポインタ型から値型へと変更し、Plan 9においては値型からポインタ型へと変更しています。これにより、システムコールから返されるプロセス終了ステータス情報の表現が、各OSの慣習により適合するように調整され、関連するコードの整合性が向上しています。

コミット

os: make the system info a value not a pointer on unix
fix a couple of other minor related details.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5690071

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

https://github.com/golang/go/commit/880cda557a8cc638667916eac28e185e686e5878

元コミット内容

os: make the system info a value not a pointer on unix
fix a couple of other minor related details.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5690071

変更の背景

この変更の背景には、Go言語のosパッケージが提供するプロセス管理機能における、システム依存のステータス情報の扱いの一貫性と正確性の向上が挙げられます。特に、os.ProcessState構造体は、os.Process.Wait()メソッドによって返されるプロセス終了時の状態をカプセル化します。このProcessStateが保持するシステム依存のステータス情報(statusフィールド)の型が、OSによってポインタ型(*syscall.WaitStatus*syscall.Waitmsg)であったり、値型(syscall.WaitStatussyscall.Waitmsg)であったりしました。

コミットメッセージにある「make the system info a value not a pointer on unix」は、Unix系システム(Linux, macOSなど)において、syscall.WaitStatusが通常、値として扱われるべきであるという設計思想に基づいています。syscall.WaitStatusは、プロセスが終了した際のステータスコードやシグナル情報などを含む構造体であり、その内容自体が重要な情報であるため、ポインタを介さずに直接値として扱う方が、コードの可読性や安全性、そしてパフォーマンスの観点から望ましいと判断されたと考えられます。ポインタは、大きなデータ構造や、nilの可能性を表現する場合に有用ですが、WaitStatusのような比較的軽量で常に有効な情報を持つ構造体には、値型が適しています。

一方で、Plan 9においては、syscall.Waitmsgがポインタ型として扱われるように変更されています。これは、Plan 9のシステムコールが返すWaitmsgの特性や、GoのsyscallパッケージにおけるPlan 9固有の実装の詳細に起因する可能性があります。異なるOSで異なる型を適用することで、各OSのネイティブなAPIのセマンティクスに忠実なGoのインターフェースを提供し、クロスプラットフォームでの一貫したosパッケージの振る舞いを維持しつつ、内部的にはOS固有の最適な表現を採用しています。

この変更は、Goの標準ライブラリの堅牢性と保守性を高めるための、細かながらも重要な改善の一環です。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびOSに関する基本的な知識が必要です。

  1. Go言語におけるポインタと値:

    • 値型: 変数に直接データが格納されます。コピーされると、データの完全な複製が作成されます。int, string, structなどが値型です。
    • ポインタ型: 変数にデータのメモリアドレスが格納されます。ポインタを介して元のデータにアクセスしたり、変更したりできます。*Tのようにアスタリスクを付けて宣言します。
    • 使い分け:
      • 大きなデータ構造を関数間で受け渡す際にコピーコストを削減したい場合や、関数内で元のデータを変更したい場合はポインタを使用します。
      • データの独立性を保ちたい場合や、nilの可能性がない場合は値型を使用します。
      • Goの慣習として、レシーバが構造体の場合、メソッドがその構造体のフィールドを変更する必要がある場合はポインタレシーバ(func (p *MyStruct) ...)を、変更しない場合は値レシーバ(func (p MyStruct) ...)を使用します。
  2. osパッケージ:

    • Go言語の標準ライブラリの一部で、オペレーティングシステムとのインタフェースを提供します。ファイル操作、プロセス管理、環境変数へのアクセスなどが含まれます。
    • os.Process: 実行中のプロセスを表す構造体です。
    • os.ProcessState: os.Process.Wait()メソッドが返す、終了したプロセスの状態情報(終了コード、CPU時間など)を格納する構造体です。
    • os.Process.Wait(): プロセスが終了するまで待機し、そのプロセスのProcessStateとエラーを返します。
  3. syscallパッケージ:

    • Go言語の標準ライブラリの一部で、低レベルのシステムコールへのアクセスを提供します。OS固有の定数、構造体、関数などが定義されています。
    • syscall.WaitStatus (Unix系): Unix系システムでwaitpidなどのシステムコールから返される、プロセスの終了ステータス情報を表す型です。終了コード、シグナル情報、コアダンプの有無などを判別するためのメソッド(Exited(), ExitStatus(), Signaled(), Signal()など)を提供します。
    • syscall.Waitmsg (Plan 9): Plan 9オペレーティングシステムでプロセス終了時に使用されるメッセージ構造体です。Unix系のWaitStatusに相当する情報を含みます。
  4. クロスプラットフォーム開発:

    • Go言語はクロスプラットフォーム開発を強く意識しており、osパッケージのようにOS固有の機能を提供する際には、_unix.go, _windows.go, _plan9.goといったファイル名サフィックスを使用して、特定のOS向けの実装を切り替える仕組み(ビルドタグ)を利用します。これにより、同じAPI(例: os.ProcessState)が異なるOSで異なる内部実装を持つことができます。

これらの知識が、ProcessStatestatusフィールドの型がOSによって異なる理由や、ポインタから値への変更がコードに与える影響を理解する上で重要となります。

技術的詳細

このコミットの技術的詳細は、os.ProcessState構造体のstatusフィールドの型定義と、そのフィールドへの値の代入、およびその値の利用方法が、各OSの実装ファイル(exec_plan9.go, exec_posix.go, exec_unix.go, exec_windows.go)でどのように変更されたかに集約されます。

変更の核心

  • Unix系 (exec_posix.go, exec_unix.go, exec_windows.go):

    • ProcessState構造体のstatusフィールドの型が、*syscall.WaitStatus (ポインタ) から syscall.WaitStatus (値) に変更されました。
    • os.Process.Wait()内でProcessStateを初期化する際、status: &statusのようにポインタのアドレスを渡していた箇所が、status: statusのように直接値を渡す形に変更されました。
    • ProcessState.Sys()メソッド内で、p.Sys().(*syscall.WaitStatus)のようにポインタ型アサーションを行っていた箇所が、p.Sys().(syscall.WaitStatus)のように値型アサーションに変更されました。これは、Sys()が返すinterface{}型から元の型にキャストする際に、statusが値型になったことに対応するためです。
  • Plan 9 (exec_plan9.go):

    • ProcessState構造体のstatusフィールドの型が、syscall.Waitmsg (値) から *syscall.Waitmsg (ポインタ) に変更されました。
    • os.Process.Wait()内でProcessStateを初期化する際、status: waitmsgのように値を渡していた箇所が、status: &waitmsgのようにポインタのアドレスを渡す形に変更されました。
    • ProcessState.Sys()およびProcessState.SysUsage()メソッド内で、return &p.statusのようにポインタを返していた箇所が、return p.statusのように直接ポインタの値を返す形に変更されました。これは、status自体が既にポインタ型になったため、そのアドレスをさらに取る必要がなくなったためです。

変更の意図と影響

  1. Unix系における値型への統一:

    • syscall.WaitStatusは通常、システムコールから直接値として返されるか、値として扱われることが一般的です。ポインタを介することで、不必要な間接参照やnilチェックの可能性が生じることがあります。値型にすることで、より直接的で効率的なデータアクセスが可能になり、コードのセマンティクスがOSのネイティブな振る舞いに近づきます。
    • これにより、ProcessState.Sys()から返されるinterface{}も値型のsyscall.WaitStatusをラップするようになり、型アサーションもそれに合わせて変更されました。
  2. Plan 9におけるポインタ型への変更:

    • Plan 9のsyscall.Waitmsgがポインタ型になったのは、Plan 9のシステムコールAPIの特性や、GoのsyscallパッケージがWaitmsgをどのように扱うかという内部的な設計判断によるものと考えられます。例えば、Waitmsgが比較的大きな構造体である場合や、特定の状況でnilを表現する必要がある場合にポインタが選択されることがあります。
    • Sys()およびSysUsage()メソッドの変更は、statusフィールド自体がポインタになったため、そのポインタを直接返すように修正されたものです。
  3. 一貫性と保守性:

    • この変更は、Goのosパッケージが提供するプロセス管理APIの内部実装において、各OSの特性に合わせた最適なデータ表現を採用し、コードベース全体の一貫性と保守性を向上させることを目的としています。
    • ユーザーから見たos.ProcessStateのAPI(Pid(), Success(), Sys(), SysUsage()など)は変更されていませんが、内部的な型の変更により、より堅牢で効率的な実装が実現されています。

このコミットは、Go言語の標準ライブラリが、異なるオペレーティングシステムの低レベルな詳細を抽象化しつつも、それぞれのOSの特性を尊重した効率的な実装を追求している良い例と言えます。

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

このコミットでは、以下の4つのファイルが変更されています。

  1. src/pkg/os/exec_plan9.go
  2. src/pkg/os/exec_posix.go
  3. src/pkg/os/exec_unix.go
  4. src/pkg/os/exec_windows.go

それぞれのファイルにおける主要な変更箇所は以下の通りです。

src/pkg/os/exec_plan9.go

--- a/src/pkg/os/exec_plan9.go
+++ b/src/pkg/os/exec_plan9.go
@@ -66,7 +66,7 @@ func (p *Process) Kill() error {
 }
 
 // Wait waits for the Process to exit or stop, and then returns a
-// Waitmsg describing its status and an error, if any.
+// ProcessState describing its status and an error, if any.
 func (p *Process) Wait() (ps *ProcessState, err error) {
 	var waitmsg syscall.Waitmsg
 
@@ -89,7 +89,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) {
 
 	ps = &ProcessState{
 		pid:    waitmsg.Pid,
-		status: waitmsg,
+		status: &waitmsg,
 	}
 	return ps, nil
 }
@@ -110,8 +110,8 @@ func findProcess(pid int) (p *Process, err error) {
 
 // ProcessState stores information about process as reported by Wait.
 type ProcessState struct {
-	pid    int             // The process's id.
-	status syscall.Waitmsg // System-dependent status info.
+	pid    int              // The process's id.
+	status *syscall.Waitmsg // System-dependent status info.
 }
 
 // Pid returns the process id of the exited process.
@@ -134,14 +134,14 @@ func (p *ProcessState) Success() bool {
 // the process.  Convert it to the appropriate underlying
 // type, such as *syscall.Waitmsg on Plan 9, to access its contents.
 func (p *ProcessState) Sys() interface{} {
-	return &p.status
+	return p.status
 }
 
 // SysUsage returns system-dependent resource usage information about
 // the exited process.  Convert it to the appropriate underlying
-// type, such as *syscall.Waitmsg on Unix, to access its contents.
+// type, such as *syscall.Waitmsg on Plan 9, to access its contents.
 func (p *ProcessState) SysUsage() interface{} {
-	return &p.status
+	return p.status
 }
 
 // UserTime returns the user CPU time of the exited process and its children.

src/pkg/os/exec_posix.go

--- a/src/pkg/os/exec_posix.go
+++ b/src/pkg/os/exec_posix.go
@@ -44,8 +44,8 @@ func (p *Process) Kill() error {
 
 // ProcessState stores information about process as reported by Wait.
 type ProcessState struct {
-	pid    int                 // The process's id.
-	status *syscall.WaitStatus // System-dependent status info.
+	pid    int                // The process's id.
+	status syscall.WaitStatus // System-dependent status info.
 	rusage *syscall.Rusage
 }
 
@@ -67,7 +67,7 @@ func (p *ProcessState) Success() bool {
 
 // Sys returns system-dependent exit information about
 // the process.  Convert it to the appropriate underlying
-// type, such as *syscall.WaitStatus on Unix, to access its contents.
+// type, such as syscall.WaitStatus on Unix, to access its contents.
 func (p *ProcessState) Sys() interface{} {
 	return p.status
 }
@@ -110,7 +110,7 @@ func (p *ProcessState) String() string {
 	if p == nil {
 		return "<nil>"
 	}
-	status := p.Sys().(*syscall.WaitStatus)
+	status := p.Sys().(syscall.WaitStatus)
 	res := ""
 	switch {
 	case status.Exited():

src/pkg/os/exec_unix.go

--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/exec_unix.go
@@ -30,7 +30,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) {
 	}
 	ps = &ProcessState{
 		pid:    pid1,
-		status: &status,
+		status: status,
 		rusage: &rusage,
 	}
 	return ps, nil

src/pkg/os/exec_windows.go

--- a/src/pkg/os/exec_windows.go
+++ b/src/pkg/os/exec_windows.go
@@ -30,7 +30,7 @@ func (p *Process) Wait() (ps *ProcessState, err error) {
 		return nil, NewSyscallError("GetExitCodeProcess", e)
 	}
 	p.done = true
-	return &ProcessState{p.Pid, &syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil
+	return &ProcessState{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil
 }
 
 // Signal sends a signal to the Process.

コアとなるコードの解説

src/pkg/os/exec_plan9.go の解説

  • ProcessState構造体の変更:

    type ProcessState struct {
    	pid    int
    	status *syscall.Waitmsg // 変更: syscall.Waitmsg から *syscall.Waitmsg へ
    }
    

    Plan 9環境では、ProcessStatestatusフィールドがsyscall.Waitmsgの値型から*syscall.Waitmsgのポインタ型に変更されました。これにより、statusフィールド自体がsyscall.Waitmsg構造体へのポインタを保持するようになります。

  • Process.Wait()メソッド内の変更:

    ps = &ProcessState{
    	pid:    waitmsg.Pid,
    	status: &waitmsg, // 変更: waitmsg から &waitmsg へ
    }
    

    ProcessStateを初期化する際に、syscall.Waitmsg型のローカル変数waitmsgのアドレス(&waitmsg)をstatusフィールドに代入するように変更されました。これは、statusフィールドがポインタ型になったため、その型に合うようにアドレスを渡す必要があります。

  • ProcessState.Sys()およびProcessState.SysUsage()メソッド内の変更:

    func (p *ProcessState) Sys() interface{} {
    	return p.status // 変更: &p.status から p.status へ
    }
    func (p *ProcessState) SysUsage() interface{} {
    	return p.status // 変更: &p.status から p.status へ
    }
    

    statusフィールド自体が既にポインタ型になったため、そのフィールドのアドレス(&p.status)を返すのではなく、フィールドが保持するポインタの値(p.status)を直接返すように変更されました。これにより、Sys()SysUsage()の呼び出し元は、*syscall.Waitmsg型の値を受け取ることになります。

src/pkg/os/exec_posix.go の解説

  • ProcessState構造体の変更:

    type ProcessState struct {
    	pid    int
    	status syscall.WaitStatus // 変更: *syscall.WaitStatus から syscall.WaitStatus へ
    	rusage *syscall.Rusage
    }
    

    Unix系環境(POSIX準拠)では、ProcessStatestatusフィールドが*syscall.WaitStatusのポインタ型からsyscall.WaitStatusの値型に変更されました。これにより、statusフィールドにsyscall.WaitStatus構造体の値が直接格納されるようになります。

  • ProcessState.Sys()メソッド内のコメントと型アサーションの変更:

    // Sys returns system-dependent exit information about
    // the process.  Convert it to the appropriate underlying
    // type, such as syscall.WaitStatus on Unix, to access its contents. // コメント変更
    func (p *ProcessState) Sys() interface{} {
    	return p.status
    }
    // ...
    status := p.Sys().(syscall.WaitStatus) // 変更: (*syscall.WaitStatus) から (syscall.WaitStatus) へ
    

    Sys()メソッドのコメントが、*syscall.WaitStatusではなくsyscall.WaitStatusに変換するように修正されました。また、String()メソッド内でSys()の戻り値を型アサーションする際に、*syscall.WaitStatusではなくsyscall.WaitStatusとしてアサートするように変更されました。これは、statusフィールドが値型になったことに対応するためです。

src/pkg/os/exec_unix.go の解説

  • Process.Wait()メソッド内の変更:
    ps = &ProcessState{
    	pid:    pid1,
    	status: status, // 変更: &status から status へ
    	rusage: &rusage,
    }
    
    Unix系環境のProcess.Wait()では、syscall.WaitStatus型のローカル変数statusの値を、ProcessStatestatusフィールドに直接代入するように変更されました。これは、ProcessState.statusが値型になったため、アドレスを取る必要がなくなったためです。

src/pkg/os/exec_windows.go の解説

  • Process.Wait()メソッド内の変更:
    return &ProcessState{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil // 変更: &syscall.WaitStatus{...} から syscall.WaitStatus{...} へ
    
    Windows環境のProcess.Wait()では、ProcessStateを初期化する際に、syscall.WaitStatus構造体をポインタとして作成する(&syscall.WaitStatus{...})のではなく、直接値として作成する(syscall.WaitStatus{...})ように変更されました。これも、ProcessState.statusが値型になったことに対応するためです。

これらの変更は、各OSのシステムコールが返すステータス情報の特性に合わせて、Goのosパッケージ内のProcessState構造体のstatusフィールドの型を最適化し、より自然で効率的なデータ表現を実現しています。

関連リンク

  • Gerrit Change-Id: https://golang.org/cl/5690071 - GoプロジェクトのコードレビューシステムであるGerritにおけるこのコミットの変更リスト(Change List)です。詳細な議論やレビューコメントが残されている可能性があります。

参考にした情報源リンク

  • Go言語の公式ドキュメント:
  • Go言語におけるポインタと値のセマンティクスに関する一般的な情報源(例: Go言語のチュートリアル、ブログ記事など)
  • Unix系システムおよびPlan 9におけるプロセス管理と終了ステータスに関する一般的な情報源(例: waitpidシステムコール、waitmsgの概念など)
  • このコミットの背景にある具体的な議論や決定については、Gerritの変更リスト(上記関連リンク)を参照することが最も直接的な情報源となります。