[インデックス 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のコードではr0
がr1
に対応)が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
として返していました。
内部的には、RawSyscall
のr0
(第一戻り値)がこの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.go
、src/pkg/syscall/zsyscall_linux_amd64.go
、src/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
の戻り値r0
をfd
変数に代入する行が削除され、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
は、この行がシステムコールラッパーを生成するための指示であることを示しています。変更前はDup2
がfd int
とerr 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
関数がoldfd
とnewfd
という2つのint
引数を取り、fd
というint
型の戻り値とerr
というerror
型の戻り値を返すことを示していました。Linuxのdup2
システムコールが成功時にnewfd
を返すという挙動に合わせて、Goのラッパーもその値をfd
として返していました。
変更後:
//sysnb Dup2(oldfd int, newfd int) (err error)
この変更により、Dup2
関数はoldfd
とnewfd
を受け取りますが、成功時にはファイルディスクリプタを返さず、エラーが発生した場合にのみ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
を呼び出し、その第一戻り値r0
をfd
変数に代入していました。これは、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固有の特殊な挙動を可能な限り抽象化しようとする設計思想を反映しています。
関連リンク
- Go CL 5650073: https://golang.org/cl/5650073
参考にした情報源リンク
dup(2)
man page: https://man7.org/linux/man-pages/man2/dup.2.htmldup2(2)
man page: https://man7.org/linux/man-pages/man2/dup2.2.html- Go
syscall
package documentation: https://pkg.go.dev/syscall - Go
mksyscall.pl
(source code for generating syscall wrappers): https://github.com/golang/go/blob/master/src/syscall/mksyscall.pl