[インデックス 16975] ファイルの概要
このコミットは、Go言語のsyscall
パッケージにおいて、Linuxシステムコールであるdup3
のサポートを追加するものです。これにより、特定の条件下でのファイルディスクリプタの複製処理が最適化され、システムコール回数の削減とパフォーマンスの向上が図られます。
コミット
commit c8d49cf56f2633ba2859f9ad4567bf754128824c
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Jul 31 23:38:53 2013 -0700
syscall: add Dup3 on Linux
With dup3, we can avoid an extra system call on some machines
while holding syscall.ForkLock. Currently we have to
syscall.Dup + syscall.CloseOnExec.
On machines with Linux and a new enough kernel, this can just
be dup3.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/12170045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c8d49cf56f2633ba2859f9ad4567bf754128824c
元コミット内容
このコミットは、Linux上でdup3
システムコールをGoのsyscall
パッケージに追加します。これにより、syscall.ForkLock
を保持している間、一部の環境で余分なシステムコールを回避できるようになります。現状ではsyscall.Dup
とsyscall.CloseOnExec
の2つのシステムコールが必要ですが、Linuxと十分新しいカーネルを持つマシンでは、これらをdup3
一つで置き換えることが可能になります。
変更の背景
Go言語のランタイムは、プロセス生成(fork
/exec
)などの操作において、ファイルディスクリプタの扱いが非常に重要になります。特に、子プロセスに継承させたくないファイルディスクリプタがある場合、それらを明示的にクローズオンエグゼック(Close-on-exec, FD_CLOEXEC
フラグ)に設定する必要があります。
従来のLinuxシステムでは、ファイルディスクリプタを複製し、かつその複製にFD_CLOEXEC
フラグを設定するには、通常以下の2つのシステムコールが必要でした。
dup
またはdup2
でファイルディスクリプタを複製する。fcntl
システムコールを使って、複製されたファイルディスクリプタにFD_CLOEXEC
フラグを設定する。
この2段階の操作は、特にsyscall.ForkLock
のようなロックを保持しているクリティカルなセクションで行われる場合、オーバーヘッドとなります。ForkLock
は、Goランタイムが新しいプロセスを生成する際に、ファイルディスクリプタの状態を安全に管理するために使用される重要なロックです。このロックを保持する時間は短い方が、ランタイム全体のパフォーマンスに寄与します。
Linuxカーネル2.6.27で導入されたdup3
システムコールは、この問題を解決するために設計されました。dup3
は、ファイルディスクリプタの複製と同時にFD_CLOEXEC
フラグの設定を単一のシステムコールで行うことができます。これにより、システムコール回数を1回削減し、特に高頻度でプロセス生成が行われるようなシナリオにおいて、パフォーマンスの改善が期待できます。
このコミットは、GoランタイムがLinuxの新しいカーネル機能を活用し、より効率的なファイルディスクリプタ管理を実現するための最適化の一環として行われました。
前提知識の解説
ファイルディスクリプタ (File Descriptor, FD)
Unix系OSにおいて、ファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数値です。プロセスはファイルディスクリプタを通じてこれらのリソースにアクセスします。
dup
/ dup2
/ dup3
システムコール
dup(oldfd)
:oldfd
が参照するファイルディスクリプタを複製し、利用可能な最小のファイルディスクリプタ番号を返します。複製されたファイルディスクリプタは、元のファイルディスクリプタと同じファイル記述エントリ(ファイルオフセット、ファイルステータスフラグなど)を共有します。dup2(oldfd, newfd)
:newfd
が既に開いているファイルディスクリプタであればそれを閉じ、oldfd
が参照するファイルディスクリプタをnewfd
に複製します。newfd
は指定された値になります。dup3(oldfd, newfd, flags)
:dup2
と同様にoldfd
をnewfd
に複製しますが、さらにflags
引数を取ることができます。このflags
引数にO_CLOEXEC
(またはFD_CLOEXEC
に対応する値)を指定することで、複製と同時にFD_CLOEXEC
フラグを設定できます。これはLinuxカーネル2.6.27以降で利用可能です。
FD_CLOEXEC
(Close-on-exec) フラグ
ファイルディスクリプタに設定できるフラグの一つで、このフラグが設定されているファイルディスクリプタは、exec
システムコール(新しいプログラムを実行する際に呼び出される)が成功した際に自動的に閉じられます。これにより、子プロセスに不要なファイルディスクリプタが継承されるのを防ぎ、セキュリティやリソース管理の観点から重要です。
syscall.ForkLock
Go言語のsyscall
パッケージ内で使用される内部的なロック機構です。Goランタイムがfork
やexec
などのシステムコールを呼び出して新しいプロセスを生成する際、ファイルディスクリプタの状態の一貫性を保つためにこのロックが使用されます。このロックが保持されている間は、他のスレッドがファイルディスクリプタを操作するのを防ぎます。ロックの保持時間が短いほど、並行処理の効率が向上します。
RawSyscall
Go言語のsyscall
パッケージで、低レベルなシステムコールを直接呼び出すための関数です。GoのランタイムがOSの機能と直接やり取りする際に使用されます。
技術的詳細
このコミットの主要な目的は、Goランタイムがファイルディスクリプタを複製し、同時にFD_CLOEXEC
フラグを設定する際の効率を向上させることです。
Go言語のsyscall
パッケージは、OSのシステムコールをGoプログラムから呼び出すためのインターフェースを提供します。dup3
システムコールは、Linuxカーネルの比較的新しいバージョンで導入された機能であり、従来のdup
/dup2
とfcntl(F_SETFD, FD_CLOEXEC)
の組み合わせを単一のシステムコールにまとめることができます。
コミットメッセージにあるように、Goランタイムがsyscall.ForkLock
を保持している間、ファイルディスクリプタの複製とFD_CLOEXEC
の設定を行う必要がありました。これは通常、以下の手順で行われていました。
syscall.Dup(oldfd)
またはsyscall.Dup2(oldfd, newfd)
を呼び出してファイルディスクリプタを複製する。- 複製されたファイルディスクリプタに対して
syscall.CloseOnExec(fd)
を呼び出す。CloseOnExec
の内部では、fcntl
システムコールが使用され、FD_CLOEXEC
フラグが設定されます。
この2つのシステムコールは、ForkLock
が保持されている間に実行されるため、ロックの保持時間を長くしていました。dup3
システムコールを導入することで、この2つの操作を1つのシステムコールに集約できます。
Go言語では、zsyscall_linux_*.go
ファイル群は、各アーキテクチャ(386, amd64, armなど)向けのシステムコールラッパーを自動生成するためのテンプレートから生成されます。これらのファイルにDup3
の定義を追加することで、Goプログラムからdup3
システムコールを直接呼び出せるようになります。
具体的には、syscall_linux.go
に//sysnb Dup3(oldfd int, newfd int, flags int) (err error)
という行を追加することで、go generate
コマンドがzsyscall_linux_*.go
ファイルにDup3
関数の実装を自動生成します。この生成された関数は、RawSyscall
を使用して実際のdup3
システムコールを呼び出します。
この変更は、GoランタイムがLinux上でより効率的に動作するための、低レベルな最適化です。特に、多数のプロセスを生成するようなアプリケーションや、ファイルディスクリプタの管理が頻繁に行われるようなシナリオで、わずかながらもパフォーマンスの向上が期待できます。
コアとなるコードの変更箇所
このコミットでは、以下の4つのファイルが変更されています。
-
src/pkg/syscall/syscall_linux.go
:Dup3
システムコールのGo言語でのシグネチャが追加されています。これは、zsyscall_linux_*.go
ファイルを生成するための指示となります。
-
src/pkg/syscall/zsyscall_linux_386.go
:- 32ビットIntelアーキテクチャ(i386)向けの
Dup3
関数の実装が追加されています。RawSyscall
を使用してSYS_DUP3
システムコールを呼び出します。
- 32ビットIntelアーキテクチャ(i386)向けの
-
src/pkg/syscall/zsyscall_linux_amd64.go
:- 64ビットIntelアーキテクチャ(amd64)向けの
Dup3
関数の実装が追加されています。RawSyscall
を使用してSYS_DUP3
システムコールを呼び出します。
- 64ビットIntelアーキテクチャ(amd64)向けの
-
src/pkg/syscall/zsyscall_linux_arm.go
:- ARMアーキテクチャ向けの
Dup3
関数の実装が追加されています。RawSyscall
を使用してSYS_DUP3
システムコールを呼び出します。
- ARMアーキテクチャ向けの
コアとなるコードの解説
src/pkg/syscall/syscall_linux.go
の変更
--- a/src/pkg/syscall/syscall_linux.go
+++ b/src/pkg/syscall/syscall_linux.go
@@ -778,6 +778,7 @@ func Mount(source string, target string, fstype string, flags uintptr, data stri
//sys Creat(path string, mode uint32) (fd int, err error)
//sysnb Dup(oldfd int) (fd int, err error)
//sysnb Dup2(oldfd int, newfd int) (err error)
+//sysnb Dup3(oldfd int, newfd int, flags int) (err error)
//sysnb EpollCreate(size int) (fd int, err error)
//sysnb EpollCreate1(flag int) (fd int, err error)
//sysnb EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)
この行は、Goのsyscall
パッケージがdup3
システムコールをGoの関数として公開するための宣言です。
//sysnb
: このコメントは、go generate
ツールに対する指示であり、この行に対応するシステムコールラッパーを生成する際に、ノンブロッキング(nb
)バージョンとして生成することを示唆しています。Dup3(oldfd int, newfd int, flags int) (err error)
:Dup3
関数のGo言語でのシグネチャを定義しています。oldfd
は複製元のファイルディスクリプタ、newfd
は複製先のファイルディスクリプタ、flags
はO_CLOEXEC
などのフラグを指定します。戻り値はエラーのみで、成功時にはnil
が返されます。
src/pkg/syscall/zsyscall_linux_386.go
, src/pkg/syscall/zsyscall_linux_amd64.go
, src/pkg/syscall/zsyscall_linux_arm.go
の変更
これらのファイルは、syscall_linux.go
の//sysnb
コメントに基づいて自動生成される部分です。各アーキテクチャ固有のシステムコール呼び出しの実装が含まれます。変更内容は3つのファイルでほぼ同じです。
--- a/src/pkg/syscall/zsyscall_linux_386.go
+++ b/src/pkg/syscall/zsyscall_linux_386.go
@@ -310,6 +310,16 @@ func Dup2(oldfd int, newfd int) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+func Dup3(oldfd int, newfd int, flags int) (err error) {
+\t_, _, e1 := RawSyscall(SYS_DUP3, uintptr(oldfd), uintptr(newfd), uintptr(flags))\n+\tif e1 != 0 {\n+\t\terr = e1\n+\t}\n+\treturn\n+}
+\n+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+\n func EpollCreate(size int) (fd int, err error) {
\tr0, _, e1 := RawSyscall(SYS_EPOLL_CREATE, uintptr(size), 0, 0)\n \tfd = int(r0)\n```
* `func Dup3(oldfd int, newfd int, flags int) (err error) { ... }`: `Dup3`関数の実際のGo言語での実装です。
* `_, _, e1 := RawSyscall(SYS_DUP3, uintptr(oldfd), uintptr(newfd), uintptr(flags))`: ここが`dup3`システムコールを呼び出す核心部分です。
* `RawSyscall`: Goの低レベルなシステムコール呼び出し関数です。
* `SYS_DUP3`: `dup3`システムコールに対応するシステムコール番号です。これはLinuxカーネルによって定義されています。
* `uintptr(oldfd)`, `uintptr(newfd)`, `uintptr(flags)`: システムコールに渡す引数です。Goの`int`型を`uintptr`型にキャストして、システムコールが期待するポインタサイズ整数として渡します。
* 戻り値の`e1`はエラーコードを表します。
* `if e1 != 0 { err = e1 }`: システムコールがエラーを返した場合(`e1`が0以外の場合)、そのエラーコードをGoのエラーとして返します。
これらの変更により、GoプログラムはLinux上で`dup3`システムコールを直接、かつ効率的に利用できるようになります。
## 関連リンク
* Go言語の`syscall`パッケージのドキュメント: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
* Linux `dup3` manページ: `man 2 dup3` (通常、システムにインストールされているmanページを参照)
* Linux `fcntl` manページ: `man 2 fcntl` (通常、システムにインストールされているmanページを参照)
## 参考にした情報源リンク
* [https://golang.org/cl/12170045](https://golang.org/cl/12170045) (Go Gerritの変更リスト)
* [https://man7.org/linux/man-pages/man2/dup.2.html](https://man7.org/linux/man-pages/man2/dup.2.html) (Linux `dup`, `dup2`, `dup3` manページ)
* [https://man7.org/linux/man-pages/man2/fcntl.2.html](https://man7.org/linux/man-pages/man2/fcntl.2.html) (Linux `fcntl` manページ)
* Go言語のソースコード (特に`src/pkg/syscall`ディレクトリ)
* Go言語のシステムコールに関する一般的な情報源 (例: Goの公式ブログ、技術記事など)