[インデックス 13916] ファイルの概要
コミット
commit 3d5ddffa306dff2a4e3546a0421191fac45ed549
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 24 00:06:22 2012 -0400
syscall: prepare for 64-bit ints
This CL fixes code that incorrectly assumes that int is 32 bits wide.
Specifically, the socketpair system call expects a pointer to a pair
of int32s, not a pair of ints. Fix this inside the wrappers without
changing the APIs.
Update #2188.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6552063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3d5ddffa306dff2a4e3546a0421191fac45ed549
元コミット内容
このコミットは、Go言語のsyscall
パッケージにおいて、int
型が常に32ビット幅であるという誤った仮定を修正するものです。具体的には、socketpair
システムコールが、2つのint32
型へのポインタを期待しているにもかかわらず、2つのint
型へのポインタを受け取るようにコードが書かれていた問題を解決します。この修正は、既存のAPIを変更することなく、ラッパー内部で適切に型を扱うように行われています。
この変更は、Issue #2188に関連しています。
変更の背景
Go言語はクロスプラットフォーム対応を重視しており、様々なアーキテクチャで動作します。しかし、C言語などと同様に、int
型のようなプリミティブ型のサイズは、コンパイルされるアーキテクチャ(32ビットシステムか64ビットシステムかなど)によって異なる場合があります。
このコミットが作成された2012年当時、Goのsyscall
パッケージは、システムコールを呼び出すためのGo言語側のラッパーを提供していました。socketpair
システムコールは、Unix系OSでソケットペアを作成するために使用され、通常、ファイルディスクリプタのペアを格納するための整数型の配列へのポインタを引数として受け取ります。
問題は、Goのint
型が32ビットシステムでは32ビット、64ビットシステムでは64ビットとなるのに対し、socketpair
システムコールが期待するファイルディスクリプタの型が、多くのシステムで32ビット整数(int32
)であったことです。もしGoのint
型が64ビットシステムで64ビット幅になった場合、socketpair
に渡されるポインタの指す先のデータ構造がシステムコールの期待するものと異なり、予期せぬ動作やクラッシュを引き起こす可能性がありました。
このコミットは、このような将来的な64ビットシステムへの対応、および既存の32ビットシステムでの正確な動作を保証するために、socketpair
システムコールへの引数の型を明示的にint32
に修正することを目的としています。これにより、Goのint
型のサイズに依存しない、より堅牢なsyscall
パッケージが実現されます。
前提知識の解説
1. システムコール (System Call)
システムコールは、オペレーティングシステム (OS) のカーネルが提供するサービスを、ユーザー空間のプログラムが利用するためのインターフェースです。ファイル操作、メモリ管理、プロセス制御、ネットワーク通信など、OSの機能にアクセスする際に使用されます。Go言語のsyscall
パッケージは、これらのシステムコールをGoプログラムから呼び出すためのラッパー関数を提供します。
2. socketpair
システムコール
socketpair
は、Unix系OSで利用可能なシステムコールで、互いに接続されたソケットのペア(双方向通信が可能な2つのソケット)を作成します。主にプロセス間通信 (IPC) に使用されます。
socketpair
の典型的なシグネチャは以下のようになります(C言語の例):
int socketpair(int domain, int type, int protocol, int sv[2]);
ここで、sv[2]
は2つの整数を格納するための配列であり、作成されたソケットのファイルディスクリプタがこの配列に格納されます。多くのシステムでは、ファイルディスクリプタは32ビット整数で表現されます。
3. Go言語の整数型 (int
, int32
, int64
など)
Go言語には、固定幅の整数型と、アーキテクチャ依存の整数型があります。
int
: これはアーキテクチャに依存する整数型です。32ビットシステムでは32ビット幅、64ビットシステムでは64ビット幅になります。これはC言語のint
型と同様の特性を持ちます。int8
,int16
,int32
,int64
: これらは固定幅の整数型です。それぞれ8ビット、16ビット、32ビット、64ビットの符号付き整数を表します。これらの型は、異なるアーキテクチャ間での互換性を保証する必要がある場合や、特定のビット幅が要求されるシステムコールとのインターフェースで特に重要になります。
4. ポインタと型の一致
システムコールにポインタを渡す際、ポインタが指す先のデータの型とサイズが、システムコールが期待する型とサイズに厳密に一致している必要があります。もし一致しない場合、メモリの読み書きが不正な領域で行われたり、データが正しく解釈されなかったりして、プログラムのクラッシュや予期せぬ動作につながります。
技術的詳細
このコミットの核心は、Goのint
型がアーキテクチャによってサイズが変動するのに対し、socketpair
システムコールが期待するファイルディスクリプタの型が多くのシステムで固定の32ビット整数であるというミスマッチを解消することにあります。
Goのsyscall
パッケージでは、システムコールを直接呼び出すための低レベルな関数(例: socketpair
)と、それらをラップしてGoらしいAPIを提供する高レベルな関数(例: Socketpair
)が存在します。
元のコードでは、低レベルなsocketpair
関数が*[2]int
(2つのint
型要素を持つ配列へのポインタ)を引数として受け取っていました。これは、32ビットシステムでは問題ありませんでしたが、64ビットシステムでint
が64ビット幅になると、socketpair
システムコールが期待するint32
の配列とは異なる構造になってしまいます。
このコミットでは、以下の2つの主要な変更が行われました。
-
低レベルな
socketpair
関数のシグネチャ変更://sysnb socketpair(domain int, typ int, proto int, fd *[2]int)
から//sysnb socketpair(domain int, typ int, proto int, fd *[2]int32)
へと変更されました。これにより、システムコールに渡されるポインタが、期待されるint32
の配列を指すことが保証されます。//sysnb
はGoのツールがシステムコールラッパーを生成するためのディレクティブです。 -
高レベルな
Socketpair
ラッパー関数の内部での型変換:func Socketpair(domain, typ, proto int) (fd [2]int, err error)
この関数は、Goのユーザーに対しては引き続き[2]int
型の配列を返します。しかし、内部では[2]int32
型のfdx
という一時変数を宣言し、低レベルなsocketpair
関数にこのfdx
へのポインタを渡します。システムコールが成功した場合、fdx
に格納されたint32
の値を、Goのint
型にキャストして最終的な戻り値fd
にコピーします。
このアプローチにより、syscall
パッケージの内部実装はシステムコールの期待する型に厳密に合わせつつ、Goのユーザーが利用するAPI(Socketpair
関数)は、Goの慣習的なint
型を維持できるため、既存のコードへの影響を最小限に抑えることができます。これは、Goのsyscall
パッケージが、OS固有の低レベルな詳細を抽象化し、Goプログラムに統一されたインターフェースを提供するという設計思想に合致しています。
コアとなるコードの変更箇所
このコミットでは、主にsrc/pkg/syscall/
ディレクトリ内の複数のファイルが変更されています。特に重要な変更は、socketpair
システムコールをラップするGoの関数定義と、その呼び出し部分です。
src/pkg/syscall/syscall_bsd.go
および src/pkg/syscall/syscall_linux.go
の変更例
--- a/src/pkg/syscall/syscall_bsd.go
+++ b/src/pkg/syscall/syscall_bsd.go
@@ -362,10 +362,15 @@ func Socket(domain, typ, proto int) (fd int, err error) {
return
}
-//sysnb socketpair(domain int, typ int, proto int, fd *[2]int) (err error)
+//sysnb socketpair(domain int, typ int, proto int, fd *[2]int32) (err error)
func Socketpair(domain, typ, proto int) (fd [2]int, err error) {
-\terr = socketpair(domain, typ, proto, &fd)
+\tvar fdx [2]int32
+\terr = socketpair(domain, typ, proto, &fdx)
+\tif err == nil {
+\t\tfd[0] = int(fdx[0])
+\t\tfd[1] = int(fdx[1])
+\t}
return
}
src/pkg/syscall/syscall_linux_386.go
などのアーキテクチャ固有ファイルの変更例
--- a/src/pkg/syscall/syscall_linux_386.go
+++ b/src/pkg/syscall/syscall_linux_386.go
@@ -193,7 +193,7 @@ func getpeername(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (err error) {
return
}
-func socketpair(domain int, typ int, flags int, fd *[2]int) (err error) {
+func socketpair(domain int, typ int, flags int, fd *[2]int32) (err error) {
_, e := rawsocketcall(_SOCKETPAIR, uintptr(domain), uintptr(typ), uintptr(flags), uintptr(unsafe.Pointer(fd)), 0, 0)
if e != 0 {
err = e
同様の変更が、zsyscall_darwin_386.go
, zsyscall_darwin_amd64.go
, zsyscall_freebsd_386.go
, zsyscall_freebsd_amd64.go
, zsyscall_linux_amd64.go
, zsyscall_linux_arm.go
, zsyscall_netbsd_386.go
, zsyscall_netbsd_amd64.go
, zsyscall_openbsd_386.go
, zsyscall_openbsd_amd64.go
など、各OSおよびアーキテクチャ固有のsyscall
関連ファイルにも適用されています。
コアとなるコードの解説
上記の変更箇所を詳しく見ていきます。
-
//sysnb socketpair(...)
の変更: これはGoのgo generate
ツールがシステムコールラッパーを自動生成するために使用する特殊なコメントです。fd *[2]int
からfd *[2]int32
への変更は、Goの低レベルなsocketpair
関数が、OSのsocketpair
システムコールに渡す引数の型を、明示的に32ビット整数配列へのポインタとして定義し直したことを意味します。これにより、Goのint
型が64ビット幅になる環境でも、システムコールが期待する32ビット幅のファイルディスクリプタを正しく受け取れるようになります。 -
func Socketpair(...)
の変更: これはGoのユーザーが直接呼び出す高レベルなラッパー関数です。var fdx [2]int32
: まず、int32
型の要素を2つ持つ配列fdx
を宣言します。この配列が、低レベルなsocketpair
関数(そして最終的にはOSのシステムコール)に渡される実際のデータ格納場所となります。err = socketpair(domain, typ, proto, &fdx)
: 低レベルなsocketpair
関数を呼び出し、fdx
のアドレスを渡します。これにより、システムコールが成功した場合、ファイルディスクリプタがfdx
にint32
型として格納されます。if err == nil { fd[0] = int(fdx[0]); fd[1] = int(fdx[1]) }
: システムコールがエラーなく完了した場合、fdx
に格納されたint32
の値を、Goのユーザーが期待するint
型にキャストして、戻り値であるfd
配列に代入します。このキャストは、int32
がint
に収まることが保証されているため安全です。
この二段階のアプローチにより、Goのsyscall
パッケージは、OSのシステムコールとのインターフェースの正確性を保ちつつ、Go言語の型システムと慣習に沿ったAPIを提供しています。これは、異なるアーキテクチャ間での互換性と、Go言語のユーザーフレンドリーな設計を両立させるための典型的なパターンです。
関連リンク
- Go Issue #2188: https://github.com/golang/go/issues/2188
- Go CL 6552063: https://golang.org/cl/6552063
参考にした情報源リンク
- Go言語の
int
型とint32
型の違いに関する一般的な情報 - Unix系OSにおける
socketpair
システムコールのドキュメント - Go言語の
syscall
パッケージの設計思想に関する情報 (Goの公式ドキュメントやブログ記事など) - Go言語の
go generate
コマンドに関する情報 - Go言語のクロスコンパイルとアーキテクチャ依存性に関する情報