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

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

このコミットは、Go言語のsyscallパッケージにおけるLinux版のDup2システムコールラッパーの挙動を、他のオペレーティングシステム(OS)の挙動と一致させるための変更です。具体的には、Dup2関数がファイルディスクリプタを返すのではなく、エラーのみを返すように修正されています。これにより、newfd-1の場合にDupを呼び出すべきという、Linux固有の特殊なケースが解消され、APIの一貫性が向上します。

コミット

commit 65ba8ee07e69d52a4452f3d3d7e2ffdb1ab79802
Author: Russ Cox <rsc@golang.org>
Date:   Mon Feb 13 00:11:36 2012 -0500

    syscall: make linux Dup2 match other systems
    
    You could argue for changing all the others, but
    Linux is outvoted, and the only time it matters
    is when newfd==-1, in which case you can call Dup.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5650073
---
 src/pkg/syscall/syscall_linux.go        | 2 +-\n src/pkg/syscall/zsyscall_linux_386.go   | 5 ++---\n src/pkg/syscall/zsyscall_linux_amd64.go | 5 ++---\n src/pkg/syscall/zsyscall_linux_arm.go   | 5 ++---\n 4 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/pkg/syscall/syscall_linux.go b/src/pkg/syscall/syscall_linux.go
index d0e16271d5..10fbc17a02 100644
--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -802,7 +802,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
 //sys	Close(fd int) (err error)\n //sys	Creat(path string, mode uint32) (fd int, err error)\n //sysnb	Dup(oldfd int) (fd int, err error)\n-//sysnb	Dup2(oldfd int, newfd int) (fd int, err error)\n+//sysnb	Dup2(oldfd int, newfd int) (err error)\n //sysnb	EpollCreate(size int) (fd int, err error)\n //sysnb	EpollCreate1(flag int) (fd int, err error)\n //sysnb	EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)\ndiff --git a/src/pkg/syscall/zsyscall_linux_386.go b/src/pkg/syscall/zsyscall_linux_386.go
index 8c3a844754..4910b3c5fb 100644
--- a/src/pkg/syscall/zsyscall_linux_386.go
+++ b/src/pkg/syscall/zsyscall_linux_386.go
@@ -210,9 +210,8 @@ func Dup(oldfd int) (fd int, err error) {\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n-func Dup2(oldfd int, newfd int) (fd int, err error) {\n-\tr0, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n-\tfd = int(r0)\n+func Dup2(oldfd int, newfd int) (err error) {\n+\t_, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n \tif e1 != 0 {\n \t\terr = e1\n \t}\ndiff --git a/src/pkg/syscall/zsyscall_linux_amd64.go b/src/pkg/syscall/zsyscall_linux_amd64.go
index c53fff7a27..2260c4ef2d 100644
--- a/src/pkg/syscall/zsyscall_linux_amd64.go
+++ b/src/pkg/syscall/zsyscall/linux_amd64.go
@@ -210,9 +210,8 @@ func Dup(oldfd int) (fd int, err error) {\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n-func Dup2(oldfd int, newfd int) (fd int, err error) {\n-\tr0, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n-\tfd = int(r0)\n+func Dup2(oldfd int, newfd int) (err error) {\n+\t_, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n \tif e1 != 0 {\n \t\terr = e1\n \t}\ndiff --git a/src/pkg/syscall/zsyscall_linux_arm.go b/src/pkg/syscall/zsyscall_linux_arm.go
index d739139dce..56878721d3 100644
--- a/src/pkg/syscall/zsyscall_linux_arm.go
+++ b/src/pkg/syscall/zsyscall_linux_arm.go
@@ -210,9 +210,8 @@ func Dup(oldfd int) (fd int, err error) {\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n-func Dup2(oldfd int, newfd int) (fd int, err error) {\n-\tr0, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n-\tfd = int(r0)\n+func Dup2(oldfd int, newfd int) (err error) {\n+\t_, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n \tif e1 != 0 {\n \t\terr = e1\n \t}\n```

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

[https://github.com/golang/go/commit/65ba8ee07e69d52a4452f3d3d7e2ffdb1ab79802](https://github.com/golang/go/commit/65ba8ee07e69d52a4452f3d3d7e2ffdb1ab79802)

## 元コミット内容

このコミットの目的は、Go言語の`syscall`パッケージにおけるLinux版の`Dup2`システムコールラッパーの動作を、他のオペレーティングシステム(OS)の動作と整合させることです。具体的には、`Dup2`関数がファイルディスクリプタを返すのではなく、エラーのみを返すように変更されます。これにより、`newfd`が`-1`の場合に`Dup`を呼び出すというLinux固有の特殊なケースが不要になり、APIの一貫性が保たれます。

## 変更の背景

`Dup2`システムコールは、既存のファイルディスクリプタ(`oldfd`)を、指定された新しいファイルディスクリプタ(`newfd`)に複製する機能を提供します。この操作により、`newfd`が`oldfd`と同じファイルまたはI/Oリソースを参照するようになります。

多くのUnix系OSでは、`Dup2`システムコールは成功した場合に`newfd`の値を返し、失敗した場合にエラーを返します。しかし、Linuxの`Dup2`システムコールは、成功した場合に複製されたファイルディスクリプタ(つまり`newfd`と同じ値)を返すという点で、他のシステムと異なっていました。

この差異は、特に`newfd`に`-1`が指定された場合に問題を引き起こしていました。通常、`newfd`に`-1`を指定することは無効な操作ですが、Linuxではこの場合に`Dup`システムコール(新しいファイルディスクリプタを割り当てる)の挙動を模倣することが可能でした。しかし、これは他のOSの`Dup2`のセマンティクスとは異なり、Go言語の`syscall`パッケージが提供するクロスプラットフォームな抽象化において不整合を生じさせていました。

コミットメッセージにある「Linux is outvoted」という表現は、このLinux固有の挙動がGo言語の標準的な`Dup2`のインターフェースと合致しないため、Linuxの挙動を他のシステムに合わせるという決定が下されたことを示しています。Go言語の`syscall`パッケージは、可能な限りOS間の差異を吸収し、統一されたインターフェースを提供することを目指しています。この変更は、その目標に沿ったものです。

## 前提知識の解説

### ファイルディスクリプタ (File Descriptor, FD)

ファイルディスクリプタは、Unix系OSにおいて、開かれたファイルやI/Oデバイス(ソケット、パイプなど)を参照するために使用される非負の整数です。プログラムがファイルを開いたり、ソケット接続を確立したりすると、OSは対応するファイルディスクリプタを返します。このディスクリプタを使って、プログラムは読み書きなどの操作を行います。

標準的なファイルディスクリプタとして、以下の3つが予約されています。
*   `0`: 標準入力 (stdin)
*   `1`: 標準出力 (stdout)
*   `2`: 標準エラー出力 (stderr)

### `dup` と `dup2` システムコール

*   **`dup(oldfd)`**:
    `dup`システムコールは、`oldfd`が参照するファイルまたはI/Oリソースに対して、新しいファイルディスクリプタを割り当てて返します。新しく割り当てられるファイルディスクリプタは、利用可能な最小の非負の整数になります。`oldfd`と新しいファイルディスクリプタは、同じファイルオフセット、ファイルステータスフラグ、およびアクセスモードを共有します。

*   **`dup2(oldfd, newfd)`**:
    `dup2`システムコールは、`oldfd`が参照するファイルまたはI/Oリソースを、`newfd`が参照するように複製します。
    1.  もし`newfd`が既に開かれている場合、`newfd`はまず閉じられます。
    2.  次に、`newfd`は`oldfd`と同じファイルまたはI/Oリソースを参照するように設定されます。
    3.  `oldfd`と`newfd`が同じ値の場合、`dup2`は何もしません(`newfd`を閉じたり開いたりしない)が、成功を返します。
    `dup2`は、特定のファイルディスクリプタ(例えば標準出力)を別のファイルやソケットにリダイレクトする際によく使用されます。

### Go言語の `syscall` パッケージ

Go言語の標準ライブラリには、低レベルのシステムコールにアクセスするための`syscall`パッケージが含まれています。このパッケージは、OS固有の機能に直接アクセスする必要がある場合(例えば、ファイルディスクリプタの操作、プロセス管理、ネットワークインターフェースの設定など)に使用されます。

`syscall`パッケージは、各OSのシステムコールをGoの関数としてラップしており、OS間の差異を吸収するための抽象化レイヤーを提供しています。しかし、完全に差異を隠蔽することは難しく、OS固有の挙動がGoのAPIに影響を与えることがあります。

### `RawSyscall` 関数

`syscall`パッケージ内で、実際のシステムコールを呼び出すために使用されるのが`RawSyscall`関数です。この関数は、システムコール番号と引数を受け取り、システムコールの戻り値とエラーを返します。

`RawSyscall`の一般的なシグネチャは以下のようになります(Goの内部実装に依存しますが、概念的には):
```go
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
  • trap: システムコール番号(例: SYS_DUP2)。
  • a1, a2, a3: システムコールに渡される引数。
  • r1, r2: システムコールからの戻り値。通常、r1が主要な戻り値(例えば、新しいファイルディスクリプタや成功/失敗を示す値)で、r2は補助的な戻り値(一部のシステムコールで使用)です。
  • err: システムコールが失敗した場合のエラーコード。

このコミットでは、RawSyscallの戻り値のうちr0(Goのコードではr0r1に対応)がDup2関数の戻り値として使用されなくなる点が重要です。

技術的詳細

このコミットの核心は、LinuxにおけるDup2システムコールのGo言語ラッパーの戻り値の変更です。

変更前のLinux Dup2の挙動

変更前、GoのsyscallパッケージにおけるLinux版のDup2関数は、以下のようなシグネチャを持っていました。

func Dup2(oldfd int, newfd int) (fd int, err error)

ここで、fdはシステムコールが成功した場合に返されるファイルディスクリプタです。Linuxのdup2システムコールは、成功時にnewfdの値を返すため、Goのラッパーもその値をfdとして返していました。

内部的には、RawSyscallr0(第一戻り値)がこのfdにマッピングされていました。

r0, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
fd = int(r0) // r0をfdとして返す

他のOSにおける Dup2 の挙動

多くのUnix系OS(例えばFreeBSD, OpenBSD, Darwinなど)では、dup2システムコールは成功時にnewfdの値を返すものの、その戻り値は通常、エラーチェックのためにのみ使用され、複製されたファイルディスクリプタ自体はnewfd引数によって既に知られているため、明示的に返す必要がないと見なされます。Goのsyscallパッケージは、これらのシステムではDup2がエラーのみを返すシグネチャを採用していました。

func Dup2(oldfd int, newfd int) (err error) // fdを返さない

変更の目的と影響

このコミットの目的は、Linux版のDup2も他のシステムと同様に、成功時にファイルディスクリプタを返さないようにすることです。 変更後のシグネチャは以下のようになります。

func Dup2(oldfd int, newfd int) (err error)

これにより、GoのsyscallパッケージにおけるDup2のAPIが、すべてのサポート対象OSで統一されます。

コミットメッセージで言及されている「the only time it matters is when newfd==-1, in which case you can call Dup」という点は重要です。Linuxでは、dup2(oldfd, -1)という呼び出しが、dup(oldfd)と同様に新しいファイルディスクリプタを割り当てて返すという特殊な挙動を持っていました。しかし、これは標準的なdup2のセマンティクスから逸脱しており、Goのクロスプラットフォームなコードでは扱いにくいものでした。この変更により、newfd == -1のような不正なnewfdが渡された場合、Dup2は単にエラーを返すようになり、開発者はDupシステムコールを明示的に呼び出すことで、新しいファイルディスクリプタの割り当てを行うべきであるという明確な指針が示されます。

zsyscall_linux_*.go ファイル群について

src/pkg/syscall/zsyscall_linux_386.gosrc/pkg/syscall/zsyscall_linux_amd64.gosrc/pkg/syscall/zsyscall_linux_arm.goといったファイルは、Goのsyscallパッケージにおいて、各アーキテクチャ(386, amd64, arm)とOS(Linux)に対応するシステムコールラッパーが自動生成されるファイルです。これらのファイルは、mksyscall.plのようなスクリプトによって、syscall_linux.goのような定義ファイルから生成されます。

このコミットでは、syscall_linux.go//sysnb Dup2行のコメントが変更され、それに基づいてzsyscall_linux_*.goファイル内のDup2関数の実装が自動的に更新されています。具体的には、RawSyscallの戻り値r0fd変数に代入する行が削除され、Dup2関数自体の戻り値からfd intが取り除かれています。

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

src/pkg/syscall/syscall_linux.go

--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -802,7 +802,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
 //sys	Close(fd int) (err error)\n //sys	Creat(path string, mode uint32) (fd int, err error)\n //sysnb	Dup(oldfd int) (fd int, err error)\n-//sysnb	Dup2(oldfd int, newfd int) (fd int, err error)\n+//sysnb	Dup2(oldfd int, newfd int) (err error)\n //sysnb	EpollCreate(size int) (fd int, err error)\n //sysnb	EpollCreate1(flag int) (fd int, err error)\n //sysnb	EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)\

この変更は、Dup2システムコールのGo言語ラッパーのシグネチャ定義を変更しています。//sysnbは、この行がシステムコールラッパーを生成するための指示であることを示しています。変更前はDup2fd interr errorを返すと定義されていましたが、変更後はerr errorのみを返すように修正されています。

src/pkg/syscall/zsyscall_linux_386.go (および amd64, arm も同様)

--- a/src/pkg/syscall/zsyscall_linux_386.go
+++ b/src/pkg/syscall/zsyscall_linux_386.go
@@ -210,9 +210,8 @@ func Dup(oldfd int) (fd int, err error) {\n \n // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT\n \n-func Dup2(oldfd int, newfd int) (fd int, err error) {\n-\tr0, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n-\tfd = int(r0)\n+func Dup2(oldfd int, newfd int) (err error) {\n+\t_, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)\n \tif e1 != 0 {\n \t\terr = e1\n \t}\

この変更は、自動生成されたDup2関数の実装を修正しています。

  • 関数のシグネチャからfd intの戻り値が削除されています。
  • RawSyscallの呼び出し結果であるr0(システムコールの第一戻り値)をfd変数に代入する行(fd = int(r0))が削除されています。
  • RawSyscallの戻り値を受け取る部分も、r0が不要になったため_(ブランク識別子)に変更されています。

コアとなるコードの解説

src/pkg/syscall/syscall_linux.go の変更

このファイルは、GoのsyscallパッケージがLinuxシステムコールをどのようにラップするかを定義する「ソース」のような役割を果たします。//sysnbというコメントは、Goのビルドシステムがこの行を解析し、対応するシステムコールラッパー関数を自動生成するための指示です。

変更前:

//sysnb	Dup2(oldfd int, newfd int) (fd int, err error)

これは、Dup2関数がoldfdnewfdという2つのint引数を取り、fdというint型の戻り値とerrというerror型の戻り値を返すことを示していました。Linuxのdup2システムコールが成功時にnewfdを返すという挙動に合わせて、Goのラッパーもその値をfdとして返していました。

変更後:

//sysnb	Dup2(oldfd int, newfd int) (err error)

この変更により、Dup2関数はoldfdnewfdを受け取りますが、成功時にはファイルディスクリプタを返さず、エラーが発生した場合にのみerrを返すように定義が変更されました。これは、他のUnix系OSにおけるDup2のGoラッパーのシグネチャと一致させ、APIの一貫性を確保するためのものです。

src/pkg/syscall/zsyscall_linux_*.go の変更

これらのファイルは、上記のsyscall_linux.goの定義に基づいて自動生成される、実際のシステムコール呼び出しを行うGoのコードです。各アーキテクチャ(386, amd64, arm)ごとに生成されます。

変更前:

func Dup2(oldfd int, newfd int) (fd int, err error) {
	r0, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0)
	fd = int(r0) // システムコールの戻り値r0をfdに代入
	if e1 != 0 {
		err = e1
	}
	return
}

このコードでは、RawSyscallを呼び出し、その第一戻り値r0fd変数に代入していました。これは、Linuxのdup2システムコールが成功時にnewfdを返すという挙動を反映したものでした。

変更後:

func Dup2(oldfd int, newfd int) (err error) {
	_, _, e1 := RawSyscall(SYS_DUP2, uintptr(oldfd), uintptr(newfd), 0) // r0は不要になったため_で破棄
	if e1 != 0 {
		err = e1
	}
	return
}

変更後では、Dup2関数のシグネチャからfd intが削除されたため、RawSyscallの第一戻り値r0は不要となり、_(ブランク識別子)で破棄されるようになりました。これにより、Dup2はエラーの有無のみを報告するようになり、Go言語のsyscallパッケージ全体でのDup2のインターフェースが統一されました。

この変更は、Go言語がクロスプラットフォームな互換性を重視し、OS固有の特殊な挙動を可能な限り抽象化しようとする設計思想を反映しています。

関連リンク

参考にした情報源リンク