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

[インデックス 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パッケージにラップされています。

  1. CreateToolhelp32Snapshot: プロセス情報のスナップショットを作成するために使用されます。
  2. Process32First: スナップショット内の最初のプロセスエントリを取得します。
  3. Process32Next: スナップショット内の次のプロセスエントリを順次取得します。

実装のロジックは以下の通りです。

  1. CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)を呼び出して、現在のシステム上の全プロセスのスナップショットを取得します。
  2. 取得したスナップショットハンドルをProcess32Firstに渡し、最初のプロセスエントリを取得します。
  3. ループ内でProcess32Nextを繰り返し呼び出し、各プロセスエントリを順次処理します。
  4. 各プロセスエントリのProcessIDが現在のプロセスのID(Getpid()で取得)と一致するかどうかを確認します。
  5. 一致するプロセスエントリが見つかった場合、そのエントリのParentProcessIDGetppid()の戻り値として返します。
  6. スナップショットの取得に失敗したり、現在のプロセスが見つからなかったりした場合は、エラーを示す値(-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()の実装です。

  1. 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がエラーを返した場合(通常は列挙の終了を示す)、エラーを返します。
  2. Getppid() (ppid int)関数: この関数は、Goのsyscallパッケージが提供する公開APIです。

    • Getpid()を呼び出して、現在のプロセスのPIDを取得します。
    • 取得したPIDをgetProcessEntry関数に渡し、現在のプロセスのProcessEntry32構造体を取得します。
    • getProcessEntryがエラーを返した場合(例: プロセスが見つからない、スナップショット作成失敗など)、Getppid()-1を返します。
    • 成功した場合、取得したProcessEntry32構造体のParentProcessIDフィールドをint型にキャストして返します。

この実装により、Windows上でもGoプログラムが親プロセスIDを効率的かつ正確に取得できるようになります。ただし、Windowsの特性として、親プロセスが既に終了している場合、そのPIDが別のプロセスに再利用されている可能性があるため、取得されるPPIDが常に「真の」親プロセスを示すとは限らない点に注意が必要です。

関連リンク

参考にした情報源リンク

  • Web検索結果 (Google Search)
  • Go言語の公式ドキュメント
  • Microsoft Learn (Windows APIドキュメント)
  • Goのソースコード (特にsrc/pkg/syscallディレクトリ)
  • Goのコードレビューシステム (Gerrit) の該当コミットページ