[インデックス 10404] ファイルの概要
このコミットは、Go言語の標準ライブラリにおけるWindows固有のシステムコール関連のコードベースに対する修正です。特に、先行するコミット d3963c0fca78 によって発生したビルドエラーを解消し、Windows環境でのGoプログラムの安定性を確保することを目的としています。osパッケージとsyscallパッケージ間の依存関係とAPIの整合性を再確立し、システムコール関連の定数や関数の参照方法を統一しています。
コミット
commit 0d37998a06b5f5fddbdbe0aed4cbb7536b4201f6
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Tue Nov 15 12:48:22 2011 -0500
syscall: make windows build again after d3963c0fca78 change
R=rsc, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/5373097
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0d37998a06b5f5fddbdbe0aed4cbb7536b4201f6
元コミット内容
このコミット自体は、d3963c0fca78というハッシュを持つ先行コミットによって引き起こされたビルド問題を修正するものです。したがって、このコミットの「元コミット内容」は、その先行コミットによって導入された変更が原因で発生した問題への対応となります。
変更の背景
このコミットの背景には、Go言語の標準ライブラリにおけるWindowsシステムコール(syscallパッケージ)のAPI設計と実装の進化があります。コミットメッセージに明記されているように、d3963c0fca78というコミットが先行して行われ、その変更がWindowsビルドに影響を与え、ビルドが不可能になる問題を引き起こしました。
先行するコミット d3963c0fca78 は、GoのsyscallパッケージにおけるWindows APIのラッパー関数群を、よりGoらしい(idiomatic Go)インターフェースに移行させるための大規模なリファクタリングであったと推測されます。具体的には、Windows APIの関数や定数をsyscallパッケージ内にカプセル化し、osパッケージなどの上位レイヤーからはsyscallパッケージを介してアクセスするように変更された可能性があります。
このリファクタリングにより、osパッケージ内で直接Windows API関数や定数を参照していた箇所が未定義となり、ビルドエラーが発生しました。本コミット 0d37998a06b5f5fddbdbe0aed4cbb7536b4201f6 は、このビルドエラーを解消するために、osパッケージ内の参照をsyscallパッケージ経由に変更し、APIの整合性を回復させることを目的としています。
前提知識の解説
Go言語のsyscallパッケージ
Go言語のsyscallパッケージは、オペレーティングシステム(OS)の低レベルな機能にアクセスするためのインターフェースを提供します。これには、ファイル操作、プロセス管理、ネットワーク通信、メモリ管理など、OSが提供するシステムコールやAPIのラッパーが含まれます。
- 目的: GoプログラムがOS固有の機能を利用できるようにすること。例えば、WindowsではWin32 API、LinuxではPOSIXシステムコールなど。
- クロスプラットフォーム性:
syscallパッケージ自体はOS固有のコードを含みますが、Goの標準ライブラリはこれらのsyscallパッケージを抽象化し、多くのOSで共通のインターフェース(例:osパッケージ)を提供します。しかし、特定のOSにしかない機能や、パフォーマンスが重要な場面ではsyscallパッケージを直接利用することがあります。 - 低レベルな操作:
syscallパッケージは、Goの他の高レベルなパッケージ(例:os,net)が内部的に利用する基盤となるものです。直接利用する際は、OSのAPIに関する深い知識が必要となる場合があります。
Windows APIとGo言語の連携
Windows APIは、Microsoft Windowsオペレーティングシステムが提供する関数、データ構造、定数の集合体です。Go言語からWindows APIを呼び出す場合、通常はsyscallパッケージがその橋渡しをします。
- UTF-16エンコーディング: Windows APIの多くは、文字列をUTF-16エンコーディングで扱います。Goの文字列はUTF-8エンコーディングであるため、Windows APIに渡す前にはUTF-16に変換し、APIからの戻り値もUTF-16からUTF-8に変換する必要があります。
unicode/utf16パッケージやsyscall.UTF16ToString、syscall.StringToUTF16などがこの変換を担います。 - ポインタ操作と
unsafeパッケージ: Windows APIはC言語ベースであり、ポインタを多用します。Goは通常、ポインタ演算を制限していますが、unsafeパッケージを使用することで、Goの型システムを迂回してポインタを直接操作することが可能になります。これは低レベルなシステムコールや外部Cライブラリとの連携で必要となることがありますが、Goのメモリ安全性を損なう可能性があるため、慎重な利用が求められます。
Errno型
Errnoは、システムコールが返すエラーコードを表すGoの型です。通常、OS固有のエラーコード(例: WindowsのERROR_FILE_NOT_FOUND)をGoのerrorインターフェースに適合させるために使用されます。Errno型を明示的に導入することで、エラーコードの型安全性が向上し、コードの可読性と保守性が高まります。
技術的詳細
このコミットは、主に以下の技術的変更を含んでいます。
-
osパッケージからsyscallパッケージへの依存関係の明確化:src/pkg/os/exec_windows.goでは、GetCommandLine、CommandLineToArgv、LocalFree、UTF16ToStringといった関数が、以前は直接利用されていたか、osパッケージ内で定義されていたものが、syscallパッケージのプレフィックス(syscall.)を付けて呼び出されるように変更されました。これは、これらの関数がsyscallパッケージの責務であることを明確にし、APIの場所を統一するリファクタリングの一環です。src/pkg/os/file_windows.goでは、MAX_PATH定数がsyscall.MAX_PATHに変更されました。これも同様に、OS固有の定数をsyscallパッケージに集約する動きです。- これらの変更に伴い、
src/pkg/os/exec_windows.goにはunsafeパッケージがインポートされました。これは、CommandLineToArgvが返すポインタをGoの型に変換するためにunsafe.Pointerが必要になったためと考えられます。
-
ランタイムレベルでの環境変数アクセス変更:
src/pkg/runtime/windows/thread.cでは、環境変数を保持するスライスがos·Envsからsyscall·envsに変更されました。これは、GoランタイムがWindowsの環境変数にアクセスする内部的なメカニズムが、osパッケージの内部構造からsyscallパッケージの内部構造へと移行したことを示唆しています。これにより、環境変数管理の責務がsyscallパッケージに一元化され、より低レベルなOSインタラクションの整合性が保たれます。
-
エラーコード生成スクリプトの変更:
src/pkg/syscall/mkerrors_windows.shでは、WindowsエラーコードをGoの定数として生成するスクリプトが修正されました。生成される定数に明示的にErrno型が付与されるようになりました(例:ERROR_FILE_NOT_FOUNDがERROR_FILE_NOT_FOUND Errnoとなる)。これにより、Goコード内でこれらのエラーコードを扱う際の型安全性が向上し、コンパイラによるチェックが強化されます。- また、
src/pkg/syscall/zerrors_windows.goのコメントから-fフラグが削除されました。これは、mkerrors_windows.shスクリプトの動作変更、または特定のフラグが不要になったことを示しています。
-
Sleep関数の削除:src/pkg/syscall/syscall_windows.goからSleep関数が削除されました。これは、Goの標準ライブラリにおいて、時間待機処理がtimeパッケージなどのより高レベルな抽象化に移行したことを示唆しています。syscallパッケージはOSのプリミティブな機能を提供する場であり、Sleepのような一般的なユーティリティ関数は、より汎用的なパッケージで提供されるべきという設計思想の変更があった可能性があります。
コアとなるコードの変更箇所
src/pkg/os/exec_windows.go
--- a/src/pkg/os/exec_windows.go
+++ b/src/pkg/os/exec_windows.go
@@ -8,6 +8,7 @@ import (
"errors"
"runtime"
"syscall"
+ "unsafe"
)
func (p *Process) Wait(options int) (w *Waitmsg, err error) {
@@ -68,14 +69,14 @@ func FindProcess(pid int) (p *Process, err error) {
func init() {
var argc int32
- cmd := GetCommandLine()
- argv, e := CommandLineToArgv(cmd, &argc)
+ cmd := syscall.GetCommandLine()
+ argv, e := syscall.CommandLineToArgv(cmd, &argc)
if e != nil {
return
}
- defer LocalFree(Handle(uintptr(unsafe.Pointer(argv))))
+ defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
Args = make([]string, argc)
for i, v := range (*argv)[:argc] {
- Args[i] = string(UTF16ToString((*v)[:]))
+ Args[i] = string(syscall.UTF16ToString((*v)[:]))
}
}
src/pkg/os/file_windows.go
--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -9,6 +9,7 @@ import (
"runtime"
"sync"
"syscall"
+ "unicode/utf16"
)
// File represents an open file descriptor.
@@ -299,7 +300,7 @@ func Pipe() (r *File, w *File, err error) {
// TempDir returns the default directory to use for temporary files.
func TempDir() string {
const pathSep = '\\'
- dirw := make([]uint16, MAX_PATH)
+ dirw := make([]uint16, syscall.MAX_PATH)
n, _ := syscall.GetTempPath(uint32(len(dirw)), &dirw[0])
if n > uint32(len(dirw)) {
dirw = make([]uint16, n)
src/pkg/runtime/windows/thread.c
--- a/src/pkg/runtime/windows/thread.c
+++ b/src/pkg/runtime/windows/thread.c
@@ -81,7 +81,7 @@ runtime·osinit(void)
void
runtime·goenvs(void)
{
- extern Slice os·Envs;
+ extern Slice syscall·envs;
uint16 *env;
String *s;
@@ -101,9 +101,9 @@ runtime·goenvs(void)
s[i] = runtime·gostringw(p);
p += runtime·findnullw(p)+1;
}
- os·Envs.array = (byte*)s;
- os·Envs.len = n;
- os·Envs.cap = n;
+ syscall·envs.array = (byte*)s;
+ syscall·envs.len = n;
+ syscall·envs.cap = n;
runtime·stdcall(runtime·FreeEnvironmentStringsW, 1, env);
}
src/pkg/syscall/mkerrors_windows.sh
--- a/src/pkg/syscall/mkerrors_windows.sh
+++ b/src/pkg/syscall/mkerrors_windows.sh
@@ -158,7 +158,7 @@ main(void)
printf("\n// Go names for Windows errors.\n");
printf("const (\n");
for(i=0; i<nelem(goerrors); i++) {
- printf("\t%s = %s\n", goerrors[i].goname, goerrors[i].winname);
+ printf("\t%s Errno = %s\n", goerrors[i].goname, goerrors[i].winname);
}
printf(")\n");
@@ -171,7 +171,7 @@ main(void)
for(i=0; i<nelem(errors); i++) {
printf("\t%s", errors[i].name);
if(iota) {
- printf(" = APPLICATION_ERROR + iota");
+ printf(" Errno = APPLICATION_ERROR + iota");
iota = !iota;
}
printf("\n");
src/pkg/syscall/syscall_windows.go
--- a/src/pkg/syscall/syscall_windows.go
+++ b/src/pkg/syscall/syscall_windows.go
@@ -357,11 +357,6 @@ func Gettimeofday(tv *Timeval) (err error) {
return nil
}
-func Sleep(nsec int64) (err error) {
- sleep(uint32((nsec + 1e6 - 1) / 1e6)) // round up to milliseconds
- return nil
-}
-
func Pipe(p []Handle) (err error) {
if len(p) != 2 {
return EINVAL
コアとなるコードの解説
src/pkg/os/exec_windows.go と src/pkg/os/file_windows.go の変更
これらのファイルでは、Windows APIに関連する関数呼び出しや定数参照にsyscallパッケージのプレフィックスが追加されました。これは、Goの標準ライブラリにおけるAPIの整理と、責務の明確化を目的としています。
GetCommandLine()->syscall.GetCommandLine(): プロセス起動時のコマンドライン文字列を取得するWindows APIのラッパー。CommandLineToArgv()->syscall.CommandLineToArgv(): コマンドライン文字列を引数配列に変換するWindows APIのラッパー。LocalFree()->syscall.LocalFree(): Windows APIで割り当てられたメモリを解放する関数。UTF16ToString()->syscall.UTF16ToString(): UTF-16エンコードされたバイトスライスをGoのUTF-8文字列に変換する関数。MAX_PATH->syscall.MAX_PATH: Windowsにおけるパスの最大長を示す定数。
これらの変更により、osパッケージはWindows固有の低レベルな詳細から切り離され、より汎用的なOS操作のインターフェースに集中できるようになります。unsafeパッケージのインポートは、CommandLineToArgvが返すポインタをGoの型システムで安全に扱えるようにするためのものです。
src/pkg/runtime/windows/thread.c の変更
C言語で書かれたGoランタイムのコードにおいて、環境変数を保持する内部スライスがos·Envsからsyscall·envsに変更されました。これは、GoランタイムがOSの環境変数にアクセスする際の内部的なデータ構造が、osパッケージの管理下からsyscallパッケージの管理下へと移行したことを意味します。これにより、Goのランタイムとsyscallパッケージ間の連携がより密接になり、Windows環境における環境変数管理のロジックが一元化されます。
src/pkg/syscall/mkerrors_windows.sh の変更
このシェルスクリプトは、WindowsのエラーコードをGoの定数として自動生成するために使用されます。変更点としては、生成される定数に明示的にErrno型が付与されるようになりました。
例:
// 変更前
printf("\t%s = %s\n", goerrors[i].goname, goerrors[i].winname);
// 変更後
printf("\t%s Errno = %s\n", goerrors[i].goname, goerrors[i].winname);
この変更により、生成されるGoのコードでは、エラーコードが単なる整数値ではなく、syscall.Errnoという特定の型を持つようになります。これにより、コンパイラが型チェックを厳密に行えるようになり、誤った型のエラーコードが使用されることを防ぎ、コードの堅牢性が向上します。
src/pkg/syscall/syscall_windows.go の Sleep 関数削除
syscallパッケージからSleep関数が削除されました。これは、Goの標準ライブラリの設計思想の変化を反映している可能性があります。Sleepのような汎用的な時間待機機能は、OSの低レベルなシステムコールを直接ラップするsyscallパッケージよりも、より高レベルな抽象化を提供するtimeパッケージ(例: time.Sleep)で提供されるべきであるという考えに基づいていると考えられます。これにより、syscallパッケージは純粋にOSのプリミティブな機能に特化し、よりクリーンなAPI設計が実現されます。
関連リンク
- Go言語の
syscallパッケージに関する公式ドキュメント: https://pkg.go.dev/syscall - Go言語の
osパッケージに関する公式ドキュメント: https://pkg.go.dev/os - Go言語の
unsafeパッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語の
unicode/utf16パッケージに関する公式ドキュメント: https://pkg.go.dev/unicode/utf16
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Gerrit Change-ID 5373097: https://golang.org/cl/5373097 (このコミットの元のコードレビューページ)
- Windows APIに関するMicrosoftのドキュメント (例:
GetCommandLine): https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getcommandlinew - Windows APIに関するMicrosoftのドキュメント (例:
CommandLineToArgvW): https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw - Windows APIに関するMicrosoftのドキュメント (例:
LocalFree): https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree - Windows APIに関するMicrosoftのドキュメント (例:
GetTempPath): https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw - Go言語における
unsafeパッケージの利用に関する議論や記事 (一般的な情報源) - Go言語におけるエラーハンドリングと
Errnoの利用に関する議論や記事 (一般的な情報源) - Go言語の標準ライブラリの設計原則に関する議論や記事 (一般的な情報源)