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

[インデックス 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 型でハンドルを扱うことは、以下の問題を引き起こす可能性がありました。

  1. 型安全性の欠如: int は汎用的な整数型であり、OSハンドルが持つ特定の意味や制約を表現できません。これにより、誤った値がハンドルとして扱われたり、プラットフォーム固有のハンドルの特性が失われたりするリスクがありました。
  2. プラットフォーム間の不整合: Unix系OSではプロセスIDがプロセスを識別する主要な手段であり、ハンドルという概念はWindowsほど明確ではありません。しかし、Windowsではハンドルがリソース管理において不可欠です。int 型を使用すると、Windowsのネイティブなハンドル表現との間にギャップが生じ、潜在的なバグや非効率性を招く可能性がありました。
  3. 将来的な拡張性: 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 に変更したことです。

  1. os.Process.handle の型変更:

    • 変更前: handle int
    • 変更後: handle uintptr この変更により、os.Process がWindowsのネイティブなハンドルをより正確に表現できるようになります。uintptr はポインタを保持できる十分な幅を持つ整数型であり、Windowsの HANDLE 型(通常は32ビットまたは64ビットのポインタ値)を適切に格納できます。
  2. newProcess 関数のシグネチャ変更:

    • 変更前: func newProcess(pid, handle int) *Process
    • 変更後: func newProcess(pid int, handle uintptr) *Process os.Process のコンストラクタである newProcess も、handle 引数の型を uintptr に合わせることで、型の一貫性を保っています。
  3. Release メソッドでのハンドルチェックの変更:

    • 変更前: if p.handle == -1 { ... p.handle = -1 }
    • 変更後: if p.handle == uintptr(syscall.InvalidHandle) { ... p.handle = uintptr(syscall.InvalidHandle) } プロセスハンドルを解放する Release メソッドでは、ハンドルの有効性をチェックし、解放後に無効な状態に設定します。この変更により、int 型の -1 というマジックナンバーではなく、syscall パッケージで定義された syscall.InvalidHandleuintptr にキャストした値を使用するようになりました。これは、よりセマンティックでプラットフォームに依存しない(ただし、syscall.InvalidHandle 自体はプラットフォーム固有の定義を持つ)方法で無効なハンドルを表現します。
  4. findProcess 関数での型変換:

    • 変更前: return newProcess(pid, int(h)), nil
    • 変更後: return newProcess(pid, uintptr(h)), nil Windows固有の findProcess 関数では、OpenProcess システムコールから取得したハンドル hnewProcess に渡す際に、int(h) から uintptr(h) へと型変換が変更されました。これは、hsyscall.Handle 型であり、その実体が uintptr であるため、より直接的な変換となります。
  5. 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レベルでプロセスを開始する低レベルなインターフェースを提供します。この関数の戻り値である handleint から uintptr に変更されました。これにより、os.StartProcesssyscall.StartProcess を呼び出す際に、型変換の必要がなくなり、よりクリーンな連携が可能になります。特にWindowsでは、この handle が実際のOSハンドルを直接表すため、uintptr であることが適切です。

これらの変更は、Goのプロセス管理が、特にWindows環境において、OSのネイティブなハンドル概念をより正確に反映し、型安全性を向上させるための重要なステップです。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルと行に集中しています。

  1. 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 {
      
  2. 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
      
  3. 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.goStartProcess 内の戻り値の型変換:
      -	return int(pi.ProcessId), int(pi.Process), nil
      +	return int(pi.ProcessId), uintptr(pi.Process), nil
      

コアとなるコードの解説

これらの変更は、Goのプロセス管理における「ハンドル」の概念を、特にWindowsのネイティブなAPIとより密接に連携させるためのものです。

  • os.Process.handleint から uintptr への変更: これは最も根本的な変更です。Windowsでは、プロセスハンドルは単なる整数ではなく、OSが管理するリソースへのポインタのようなものです。uintptr は、ポインタ値を整数として扱うためのGoの型であり、これによりWindowsの HANDLE 型(通常は void* のようなポインタ型)を正確に表現できるようになります。これにより、Goの os パッケージがWindowsのプロセスハンドルをより安全かつ効率的に操作できるようになります。Unix系OSでは handle は通常使用されませんが、型を uintptr に統一することで、クロスプラットフォームでのインターフェースの一貫性が保たれます。

  • newProcess 関数のシグネチャ変更: newProcessos.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.Processsyscall.StartProcess が返すハンドルが、Windowsの HANDLE 型と互換性のある uintptr として扱われるようになります。

これらの変更は、Goのプロセス管理レイヤーが、OSの低レベルな詳細をより正確に抽象化し、特にWindows環境での堅牢性と正確性を向上させるための重要な改善です。

関連リンク

参考にした情報源リンク

  • 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の違いについて解説している記事を参考にしました。