[インデックス 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.WaitStatus
やsyscall.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に関する基本的な知識が必要です。
-
Go言語におけるポインタと値:
- 値型: 変数に直接データが格納されます。コピーされると、データの完全な複製が作成されます。
int
,string
,struct
などが値型です。 - ポインタ型: 変数にデータのメモリアドレスが格納されます。ポインタを介して元のデータにアクセスしたり、変更したりできます。
*T
のようにアスタリスクを付けて宣言します。 - 使い分け:
- 大きなデータ構造を関数間で受け渡す際にコピーコストを削減したい場合や、関数内で元のデータを変更したい場合はポインタを使用します。
- データの独立性を保ちたい場合や、
nil
の可能性がない場合は値型を使用します。 - Goの慣習として、レシーバが構造体の場合、メソッドがその構造体のフィールドを変更する必要がある場合はポインタレシーバ(
func (p *MyStruct) ...
)を、変更しない場合は値レシーバ(func (p MyStruct) ...
)を使用します。
- 値型: 変数に直接データが格納されます。コピーされると、データの完全な複製が作成されます。
-
os
パッケージ:- Go言語の標準ライブラリの一部で、オペレーティングシステムとのインタフェースを提供します。ファイル操作、プロセス管理、環境変数へのアクセスなどが含まれます。
os.Process
: 実行中のプロセスを表す構造体です。os.ProcessState
:os.Process.Wait()
メソッドが返す、終了したプロセスの状態情報(終了コード、CPU時間など)を格納する構造体です。os.Process.Wait()
: プロセスが終了するまで待機し、そのプロセスのProcessState
とエラーを返します。
-
syscall
パッケージ:- Go言語の標準ライブラリの一部で、低レベルのシステムコールへのアクセスを提供します。OS固有の定数、構造体、関数などが定義されています。
syscall.WaitStatus
(Unix系): Unix系システムでwaitpid
などのシステムコールから返される、プロセスの終了ステータス情報を表す型です。終了コード、シグナル情報、コアダンプの有無などを判別するためのメソッド(Exited()
,ExitStatus()
,Signaled()
,Signal()
など)を提供します。syscall.Waitmsg
(Plan 9): Plan 9オペレーティングシステムでプロセス終了時に使用されるメッセージ構造体です。Unix系のWaitStatus
に相当する情報を含みます。
-
クロスプラットフォーム開発:
- Go言語はクロスプラットフォーム開発を強く意識しており、
os
パッケージのようにOS固有の機能を提供する際には、_unix.go
,_windows.go
,_plan9.go
といったファイル名サフィックスを使用して、特定のOS向けの実装を切り替える仕組み(ビルドタグ)を利用します。これにより、同じAPI(例:os.ProcessState
)が異なるOSで異なる内部実装を持つことができます。
- Go言語はクロスプラットフォーム開発を強く意識しており、
これらの知識が、ProcessState
のstatus
フィールドの型が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
自体が既にポインタ型になったため、そのアドレスをさらに取る必要がなくなったためです。
変更の意図と影響
-
Unix系における値型への統一:
syscall.WaitStatus
は通常、システムコールから直接値として返されるか、値として扱われることが一般的です。ポインタを介することで、不必要な間接参照やnil
チェックの可能性が生じることがあります。値型にすることで、より直接的で効率的なデータアクセスが可能になり、コードのセマンティクスがOSのネイティブな振る舞いに近づきます。- これにより、
ProcessState.Sys()
から返されるinterface{}
も値型のsyscall.WaitStatus
をラップするようになり、型アサーションもそれに合わせて変更されました。
-
Plan 9におけるポインタ型への変更:
- Plan 9の
syscall.Waitmsg
がポインタ型になったのは、Plan 9のシステムコールAPIの特性や、Goのsyscall
パッケージがWaitmsg
をどのように扱うかという内部的な設計判断によるものと考えられます。例えば、Waitmsg
が比較的大きな構造体である場合や、特定の状況でnil
を表現する必要がある場合にポインタが選択されることがあります。 Sys()
およびSysUsage()
メソッドの変更は、status
フィールド自体がポインタになったため、そのポインタを直接返すように修正されたものです。
- Plan 9の
-
一貫性と保守性:
- この変更は、Goの
os
パッケージが提供するプロセス管理APIの内部実装において、各OSの特性に合わせた最適なデータ表現を採用し、コードベース全体の一貫性と保守性を向上させることを目的としています。 - ユーザーから見た
os.ProcessState
のAPI(Pid()
,Success()
,Sys()
,SysUsage()
など)は変更されていませんが、内部的な型の変更により、より堅牢で効率的な実装が実現されています。
- この変更は、Goの
このコミットは、Go言語の標準ライブラリが、異なるオペレーティングシステムの低レベルな詳細を抽象化しつつも、それぞれのOSの特性を尊重した効率的な実装を追求している良い例と言えます。
コアとなるコードの変更箇所
このコミットでは、以下の4つのファイルが変更されています。
src/pkg/os/exec_plan9.go
src/pkg/os/exec_posix.go
src/pkg/os/exec_unix.go
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環境では、
ProcessState
のstatus
フィールドが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準拠)では、
ProcessState
のstatus
フィールドが*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()
メソッド内の変更:
Unix系環境のps = &ProcessState{ pid: pid1, status: status, // 変更: &status から status へ rusage: &rusage, }
Process.Wait()
では、syscall.WaitStatus
型のローカル変数status
の値を、ProcessState
のstatus
フィールドに直接代入するように変更されました。これは、ProcessState.status
が値型になったため、アドレスを取る必要がなくなったためです。
src/pkg/os/exec_windows.go
の解説
Process.Wait()
メソッド内の変更:
Windows環境のreturn &ProcessState{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil // 変更: &syscall.WaitStatus{...} から syscall.WaitStatus{...} へ
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言語の公式ドキュメント:
os
パッケージ: https://pkg.go.dev/ossyscall
パッケージ: https://pkg.go.dev/syscall
- Go言語におけるポインタと値のセマンティクスに関する一般的な情報源(例: Go言語のチュートリアル、ブログ記事など)
- Unix系システムおよびPlan 9におけるプロセス管理と終了ステータスに関する一般的な情報源(例:
waitpid
システムコール、waitmsg
の概念など) - このコミットの背景にある具体的な議論や決定については、Gerritの変更リスト(上記関連リンク)を参照することが最も直接的な情報源となります。