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

[インデックス 11783] ファイルの概要

このコミットは、Go言語の標準ライブラリにおけるosおよびsyscallパッケージの変更に関するものです。特に、Windowsビルドの修正に焦点を当てており、syscall.ProcAttr.Filesフィールドの型を[]int(またはWindows固有の[]Handle)から[]uintptrに変更することで、プロセス生成時のファイルディスクリプタ(Windowsではハンドル)の扱いを改善しています。

コミット

commit fbab6d8512c876dcef65e85f7a400117bc1f08f3
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Sat Feb 11 08:47:19 2012 +1100

    os,syscall: fix windows build
    
    make syscall.ProcAttr.Files be []uintptr
    
    all.bash passes on Linux.
    things seem to compile on GOOS={darwin,windows}
    
    R=golang-dev, mattn.jp, alex.brainman, rsc
    CC=golang-dev
    https://golang.org/cl/5653055

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fbab6d8512c876dcef65e85f7a400117bc1f08f3

元コミット内容

このコミットの主な目的は、Go言語のWindowsビルドにおける問題を修正することです。具体的には、syscall.ProcAttr構造体のFilesフィールドのデータ型を[]uintptrに変更することで、プロセス生成時に子プロセスに渡されるファイルディスクリプタ(Windowsではハンドル)の取り扱いを改善しています。コミットメッセージには、Linuxでのall.bashテストの成功と、Darwin(macOS)およびWindowsでのコンパイルが確認されたことが記されています。

変更の背景

Go言語はクロスプラットフォーム対応を重視しており、異なるオペレーティングシステム(OS)間で一貫した動作を提供することを目指しています。しかし、OSによってはファイルディスクリプタやプロセスハンドルといった低レベルのリソースの表現方法が異なります。

Windowsでは、ファイルやI/Oオブジェクトは「ハンドル」と呼ばれる抽象的な識別子で管理されます。これらのハンドルは通常、ポインタサイズに相当するuintptr_tのような型で表現されます。一方、Unix系OSでは、ファイルディスクリプタは通常int型の整数で表現されます。

Goのsyscall.ProcAttr構造体は、新しいプロセスを起動する際にそのプロセスに適用される属性(作業ディレクトリ、環境変数、そして継承されるファイルディスクリプタなど)を定義します。以前のバージョンでは、このFilesフィールドが[]int型(またはWindowsでは[]Handle型)として定義されていましたが、これがWindows環境でのビルドや実行時に問題を引き起こす可能性がありました。特に、int型がWindowsのハンドルを完全に表現できない場合(例えば、intが32ビットでハンドルが64ビットの場合など)に、データの切り捨てや不正な値の伝達が発生するリスクがありました。

このコミットは、このようなクロスプラットフォーム間の型の不一致に起因する問題を解決し、特にWindows環境でのos/execパッケージの安定性と正確性を確保するために行われました。

前提知識の解説

  • ファイルディスクリプタ (File Descriptor / FD): Unix系OSにおいて、開かれたファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。
  • ハンドル (Handle): Windows OSにおいて、ファイル、レジストリキー、プロセス、スレッドなどのシステムオブジェクトを識別するために使用される抽象的な識別子です。多くの場合、ポインタとして扱われるため、void*uintptr_tのような型で表現されます。
  • syscallパッケージ: Go言語の標準ライブラリの一部で、OS固有の低レベルなシステムコールへのアクセスを提供します。これにより、GoプログラムがOSの機能と直接対話できるようになります。
  • os/execパッケージ: Go言語で外部コマンドを実行するためのパッケージです。このパッケージは内部的にsyscallパッケージを利用して、新しいプロセスの生成やI/Oのリダイレクトなどを行います。
  • ProcAttr構造体: syscallパッケージ内で定義される構造体で、StartProcess関数などを用いて新しいプロセスを起動する際に、そのプロセスの属性(環境変数、作業ディレクトリ、継承するファイルディスクリプタなど)を設定するために使用されます。
  • uintptr: Go言語の組み込み型の一つで、ポインタのビットパターンを保持するのに十分な大きさの符号なし整数型です。これは、GoのポインタとC言語のポインタ(またはOSのハンドル)の間で値を変換する際に、型安全性を保ちつつ低レベルな操作を行うために使用されます。unsafe.Pointerと組み合わせて使用されることが多いですが、このコミットでは直接uintptrが使われています。

技術的詳細

このコミットの核心は、syscall.ProcAttr構造体のFilesフィールドの型を[]int(またはWindowsの[]Handle)から[]uintptrに変更した点にあります。

  • uintptrの採用理由:

    • クロスプラットフォーム互換性: uintptrはポインタのサイズを保持できるため、Windowsのハンドル(通常はポインタサイズ)とUnix系のファイルディスクリプタ(整数)の両方を安全に表現できます。これにより、異なるOS間での型の不一致による問題を回避し、より堅牢なクロスプラットフォームコードを実現します。
    • Windowsハンドルの正確な表現: Windowsのハンドルは、int型では表現しきれない場合があるため、uintptrを使用することで、ハンドルの値が正確に保持され、切り捨てや誤った解釈を防ぎます。
    • 低レベルAPIとの整合性: syscallパッケージはOSの低レベルAPIと直接対話するため、OSが期待する型(ポインタやハンドル)に合わせたuintptrの使用は、API呼び出しの正確性を高めます。
  • 変更の影響範囲:

    • syscall.ProcAttrの定義が変更されたため、この構造体を使用しているすべての箇所で、Filesフィールドへのアクセスや値の代入方法が調整されました。
    • 特に、os/execパッケージ内のプロセス起動ロジック(exec_posix.goexec_bsd.goexec_linux.goexec_windows.go)では、f.Fd()から取得したファイルディスクリプタ(またはハンドル)をuintptr型にキャストしてsysattr.Filesに追加する変更が行われました。
    • os/file_windows.goでは、NewFile関数を呼び出す際に、ファイルディスクリプタをuintptrにキャストして渡すように変更されています。
    • src/pkg/net/sendfile_windows.goでは、f.Fd()から取得した値をsyscall.Handleにキャストしてo.srcに代入する箇所が、syscall.Handleuintptrのエイリアスであるため、この変更の影響を受けています。
    • src/pkg/os/exec/exec_test.goでは、テストコード内でファイルディスクリプタを閉じる際に、直接syscall.Close(fd)を呼び出すのではなく、os.NewFile(fd, "").Close()を使用するように変更されました。これは、fduintptr型になったことに対応し、osパッケージのより高レベルな抽象化を通じて安全にリソースを解放するための変更と考えられます。

この変更により、Goのプロセス管理機能がWindows環境でより安定し、正確に動作するようになりました。

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

このコミットにおける主要なコード変更は、syscall.ProcAttr構造体のFilesフィールドの型定義と、それに関連するファイルディスクリプタの型変換です。

1. src/pkg/syscall/exec_unix.go (および src/pkg/syscall/exec_windows.go)

--- a/src/pkg/syscall/exec_unix.go
+++ b/src/pkg/syscall/exec_unix.go
@@ -101,9 +101,9 @@ type Credential struct {
 // ProcAttr holds attributes that will be applied to a new process started
 // by StartProcess.
 type ProcAttr struct {
-	Dir   string   // Current working directory.
-	Env   []string // Environment.
-	Files []int    // File descriptors.
+	Dir   string    // Current working directory.
+	Env   []string  // Environment.
+	Files []uintptr // File descriptors.
 	Sys   *SysProcAttr
 }

この変更は、syscall.ProcAttr構造体のFilesフィールドの型を[]intから[]uintptrに変更しています。Windows版のexec_windows.goでも同様に[]Handleから[]uintptrに変更されています。

2. src/pkg/os/exec_posix.go (および src/pkg/syscall/exec_bsd.go, src/pkg/syscall/exec_linux.go)

--- a/src/pkg/os/exec_posix.go
+++ b/src/pkg/os/exec_posix.go
@@ -38,7 +38,7 @@ func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e
 		sysattr.Env = Environ()
 	}
 	for _, f := range attr.Files {
-		sysattr.Files = append(sysattr.Files, int(f.Fd()))
+		sysattr.Files = append(sysattr.Files, f.Fd())
 	}
 
 	pid, h, e := syscall.StartProcess(name, argv, sysattr)

attr.Files[]uintptrになったため、f.Fd()の戻り値(uintptr)をintにキャストする必要がなくなりました。syscall/exec_bsd.gosyscall/exec_linux.goでは、attr.Filesからfdスライスを生成する際に、int(ufd)と明示的にキャストするループが追加されています。これは、内部的な処理でint型のファイルディスクリプタが必要なためです。

3. src/pkg/os/file_windows.go

--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -70,7 +70,7 @@ func openFile(name string, flag int, perm FileMode) (file *File, err error) {
 		syscall.CloseOnExec(r)
 	}
 
-	return NewFile(r, name), nil
+	return NewFile(uintptr(r), name), nil
 }
 
 func openDir(name string) (file *File, err error) {
@@ -79,7 +79,7 @@ func openDir(name string) (file *File, err error) {
 	if e != nil {
 		return nil, &PathError{"open", name, e}
 	}
-	f := NewFile(r, name)
+	f := NewFile(uintptr(r), name)
 	f.dirinfo = d
 	return f, nil
 }
@@ -313,7 +313,7 @@ func Pipe() (r *File, w *File, err error) {
 	syscall.CloseOnExec(p[1])
 	syscall.ForkLock.RUnlock()
 
-	return NewFile(p[0], "|0"), NewFile(p[1], "|1"), nil
+	return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
 }

Windows固有のファイル操作関数において、syscallパッケージから返されるハンドル(uintptr型)をos.NewFileに渡す際に、明示的にuintptrにキャストしています。

4. src/pkg/os/exec/exec_test.go

--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -17,7 +17,6 @@ import (
 	"runtime"
 	"strconv"
 	"strings"
-	"syscall"
 	"testing"
 )
 
@@ -153,8 +152,8 @@ func TestExtraFiles(t *testing.T) {
 
 	// Ensure that file descriptors have not already been leaked into
 	// our environment.
-	for fd := int(os.Stderr.Fd()) + 1; fd <= 101; fd++ {
-		err := syscall.Close(fd)
+	for fd := os.Stderr.Fd() + 1; fd <= 101; fd++ {
+		err := os.NewFile(fd, "").Close()
 		if err == nil {
 			t.Logf("Something already leaked - closed fd %d", fd)
 		}

テストコード内でファイルディスクリプタを閉じるロジックが変更されました。syscall.Close(fd)の直接呼び出しから、os.NewFile(fd, "").Close()というosパッケージの抽象化を通じた方法に変更されています。これは、fduintptr型になったことへの対応と、よりGoらしいリソース管理の方法への移行を示唆しています。

コアとなるコードの解説

このコミットの主要な変更は、syscall.ProcAttr.Filesの型を[]intから[]uintptrに変更したことです。

  • syscall.ProcAttr.Files []uintptr:

    • syscall.ProcAttrは、新しいプロセスを起動する際にそのプロセスに渡す属性を定義します。Filesフィールドは、子プロセスが継承するファイルディスクリプタ(またはWindowsのハンドル)のリストを保持します。
    • uintptr型は、ポインタのビットパターンを保持できる符号なし整数型であり、OSの低レベルAPIが扱うハンドルやポインタを安全に表現するのに適しています。
    • この変更により、特にWindows環境で、int型では表現しきれない可能性のあるハンドル値を正確に扱うことができるようになり、クロスプラットフォームでの互換性と堅牢性が向上しました。
  • f.Fd()の戻り値と型変換:

    • os.FileFd()メソッドは、基となるファイルディスクリプタ(またはハンドル)をuintptr型で返します。
    • 以前は、このuintptrintにキャストしてsyscall.ProcAttr.Files[]int)に追加していましたが、Files[]uintptrになったことで、この明示的なintへのキャストが不要になりました。これにより、コードがよりシンプルになり、潜在的な型変換エラーが減少します。
    • ただし、Unix系のsyscall/exec_bsd.gosyscall/exec_linux.goでは、内部的にint型のファイルディスクリプタが必要な処理があるため、uintptrからintへの明示的なキャストが引き続き行われています。これは、uintptrが汎用的なハンドル型として使われつつも、特定のOSのシステムコールが期待する具体的な整数型に変換する必要があるためです。
  • os.NewFile(fd, "").Close()への変更:

    • exec_test.goにおけるこの変更は、テストのクリーンアップ処理に関連しています。以前はsyscall.Close(fd)を直接呼び出していましたが、fduintptr型になったことと、よりGoらしいリソース管理の観点から変更されました。
    • os.NewFile(fd, "")は、与えられたファイルディスクリプタ(uintptr)から*os.Fileオブジェクトを作成します。
    • その*os.Fileオブジェクトに対してClose()メソッドを呼び出すことで、Goのosパッケージが提供する抽象化を通じて、ファイルディスクリプタが適切に閉じられます。これにより、OS固有のsyscall.Closeを直接呼び出すよりも、より安全で移植性の高い方法でリソースを解放できます。

これらの変更は、Go言語が低レベルなOSインタラクションを扱う際の堅牢性とクロスプラットフォーム互換性を高める上で重要なステップでした。

関連リンク

  • Go言語のsyscallパッケージドキュメント: https://pkg.go.dev/syscall
  • Go言語のos/execパッケージドキュメント: https://pkg.go.dev/os/exec
  • Go言語のuintptrに関する議論(Goの設計原則など): Goの公式ドキュメントやブログ記事でuintptrunsafe.Pointerに関する詳細な説明が見つかることがあります。

参考にした情報源リンク