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

[インデックス 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つの主要な変更が行われました。

  1. 低レベルな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のツールがシステムコールラッパーを生成するためのディレクティブです。

  2. 高レベルな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関連ファイルにも適用されています。

コアとなるコードの解説

上記の変更箇所を詳しく見ていきます。

  1. //sysnb socketpair(...) の変更: これはGoのgo generateツールがシステムコールラッパーを自動生成するために使用する特殊なコメントです。 fd *[2]int から fd *[2]int32 への変更は、Goの低レベルなsocketpair関数が、OSのsocketpairシステムコールに渡す引数の型を、明示的に32ビット整数配列へのポインタとして定義し直したことを意味します。これにより、Goのint型が64ビット幅になる環境でも、システムコールが期待する32ビット幅のファイルディスクリプタを正しく受け取れるようになります。

  2. func Socketpair(...) の変更: これはGoのユーザーが直接呼び出す高レベルなラッパー関数です。

    • var fdx [2]int32: まず、int32型の要素を2つ持つ配列fdxを宣言します。この配列が、低レベルなsocketpair関数(そして最終的にはOSのシステムコール)に渡される実際のデータ格納場所となります。
    • err = socketpair(domain, typ, proto, &fdx): 低レベルなsocketpair関数を呼び出し、fdxのアドレスを渡します。これにより、システムコールが成功した場合、ファイルディスクリプタがfdxint32型として格納されます。
    • if err == nil { fd[0] = int(fdx[0]); fd[1] = int(fdx[1]) }: システムコールがエラーなく完了した場合、fdxに格納されたint32の値を、Goのユーザーが期待するint型にキャストして、戻り値であるfd配列に代入します。このキャストは、int32intに収まることが保証されているため安全です。

この二段階のアプローチにより、Goのsyscallパッケージは、OSのシステムコールとのインターフェースの正確性を保ちつつ、Go言語の型システムと慣習に沿ったAPIを提供しています。これは、異なるアーキテクチャ間での互換性と、Go言語のユーザーフレンドリーな設計を両立させるための典型的なパターンです。

関連リンク

参考にした情報源リンク

  • Go言語のint型とint32型の違いに関する一般的な情報
  • Unix系OSにおけるsocketpairシステムコールのドキュメント
  • Go言語のsyscallパッケージの設計思想に関する情報 (Goの公式ドキュメントやブログ記事など)
  • Go言語のgo generateコマンドに関する情報
  • Go言語のクロスコンパイルとアーキテクチャ依存性に関する情報