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

[インデックス 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.Dupsyscall.CloseOnExecの2つのシステムコールが必要ですが、Linuxと十分新しいカーネルを持つマシンでは、これらをdup3一つで置き換えることが可能になります。

変更の背景

Go言語のランタイムは、プロセス生成(fork/exec)などの操作において、ファイルディスクリプタの扱いが非常に重要になります。特に、子プロセスに継承させたくないファイルディスクリプタがある場合、それらを明示的にクローズオンエグゼック(Close-on-exec, FD_CLOEXECフラグ)に設定する必要があります。

従来のLinuxシステムでは、ファイルディスクリプタを複製し、かつその複製にFD_CLOEXECフラグを設定するには、通常以下の2つのシステムコールが必要でした。

  1. dupまたはdup2でファイルディスクリプタを複製する。
  2. 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と同様にoldfdnewfdに複製しますが、さらにflags引数を取ることができます。このflags引数にO_CLOEXEC(またはFD_CLOEXECに対応する値)を指定することで、複製と同時にFD_CLOEXECフラグを設定できます。これはLinuxカーネル2.6.27以降で利用可能です。

FD_CLOEXEC (Close-on-exec) フラグ

ファイルディスクリプタに設定できるフラグの一つで、このフラグが設定されているファイルディスクリプタは、execシステムコール(新しいプログラムを実行する際に呼び出される)が成功した際に自動的に閉じられます。これにより、子プロセスに不要なファイルディスクリプタが継承されるのを防ぎ、セキュリティやリソース管理の観点から重要です。

syscall.ForkLock

Go言語のsyscallパッケージ内で使用される内部的なロック機構です。Goランタイムがforkexecなどのシステムコールを呼び出して新しいプロセスを生成する際、ファイルディスクリプタの状態の一貫性を保つためにこのロックが使用されます。このロックが保持されている間は、他のスレッドがファイルディスクリプタを操作するのを防ぎます。ロックの保持時間が短いほど、並行処理の効率が向上します。

RawSyscall

Go言語のsyscallパッケージで、低レベルなシステムコールを直接呼び出すための関数です。GoのランタイムがOSの機能と直接やり取りする際に使用されます。

技術的詳細

このコミットの主要な目的は、Goランタイムがファイルディスクリプタを複製し、同時にFD_CLOEXECフラグを設定する際の効率を向上させることです。

Go言語のsyscallパッケージは、OSのシステムコールをGoプログラムから呼び出すためのインターフェースを提供します。dup3システムコールは、Linuxカーネルの比較的新しいバージョンで導入された機能であり、従来のdup/dup2fcntl(F_SETFD, FD_CLOEXEC)の組み合わせを単一のシステムコールにまとめることができます。

コミットメッセージにあるように、Goランタイムがsyscall.ForkLockを保持している間、ファイルディスクリプタの複製とFD_CLOEXECの設定を行う必要がありました。これは通常、以下の手順で行われていました。

  1. syscall.Dup(oldfd) または syscall.Dup2(oldfd, newfd) を呼び出してファイルディスクリプタを複製する。
  2. 複製されたファイルディスクリプタに対して 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つのファイルが変更されています。

  1. src/pkg/syscall/syscall_linux.go:

    • Dup3システムコールのGo言語でのシグネチャが追加されています。これは、zsyscall_linux_*.goファイルを生成するための指示となります。
  2. src/pkg/syscall/zsyscall_linux_386.go:

    • 32ビットIntelアーキテクチャ(i386)向けのDup3関数の実装が追加されています。RawSyscallを使用してSYS_DUP3システムコールを呼び出します。
  3. src/pkg/syscall/zsyscall_linux_amd64.go:

    • 64ビットIntelアーキテクチャ(amd64)向けのDup3関数の実装が追加されています。RawSyscallを使用してSYS_DUP3システムコールを呼び出します。
  4. src/pkg/syscall/zsyscall_linux_arm.go:

    • ARMアーキテクチャ向けのDup3関数の実装が追加されています。RawSyscallを使用してSYS_DUP3システムコールを呼び出します。

コアとなるコードの解説

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は複製先のファイルディスクリプタ、flagsO_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の公式ブログ、技術記事など)