[インデックス 11561] ファイルの概要
このコミットは、Go言語の標準ライブラリ os
パッケージにおける Process
型の内部フィールド handle
の型を int
から uintptr
(Windows環境では syscall.Handle
の実体) に変更するものです。これにより、特にWindows環境でのプロセスハンドルの扱いが、より正確かつプラットフォームのネイティブな型に沿ったものになります。また、関連する syscall
パッケージの StartProcess
関数の戻り値の型も同様に変更されています。
コミット
commit 16ce2f9369fd76334880a3883ca1def77d41c7e3
Author: Wei Guangjing <vcc.163@gmail.com>
Date: Thu Feb 2 14:08:48 2012 -0500
os: Process.handle use syscall.Handle
R=golang-dev, alex.brainman, rsc
CC=golang-dev
https://golang.org/cl/5605050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/16ce2f9369fd76334880a3883ca1def77d41c7e3
元コミット内容
diff --git a/src/pkg/os/exec.go b/src/pkg/os/exec.go
index 33e223fd29..6e0f168c76 100644
--- a/src/pkg/os/exec.go
+++ b/src/pkg/os/exec.go
@@ -12,11 +12,11 @@ import (
// Process stores the information about a process created by StartProcess.
type Process struct {
Pid int
- handle int
+ handle uintptr
done bool // process has been successfuly waited on
}
-func newProcess(pid, handle int) *Process {
+func newProcess(pid int, handle uintptr) *Process {
p := &Process{Pid: pid, handle: handle}
runtime.SetFinalizer(p, (*Process).Release)
return p
diff --git a/src/pkg/os/exec_windows.go b/src/pkg/os/exec_windows.go
index c7e25f9853..b89f91c197 100644
--- a/src/pkg/os/exec_windows.go
+++ b/src/pkg/os/exec_windows.go
@@ -46,14 +46,14 @@ func (p *Process) Signal(sig Signal) error {
// Release releases any resources associated with the Process.
func (p *Process) Release() error {
- if p.handle == -1 {
+ if p.handle == uintptr(syscall.InvalidHandle) {
return EINVAL
}
e := syscall.CloseHandle(syscall.Handle(p.handle))
if e != nil {
return NewSyscallError("CloseHandle", e)
}
- p.handle = -1
+ p.handle = uintptr(syscall.InvalidHandle)
// no need for a finalizer anymore
runtime.SetFinalizer(p, nil)
return nil
@@ -66,7 +66,7 @@ func findProcess(pid int) (p *Process, err error) {
if e != nil {
return nil, NewSyscallError("OpenProcess", e)
}
- return newProcess(pid, int(h)), nil
+ return newProcess(pid, uintptr(h)), nil
}
func init() {
diff --git a/src/pkg/syscall/exec_plan9.go b/src/pkg/syscall/exec_plan9.go
index 788666b2f2..de6421c239 100644
--- a/src/pkg/syscall/exec_plan9.go
+++ b/src/pkg/syscall/exec_plan9.go
@@ -483,7 +483,7 @@ func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
}
// StartProcess wraps ForkExec for package os.
-func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid, handle int, err error) {
+func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
pid, err = forkExec(argv0, argv, attr)
return pid, 0, err
}
diff --git a/src/pkg/syscall/exec_unix.go b/src/pkg/syscall/exec_unix.go
index ad3cf48c80..b70e1880b9 100644
--- a/src/pkg/syscall/exec_unix.go
+++ b/src/pkg/syscall/exec_unix.go
@@ -208,7 +208,7 @@ func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
}
// StartProcess wraps ForkExec for package os.
-func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid, handle int, err error) {
+func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
pid, err = forkExec(argv0, argv, attr)
return pid, 0, err
}
diff --git a/src/pkg/syscall/exec_windows.go b/src/pkg/syscall/exec_windows.go
index 2826e2f35a..6cb25a7d00 100644
--- a/src/pkg/syscall/exec_windows.go
+++ b/src/pkg/syscall/exec_windows.go
@@ -232,7 +232,7 @@ type SysProcAttr struct {
var zeroProcAttr ProcAttr
var zeroSysProcAttr SysProcAttr
-func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid, handle int, err error) {
+func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
if len(argv0) == 0 {
return 0, 0, EWINDOWS
}
@@ -319,7 +319,7 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid, handle int,
}\n\tdefer CloseHandle(Handle(pi.Thread))\n \n-\treturn int(pi.ProcessId), int(pi.Process), nil
+\treturn int(pi.ProcessId), uintptr(pi.Process), nil
}\n \n func Exec(argv0 string, argv []string, envv []string) (err error) {
変更の背景
このコミットの背景には、Go言語が異なるオペレーティングシステム(OS)上で一貫したプロセス管理を提供するための、より堅牢で正確な基盤を構築するという目的があります。特にWindows環境では、プロセスを識別するための「プロセスID (PID)」と、プロセスオブジェクトへの参照である「ハンドル (Handle)」は異なる概念です。
従来のGoの os.Process
構造体では、内部の handle
フィールドが int
型で定義されていました。しかし、Windows APIにおけるハンドルは通常 HANDLE
型であり、これは実質的にポインタまたは uintptr
として扱われる符号なし整数です。int
型でハンドルを扱うことは、以下の問題を引き起こす可能性がありました。
- 型安全性の欠如:
int
は汎用的な整数型であり、OSハンドルが持つ特定の意味や制約を表現できません。これにより、誤った値がハンドルとして扱われたり、プラットフォーム固有のハンドルの特性が失われたりするリスクがありました。 - プラットフォーム間の不整合: Unix系OSではプロセスIDがプロセスを識別する主要な手段であり、ハンドルという概念はWindowsほど明確ではありません。しかし、Windowsではハンドルがリソース管理において不可欠です。
int
型を使用すると、Windowsのネイティブなハンドル表現との間にギャップが生じ、潜在的なバグや非効率性を招く可能性がありました。 - 将来的な拡張性:
uintptr
を使用することで、OSが提供するネイティブなハンドル型との互換性が向上し、将来的にOS固有のAPIをより直接的に呼び出す際の柔軟性が高まります。
この変更は、Goの os
パッケージがOSのプロセス管理機能をより正確に抽象化し、特にWindows環境での堅牢性と互換性を向上させることを目的としています。
前提知識の解説
1. プロセスとプロセスハンドル
- プロセス (Process): 実行中のプログラムのインスタンスです。それぞれが独自のメモリ空間、リソース、実行コンテキストを持ちます。
- プロセスID (PID): オペレーティングシステムが各プロセスに割り当てる一意の数値識別子です。Unix系OSでは、PIDがプロセスを操作する主要な手段となります。
- プロセスハンドル (Process Handle): Windows OSに特有の概念で、プロセスオブジェクトへの参照です。Windows APIを介してプロセスを操作(例えば、プロセスの終了、情報の取得、同期など)するには、そのプロセスのハンドルが必要です。ハンドルは通常、
uintptr
型(符号なし整数)で表現されます。ハンドルはリソースであり、使用後はCloseHandle
関数などで明示的に解放する必要があります。
2. Go言語の os
パッケージ
os
パッケージは、オペレーティングシステムが提供する機能(ファイル操作、プロセス管理、環境変数など)へのプラットフォームに依存しないインターフェースを提供します。
os.Process
: 実行中の外部プロセスを表す構造体です。この構造体には、プロセスのPIDや、プラットフォーム固有のハンドル情報などが含まれます。
3. Go言語の syscall
パッケージ
syscall
パッケージは、低レベルのオペレーティングシステムプリミティブへのアクセスを提供します。これには、システムコール、OS固有の定数、データ構造などが含まれます。
syscall.Handle
: Windows環境において、OSハンドルを表す型です。Goの内部ではuintptr
のエイリアスとして定義されています。uintptr
: 任意のポインタ値を保持できる整数型です。ポインタ演算は許可されていませんが、ポインタと整数間の変換が可能です。OS固有のハンドルやメモリアドレスを表現する際に使用されます。syscall.InvalidHandle
: 無効なハンドルを表す定数です。Windows APIでは、無効なハンドルは通常NULL
またはINVALID_HANDLE_VALUE
で表現されますが、Goのsyscall
パッケージではuintptr(0)
またはuintptr(^uintptr(0))
(つまりuintptr(-1)
) に対応する値として定義されることがあります。このコミットではuintptr(syscall.InvalidHandle)
と比較することで、ハンドルが有効かどうかをチェックしています。
4. クロスプラットフォーム開発における課題
Goはクロスプラットフォーム言語であり、同じコードベースで複数のOSに対応することを目指しています。しかし、OSごとにプロセス管理のメカニズムが異なるため、os
パッケージのようなOSに密接に関連する部分では、プラットフォーム固有の実装を抽象化しつつ、それぞれのOSの特性を尊重する必要があります。このコミットは、特にWindowsのハンドル概念をより適切に扱うための改善です。
技術的詳細
このコミットの核心は、os.Process
構造体の handle
フィールドの型を int
から uintptr
に変更したことです。
-
os.Process.handle
の型変更:- 変更前:
handle int
- 変更後:
handle uintptr
この変更により、os.Process
がWindowsのネイティブなハンドルをより正確に表現できるようになります。uintptr
はポインタを保持できる十分な幅を持つ整数型であり、WindowsのHANDLE
型(通常は32ビットまたは64ビットのポインタ値)を適切に格納できます。
- 変更前:
-
newProcess
関数のシグネチャ変更:- 変更前:
func newProcess(pid, handle int) *Process
- 変更後:
func newProcess(pid int, handle uintptr) *Process
os.Process
のコンストラクタであるnewProcess
も、handle
引数の型をuintptr
に合わせることで、型の一貫性を保っています。
- 変更前:
-
Release
メソッドでのハンドルチェックの変更:- 変更前:
if p.handle == -1 { ... p.handle = -1 }
- 変更後:
if p.handle == uintptr(syscall.InvalidHandle) { ... p.handle = uintptr(syscall.InvalidHandle) }
プロセスハンドルを解放するRelease
メソッドでは、ハンドルの有効性をチェックし、解放後に無効な状態に設定します。この変更により、int
型の-1
というマジックナンバーではなく、syscall
パッケージで定義されたsyscall.InvalidHandle
をuintptr
にキャストした値を使用するようになりました。これは、よりセマンティックでプラットフォームに依存しない(ただし、syscall.InvalidHandle
自体はプラットフォーム固有の定義を持つ)方法で無効なハンドルを表現します。
- 変更前:
-
findProcess
関数での型変換:- 変更前:
return newProcess(pid, int(h)), nil
- 変更後:
return newProcess(pid, uintptr(h)), nil
Windows固有のfindProcess
関数では、OpenProcess
システムコールから取得したハンドルh
をnewProcess
に渡す際に、int(h)
からuintptr(h)
へと型変換が変更されました。これは、h
がsyscall.Handle
型であり、その実体がuintptr
であるため、より直接的な変換となります。
- 変更前:
-
syscall.StartProcess
関数のシグネチャ変更:- 変更前 (Plan 9, Unix, Windows):
func StartProcess(...) (pid, handle int, err error)
- 変更後 (Plan 9, Unix, Windows):
func StartProcess(...) (pid int, handle uintptr, err error)
syscall
パッケージ内のStartProcess
関数は、OSレベルでプロセスを開始する低レベルなインターフェースを提供します。この関数の戻り値であるhandle
もint
からuintptr
に変更されました。これにより、os.StartProcess
がsyscall.StartProcess
を呼び出す際に、型変換の必要がなくなり、よりクリーンな連携が可能になります。特にWindowsでは、このhandle
が実際のOSハンドルを直接表すため、uintptr
であることが適切です。
- 変更前 (Plan 9, Unix, Windows):
これらの変更は、Goのプロセス管理が、特にWindows環境において、OSのネイティブなハンドル概念をより正確に反映し、型安全性を向上させるための重要なステップです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルと行に集中しています。
-
src/pkg/os/exec.go
:Process
構造体のhandle
フィールドの型定義:- handle int + handle uintptr
newProcess
関数のhandle
引数の型定義:-func newProcess(pid, handle int) *Process { +func newProcess(pid int, handle uintptr) *Process {
-
src/pkg/os/exec_windows.go
:Release
メソッド内のhandle
の比較と代入:- if p.handle == -1 { + if p.handle == uintptr(syscall.InvalidHandle) { // ... - p.handle = -1 + p.handle = uintptr(syscall.InvalidHandle)
findProcess
関数内のnewProcess
呼び出し時の型変換:- return newProcess(pid, int(h)), nil + return newProcess(pid, uintptr(h)), nil
-
src/pkg/syscall/exec_plan9.go
,src/pkg/syscall/exec_unix.go
,src/pkg/syscall/exec_windows.go
:- 各OS固有の
StartProcess
関数のhandle
戻り値の型定義:-func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid, handle int, err error) { +func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
src/pkg/syscall/exec_windows.go
のStartProcess
内の戻り値の型変換:- return int(pi.ProcessId), int(pi.Process), nil + return int(pi.ProcessId), uintptr(pi.Process), nil
- 各OS固有の
コアとなるコードの解説
これらの変更は、Goのプロセス管理における「ハンドル」の概念を、特にWindowsのネイティブなAPIとより密接に連携させるためのものです。
-
os.Process.handle
のint
からuintptr
への変更: これは最も根本的な変更です。Windowsでは、プロセスハンドルは単なる整数ではなく、OSが管理するリソースへのポインタのようなものです。uintptr
は、ポインタ値を整数として扱うためのGoの型であり、これによりWindowsのHANDLE
型(通常はvoid*
のようなポインタ型)を正確に表現できるようになります。これにより、Goのos
パッケージがWindowsのプロセスハンドルをより安全かつ効率的に操作できるようになります。Unix系OSではhandle
は通常使用されませんが、型をuintptr
に統一することで、クロスプラットフォームでのインターフェースの一貫性が保たれます。 -
newProcess
関数のシグネチャ変更:newProcess
はos.Process
オブジェクトを生成する内部関数です。この関数のhandle
引数の型をuintptr
に変更することで、os.Process
構造体のhandle
フィールドに正しい型の値が渡されることが保証されます。これにより、型ミスマッチによる潜在的なバグを防ぎます。 -
Release
メソッドでのsyscall.InvalidHandle
の使用:Release
メソッドは、プロセスに関連付けられたOSリソース(この場合はプロセスハンドル)を解放する役割を担います。if p.handle == uintptr(syscall.InvalidHandle)
: これは、現在のプロセスハンドルが既に無効であるかどうかをチェックしています。以前は-1
というマジックナンバーを使用していましたが、syscall.InvalidHandle
を使用することで、OSが定義する「無効なハンドル」の概念を直接参照し、コードの意図がより明確になります。syscall.InvalidHandle
は、プラットフォームに応じて適切な無効値を定義しています(例: Windowsでは0
または^uintptr(0)
)。p.handle = uintptr(syscall.InvalidHandle)
: ハンドルを解放した後、p.handle
を無効な状態に設定することで、二重解放などの問題を防止し、Process
オブジェクトの状態を安全に保ちます。
-
findProcess
およびsyscall.StartProcess
での型変換: これらの関数は、OSから直接プロセスハンドルを取得したり、OSにプロセスを開始させたりする低レベルな操作を行います。uintptr(h)
やuintptr(pi.Process)
のように、OSから返されるハンドル値をuintptr
に明示的にキャストすることで、Goの内部表現とOSのネイティブな表現との間の整合性を確保しています。これにより、os.Process
やsyscall.StartProcess
が返すハンドルが、WindowsのHANDLE
型と互換性のあるuintptr
として扱われるようになります。
これらの変更は、Goのプロセス管理レイヤーが、OSの低レベルな詳細をより正確に抽象化し、特にWindows環境での堅牢性と正確性を向上させるための重要な改善です。
関連リンク
- Go言語の
os
パッケージドキュメント: https://pkg.go.dev/os - Go言語の
syscall
パッケージドキュメント: https://pkg.go.dev/syscall - Go言語の
uintptr
型に関するドキュメント: https://pkg.go.dev/builtin#uintptr - Windows API
HANDLE
型に関するMicrosoftドキュメント: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types (HANDLEの定義を含む)
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go Code Review (Gerrit) CL 5605050: https://golang.org/cl/5605050 (コミットメッセージに記載されているリンク)
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語における
uintptr
の利用に関する一般的な情報源 (例: Goのブログ記事、Stack Overflowなど)uintptr
の利用例やその目的について解説している記事を参考にしました。- Windowsのプロセス管理におけるハンドルとPIDの違いについて解説している記事を参考にしました。