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

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

このコミットは、Go言語の net パッケージにおけるWindowsビルドの問題を修正するものです。具体的には、ソケット作成時に syscall.ForkLock を適切に使用し、CloseOnExec フラグを設定することで、Windows環境での安定性と互換性を向上させています。

コミット

commit 2cd5b014544fd46a015acf3a37aea916bba19811
Author: Ian Lance Taylor <iant@golang.org>
Date:   Mon Jan 28 09:37:10 2013 -0800

    net: fix windows build
    
    R=golang-dev, minux.ma
    CC=golang-dev
    https://golang.org/cl/7229050
---
 src/pkg/net/sock_windows.go | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/pkg/net/sock_windows.go b/src/pkg/net/sock_windows.go
index cce6181c9e..fc5d9e5de2 100644
--- a/src/pkg/net/sock_windows.go
+++ b/src/pkg/net/sock_windows.go
@@ -41,3 +41,14 @@ func listenerSockaddr(s syscall.Handle, f int, la syscall.Sockaddr, toAddr func(\n 	}\n 	return la, nil\n }\n+\n+func sysSocket(f, t, p int) (syscall.Handle, error) {\n+\t// See ../syscall/exec_unix.go for description of ForkLock.\n+\tsyscall.ForkLock.RLock()\n+\ts, err := syscall.Socket(f, t, p)\n+\tif err == nil {\n+\t\tsyscall.CloseOnExec(s)\n+\t}\n+\tsyscall.ForkLock.RUnlock()\n+\treturn s, err\n+}\n

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

https://github.com/golang/go/commit/2cd5b014544fd46a015acf3a37aea916bba19811

元コミット内容

このコミットは、Go言語の net パッケージにおけるWindowsビルドの不具合を修正することを目的としています。具体的には、ソケットを作成する際に syscall.ForkLock を使用し、ソケットハンドルに CloseOnExec フラグを設定する sysSocket 関数を追加しています。これにより、子プロセスが予期せず親プロセスのソケットハンドルを継承してしまう問題を回避し、Windows環境でのネットワーク操作の堅牢性を高めています。

変更の背景

Go言語はクロスプラットフォーム対応を重視しており、Windows環境でも安定して動作することが求められます。このコミットが作成された2013年当時、GoのWindowsサポートはまだ発展途上にあり、特にシステムコールやプロセス管理に関する細かな挙動の違いが問題となることがありました。

Unix系システムでは、fork システムコールによって子プロセスが親プロセスのファイルディスクリプタ(ソケットも含む)を継承するのが一般的です。しかし、Windowsではプロセス作成のメカニズムが異なり、明示的にハンドルを継承させるか、継承させないかを制御する必要があります。Goのランタイムが内部的にプロセスを起動する際、親プロセスのソケットハンドルが意図せず子プロセスに継承されてしまうと、リソースリークや予期せぬ動作を引き起こす可能性がありました。

この問題に対処するため、ソケット作成時に CloseOnExec フラグを設定することが重要になります。CloseOnExec は、新しいプロセスが実行される際に、そのハンドルが自動的に閉じられるようにするフラグです。これにより、子プロセスが親プロセスのソケットハンドルを不必要に保持することを防ぎます。

また、syscall.ForkLock の使用は、Goランタイムが内部的に fork/exec に似た操作(Windowsでは CreateProcess など)を行う際に、競合状態を防ぐためのものです。特に、ソケットの作成のようなシステムリソースを操作する処理と、プロセスの生成が同時に行われる場合に、デッドロックや不正な状態に陥ることを避けるために導入されました。

前提知識の解説

1. システムコール (syscall)

システムコールは、オペレーティングシステム (OS) のカーネルが提供するサービスをプログラムが利用するためのインターフェースです。ファイル操作、ネットワーク通信、プロセス管理など、OSの機能にアクセスするために使用されます。Go言語の syscall パッケージは、これらのOS固有のシステムコールをGoプログラムから呼び出すための機能を提供します。

2. ソケット (Socket)

ソケットは、ネットワーク上でプロセス間通信を行うためのエンドポイントです。IPアドレスとポート番号の組み合わせによって識別され、データ送受信の窓口となります。Go言語の net パッケージは、ソケットを用いたネットワークプログラミングを抽象化して提供していますが、その内部ではOSのシステムコールを利用してソケットの作成や操作を行っています。

3. ファイルディスクリプタ / ハンドル (File Descriptor / Handle)

Unix系システムでは、ファイルやソケットなどのI/Oリソースは「ファイルディスクリプタ」と呼ばれる整数値で識別されます。一方、Windowsシステムでは「ハンドル」と呼ばれるポインタのような値で識別されます。これらは、プログラムがOSのリソースにアクセスするための参照です。

4. CloseOnExec フラグ

CloseOnExec (Close on Execute) は、ファイルディスクリプタやハンドルに設定できるフラグの一つです。このフラグが設定されたリソースは、新しいプログラムが exec (Unix系) や CreateProcess (Windows) などのシステムコールによって実行される際に、自動的に閉じられます。これにより、子プロセスが親プロセスの不要なリソースを継承することを防ぎ、リソースリークやセキュリティ上の問題を回避できます。

5. syscall.ForkLock

Go言語の syscall パッケージには ForkLock というミューテックス(排他制御機構)が存在します。これは、Goランタイムが内部的に fork/exec に似た操作を行う際に、特定のクリティカルセクションを保護するために使用されます。特に、OSのシステムコールを呼び出す際に、他のゴルーチン(Goの軽量スレッド)が同時にシステムコールを呼び出すことによる競合状態やデッドロックを防ぐ目的があります。Windows環境では fork は存在しませんが、Goランタイムは内部的にプロセス生成の際に同様の同期メカニズムを必要とすることがあります。

技術的詳細

このコミットの核心は、Windows環境におけるソケット作成の安全性を確保することにあります。

Goの net パッケージは、OSに依存しないネットワークAPIを提供しますが、その実装は各OSのシステムコールに依存しています。Windowsでは、ソケットは syscall.Socket 関数によって作成されます。この関数は、Unix系OSの socket システムコールに相当します。

追加された sysSocket 関数は、以下の重要な処理を行っています。

  1. syscall.ForkLock.RLock()syscall.ForkLock.RUnlock(): sysSocket 関数は、ソケットを作成する syscall.Socket の呼び出しを syscall.ForkLock のリードロック (RLock) で囲んでいます。これは、Goランタイムが新しいプロセスを生成する際に、ソケット作成のようなシステムリソースを操作する処理と競合しないようにするためです。リードロックを使用することで、複数のゴルーチンが同時にソケットを作成することは可能ですが、プロセス生成のような書き込み操作とは排他制御されます。これにより、プロセス生成とソケット作成の間のデッドロックや不正な状態を防ぎます。

  2. syscall.CloseOnExec(s): syscall.Socket が成功し、ソケットハンドル s が有効な場合に、syscall.CloseOnExec(s) が呼び出されます。この関数は、Windows APIの SetHandleInformation 関数を内部的に呼び出し、ソケットハンドル sHANDLE_FLAG_CLOSE_ON_EXEC フラグを設定します。このフラグが設定されると、このソケットハンドルは、Goプログラムが exec (Windowsでは CreateProcess に相当する操作) を介して新しいプロセスを実行する際に、自動的に閉じられます。これにより、子プロセスが親プロセスのソケットハンドルを意図せず継承してしまうことを防ぎ、リソースリークや予期せぬネットワーク動作を防ぎます。

この修正により、GoプログラムがWindows上でネットワーク通信を行いながら、同時に外部コマンドを実行したり、子プロセスを起動したりするようなシナリオにおいて、より堅牢で予測可能な動作が保証されるようになります。

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

変更は src/pkg/net/sock_windows.go ファイルに集中しており、以下の sysSocket 関数が追加されています。

func sysSocket(f, t, p int) (syscall.Handle, error) {
	// See ../syscall/exec_unix.go for description of ForkLock.
	syscall.ForkLock.RLock()
	s, err := syscall.Socket(f, t, p)
	if err == nil {
		syscall.CloseOnExec(s)
	}
	syscall.ForkLock.RUnlock()
	return s, err
}

コアとなるコードの解説

追加された sysSocket 関数は、Goの net パッケージがWindows上でソケットを作成する際の内部的なヘルパー関数として機能します。

  • f, t, p はそれぞれ、ソケットファミリー (e.g., syscall.AF_INET), ソケットタイプ (e.g., syscall.SOCK_STREAM), プロトコル (e.g., syscall.IPPROTO_TCP) を表す整数値です。これらは syscall.Socket 関数に直接渡されます。
  • syscall.ForkLock.RLock()syscall.ForkLock.RUnlock() は、ソケット作成処理の前後でリードロックを取得・解放しています。これにより、Goランタイムがプロセスを生成する際の内部的なロックと競合しないようにします。
  • s, err := syscall.Socket(f, t, p) は、実際にWindowsのシステムコールを呼び出してソケットを作成します。成功するとソケットハンドル s が返されます。
  • if err == nil { syscall.CloseOnExec(s) } は、ソケット作成が成功した場合にのみ、そのソケットハンドルに CloseOnExec フラグを設定します。これにより、このソケットが子プロセスに継承されないようにします。

この sysSocket 関数が導入されたことで、net パッケージ内の他の場所でソケットを作成する際に、直接 syscall.Socket を呼び出すのではなく、この sysSocket を経由するようになります。これにより、Windows環境でのソケット作成が常に安全な方法で行われることが保証されます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/pkg/net/sock_windows.go および src/pkg/syscall/exec_unix.go の関連部分)
  • Go言語のIssueトラッカーやメーリングリストでの議論 (当時のWindowsサポートに関する情報)
  • Windows APIのドキュメント (MSDN)
  • Unix系OSの fork, exec, fcntl (F_SETFD, FD_CLOEXEC) に関する一般的な知識
  • Go言語のクロスコンパイルとシステムコールに関する一般的な情報