[インデックス 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言語の標準ライブラリの設計原則に関する議論や記事 (一般的な情報源)