[インデックス 19544] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおいて、WindowsプラットフォームでのGetppid()
関数の実装を追加し、関連するテストを拡充するものです。これにより、Windows環境でも親プロセスのIDを取得する機能が提供され、Goプログラムのクロスプラットフォーム互換性が向上します。
コミット
commit 6f6f1bd054bf8789ede8cdfeaf28fa5af88bc371
Author: Alan Shreve <alan@inconshreveable.com>
Date: Sat Jun 14 15:51:00 2014 +1000
syscall: implement syscall.Getppid() on Windows
Also added a test to verify os.Getppid() works across all platforms
LGTM=alex.brainman
R=golang-codereviews, alex.brainman, shreveal, iant
CC=golang-codereviews
https://golang.org/cl/102320044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6f6f1bd054bf8789ede8cdfeaf28fa5af88bc371
元コミット内容
このコミットの目的は、Windows上でsyscall.Getppid()
関数を実装することです。これに伴い、os.Getppid()
がすべてのプラットフォームで正しく動作することを確認するためのテストも追加されています。
変更の背景
Go言語はクロスプラットフォーム開発を強く意識しており、OS固有の機能へのアクセスはsyscall
パッケージを通じて抽象化されています。しかし、特定のOSではまだ未実装の機能が存在することがあります。Getppid()
(親プロセスIDの取得)はUnix系システムでは一般的な機能ですが、Windowsでは直接的なAPIが存在せず、より複雑な手順を踏む必要があります。このコミットは、Windows環境でのGetppid()
の欠落を解消し、Goプログラムが親プロセス情報を必要とする際に、プラットフォームに依存しない形でその機能を利用できるようにすることを目的としています。これにより、例えばデーモンプロセスやサービスとして動作するアプリケーションが、自身の起動元プロセスを特定するなどのユースケースに対応できるようになります。
前提知識の解説
プロセスと親子関係
オペレーティングシステムにおいて、プロセスは実行中のプログラムのインスタンスです。プロセスは通常、別のプロセスによって作成され、この作成元のプロセスを「親プロセス」、作成されたプロセスを「子プロセス」と呼びます。各プロセスには一意の識別子であるプロセスID(PID)が割り当てられ、親プロセスには親プロセスID(PPID)が割り当てられます。
Getppid()
関数
Getppid()
関数は、現在のプロセスの親プロセスIDを返すシステムコールです。Unix系OSでは、この機能は直接的なシステムコールとして提供されることが多く、比較的簡単に親プロセスIDを取得できます。
Windows APIとプロセス情報
Windowsでは、プロセス情報を取得するために「Tool Help Library」と呼ばれる一連のAPIが提供されています。これらは、システム上のプロセス、スレッド、ヒープ、モジュールなどのスナップショット(ある時点での状態)を取得し、その情報を列挙するためのものです。
CreateToolhelp32Snapshot
: この関数は、指定された種類のシステムオブジェクト(プロセス、ヒープ、モジュール、スレッドなど)のスナップショットを作成します。プロセス情報を取得する場合は、TH32CS_SNAPPROCESS
フラグを指定します。Process32First
:CreateToolhelp32Snapshot
で作成されたスナップショットから、最初のプロセスに関する情報を取得します。取得した情報はPROCESSENTRY32
構造体に格納されます。Process32Next
:Process32First
または前回のProcess32Next
の呼び出しに続いて、次のプロセスに関する情報を取得します。PROCESSENTRY32
構造体: この構造体には、プロセスのID (th32ProcessID
)、親プロセスのID (th32ParentProcessID
)、実行ファイル名 (szExeFile
) など、プロセスの詳細情報が含まれます。
WindowsにおけるPPIDの取得は、Unix系OSのように直接的なAPIが存在しないため、これらのTool Help APIを使用してシステム上の全プロセスのスナップショットを取得し、その中から現在のプロセスのエントリを見つけ出し、そのエントリに含まれる親プロセスIDを読み取るという手順を踏む必要があります。
技術的詳細
このコミットでは、Windows上でsyscall.Getppid()
を実装するために、以下のWindows APIがGoのsyscall
パッケージにラップされています。
CreateToolhelp32Snapshot
: プロセス情報のスナップショットを作成するために使用されます。Process32First
: スナップショット内の最初のプロセスエントリを取得します。Process32Next
: スナップショット内の次のプロセスエントリを順次取得します。
実装のロジックは以下の通りです。
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
を呼び出して、現在のシステム上の全プロセスのスナップショットを取得します。- 取得したスナップショットハンドルを
Process32First
に渡し、最初のプロセスエントリを取得します。 - ループ内で
Process32Next
を繰り返し呼び出し、各プロセスエントリを順次処理します。 - 各プロセスエントリの
ProcessID
が現在のプロセスのID(Getpid()
で取得)と一致するかどうかを確認します。 - 一致するプロセスエントリが見つかった場合、そのエントリの
ParentProcessID
をGetppid()
の戻り値として返します。 - スナップショットの取得に失敗したり、現在のプロセスが見つからなかったりした場合は、エラーを示す値(-1)を返します。
また、PROCESSENTRY32
構造体もsrc/pkg/syscall/ztypes_windows.go
に追加され、Windows APIからの情報をGoの型で扱えるようにしています。
テストケースTestGetppid
は、子プロセスを起動し、その子プロセス内でos.Getppid()
を呼び出して親プロセスIDを取得します。取得したIDが、子プロセスを起動した親プロセスのIDと一致するかどうかを検証することで、Getppid()
の実装が正しく機能していることを確認します。このテストは、runtime.GOOS == "nacl"
の場合にはスキップされます。
コアとなるコードの変更箇所
src/pkg/os/os_test.go
TestGetppid
関数が追加され、os.Getppid()
のクロスプラットフォームでの動作を検証します。子プロセスを生成し、その子プロセスが自身の親プロセスIDを標準出力に出力するように設定し、親プロセス側でその出力をキャプチャして自身のPIDと比較します。
func TestGetppid(t *testing.T) {
if runtime.GOOS == "nacl" {
t.Skip("skipping on nacl")
}
if Getenv("GO_WANT_HELPER_PROCESS") == "1" {
fmt.Print(Getppid())
Exit(0)
}
cmd := osexec.Command(Args[0], "-test.run=TestGetppid")
cmd.Env = append(Environ(), "GO_WANT_HELPER_PROCESS=1")
// verify that Getppid() from the forked process reports our process id
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("Failed to spawn child process: %v %q", err, string(output))
}
childPpid := string(output)
ourPid := fmt.Sprintf("%d", Getpid())
if childPpid != ourPid {
t.Fatalf("Child process reports parent process id '%v', expected '%v'", childPpid, ourPid)
}
}
src/pkg/syscall/syscall_windows.go
Getppid()
関数の実装が追加されました。以前はreturn -1
というスタブ実装でした。
また、Windows APIのラッパーであるCreateToolhelp32Snapshot
, Process32First
, Process32Next
が//sys
ディレクティブで宣言されています。
//sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot
//sys Process32First(snapshot Handle, procEntry *Process32Entry) (err error) = kernel32.Process32FirstW
//sys Process32Next(snapshot Handle, procEntry *Process32Entry) (err error) = kernel32.Process32NextW
func getProcessEntry(pid int) (*ProcessEntry32, error) {
snapshot, err := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
if err != nil {
return nil, err
}
defer CloseHandle(snapshot)
var procEntry ProcessEntry32
procEntry.Size = uint32(unsafe.Sizeof(procEntry))
if err = Process32First(snapshot, &procEntry); err != nil {
return nil, err
}
for {
if procEntry.ProcessID == uint32(pid) {
return &procEntry, nil
}
err = Process32Next(snapshot, &procEntry)
if err != nil {
return nil, err
}
}
}
func Getppid() (ppid int) {
pe, err := getProcessEntry(Getpid())
if err != nil {
return -1
}
return int(pe.ParentProcessID)
}
src/pkg/syscall/zsyscall_windows_386.go
および src/pkg/syscall/zsyscall_windows_amd64.go
CreateToolhelp32Snapshot
, Process32FirstW
, Process32NextW
のプロシージャアドレスがmodkernel32
から取得されるように定義が追加されています。これらのファイルは、Goのsyscall
パッケージがWindows APIを呼び出すための低レベルなバインディングを生成する際に使用されます。
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
procProcess32FirstW = modkernel32.NewProc("Process32FirstW")
procProcess32NextW = modkernel32.NewProc("Process32NextW")
そして、それぞれの関数に対するSyscall
呼び出しのラッパーが追加されています。
src/pkg/syscall/ztypes_windows.go
Windows APIで使用される定数と構造体が追加されています。
TH32CS_SNAPPROCESS
などのCreateToolhelp32Snapshot
のフラグ定数。ProcessEntry32
構造体。この構造体は、WindowsのPROCESSENTRY32
構造体に対応し、プロセスのID、親プロセスのID、実行ファイル名などの情報を含みます。
const (
// flags for CreateToolhelp32Snapshot
TH32CS_SNAPHEAPLIST = 0x01
TH32CS_SNAPPROCESS = 0x02
TH32CS_SNAPTHREAD = 0x04
TH32CS_SNAPMODULE = 0x08
TH32CS_SNAPMODULE32 = 0x10
TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPMODULE | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD
TH32CS_INHERIT = 0x80000000
)
type ProcessEntry32 struct {
Size uint32
Usage uint32
ProcessID uint32
DefaultHeapID uintptr
ModuleID uint32
Threads uint32
ParentProcessID uint32
PriClassBase int32
Flags uint32
ExeFile [MAX_PATH]uint16
}
コアとなるコードの解説
このコミットの核となるのは、Windowsにおけるsyscall.Getppid()
の実装です。
-
getProcessEntry(pid int) (*ProcessEntry32, error)
関数: このヘルパー関数は、指定されたPIDを持つプロセスのProcessEntry32
構造体を取得します。CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
を呼び出し、システム上の全プロセスのスナップショットを作成します。TH32CS_SNAPPROCESS
はプロセス情報のみを取得するフラグです。- スナップショットの作成に失敗した場合、エラーを返します。
defer CloseHandle(snapshot)
により、関数終了時にスナップショットハンドルが確実に閉じられるようにします。ProcessEntry32
構造体のSize
フィールドを適切に設定します。これはWindows APIの要件です。Process32First
で最初のプロセスエントリを取得し、その後Process32Next
をループで呼び出して全てのエントリを走査します。- 走査中に、現在のプロセスのPIDと一致する
ProcessID
を持つエントリが見つかった場合、そのProcessEntry32
構造体とnil
エラーを返します。 - ループが終了してもプロセスが見つからない場合、または
Process32Next
がエラーを返した場合(通常は列挙の終了を示す)、エラーを返します。
-
Getppid() (ppid int)
関数: この関数は、Goのsyscall
パッケージが提供する公開APIです。Getpid()
を呼び出して、現在のプロセスのPIDを取得します。- 取得したPIDを
getProcessEntry
関数に渡し、現在のプロセスのProcessEntry32
構造体を取得します。 getProcessEntry
がエラーを返した場合(例: プロセスが見つからない、スナップショット作成失敗など)、Getppid()
は-1
を返します。- 成功した場合、取得した
ProcessEntry32
構造体のParentProcessID
フィールドをint
型にキャストして返します。
この実装により、Windows上でもGoプログラムが親プロセスIDを効率的かつ正確に取得できるようになります。ただし、Windowsの特性として、親プロセスが既に終了している場合、そのPIDが別のプロセスに再利用されている可能性があるため、取得されるPPIDが常に「真の」親プロセスを示すとは限らない点に注意が必要です。
関連リンク
- Go言語の
syscall
パッケージ: https://pkg.go.dev/syscall - Go言語の
os
パッケージ: https://pkg.go.dev/os - Microsoft Learn - Tool Help Library: https://learn.microsoft.com/en-us/windows/win32/toolhelp/tool-help-library
- Microsoft Learn -
CreateToolhelp32Snapshot
function: https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot - Microsoft Learn -
Process32First
function: https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-process32first - Microsoft Learn -
Process32Next
function: https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-process32next - Microsoft Learn -
PROCESSENTRY32
structure: https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32
参考にした情報源リンク
- Web検索結果 (Google Search)
- Go言語の公式ドキュメント
- Microsoft Learn (Windows APIドキュメント)
- Goのソースコード (特に
src/pkg/syscall
ディレクトリ) - Goのコードレビューシステム (Gerrit) の該当コミットページ