[インデックス 17376] ファイルの概要
このコミットは、Go言語のnet
パッケージがDragonFly BSDオペレーティングシステム上でビルドされ、正しく動作するようにするための変更を導入しています。具体的には、既存のBSD系およびUnix系システム向けのネットワーク関連ファイルにDragonFly BSDのサポートを追加し、さらにDragonFly BSD固有のネットワークインターフェース情報取得およびsendfile
システムコール実装ファイルを追加しています。
コミット
commit fce060856112dc1929b77b55aba5c5b8347a8d30
Author: Joel Sing <jsing@google.com>
Date: Sat Aug 24 02:18:22 2013 +1000
net: dragonfly support
Make the net package build and work on dragonfly.
R=bradfitz
CC=golang-dev
https://golang.org/cl/13173044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fce060856112dc1929b77b55aba5c5b8347a8d30
元コミット内容
net: dragonfly support
Make the net package build and work on dragonfly.
このコミットの目的は、Go言語のnet
パッケージがDragonFly BSD上でビルドされ、期待通りに機能するようにすることです。
変更の背景
Go言語は、その設計思想としてクロスプラットフォーム対応を重視しており、様々なオペレーティングシステム上で動作することを目標としています。このコミットが作成された2013年当時、Goは既にLinux、macOS (Darwin)、FreeBSD、OpenBSD、NetBSD、Windows、Plan 9といった主要なOSをサポートしていました。しかし、BSD系OSの一つであるDragonFly BSDに対する公式なサポートはまだ不完全でした。
net
パッケージは、ネットワーク通信を行うGoプログラムの基盤となる非常に重要なパッケージです。ソケットの作成、接続、データの送受信、DNS解決、ネットワークインターフェース情報の取得など、多岐にわたる機能を提供します。これらの機能は、OSのシステムコールに深く依存しており、OSごとに異なるAPIや挙動を持つことがあります。
DragonFly BSDは、FreeBSD 4.8からフォークしたオペレーティングシステムであり、独自のカーネル設計やシステムコールを持つ部分があります。そのため、既存のFreeBSDや他のBSD系OS向けのコードだけでは、DragonFly BSD上でnet
パッケージが完全に機能しない可能性がありました。このコミットは、Goのクロスプラットフォーム対応をさらに強化し、DragonFly BSDユーザーがGo言語を快適に利用できるようにするために行われました。特に、sendfile
のようなパフォーマンスに影響するシステムコールはOS固有の実装が必要となるため、その対応が重要でした。
前提知識の解説
DragonFly BSD
DragonFly BSDは、2003年にFreeBSD 4.8からフォークして開発が始まったオープンソースのUnix系オペレーティングシステムです。FreeBSDとは異なり、マイクロカーネル的な設計思想を取り入れ、メッセージパッシングによるプロセス間通信を重視しています。また、独自のファイルシステムであるHAMMERファイルシステムや、軽量なカーネルスレッドスケジューラなど、FreeBSDとは異なる多くの技術的革新を導入しています。
Go言語のようなシステムプログラミング言語が特定のOSをサポートするためには、そのOSのシステムコールやライブラリのインターフェースに合わせてコードを記述する必要があります。特にネットワーク関連の機能は、OSのカーネルと密接に連携するため、OS固有の調整が不可欠です。
Goのビルドタグ (+build
ディレクティブ)
Go言語では、ソースコードファイルの先頭に+build
ディレクティブを記述することで、特定の環境下でのみそのファイルをコンパイル対象とするように制御できます。これは「ビルドタグ」と呼ばれ、クロスプラットフォーム開発において非常に重要な機能です。
例:
// +build linux
:Linux環境でのみコンパイル// +build darwin freebsd
:macOSまたはFreeBSD環境でのみコンパイル// +build !windows
:Windows以外の環境でコンパイル
このコミットでは、既存のnet
パッケージ内のファイルにdragonfly
ビルドタグを追加することで、DragonFly BSD環境でもこれらのファイルがコンパイルされるように変更しています。また、DragonFly BSD固有の機能を提供する新しいファイルには、+build dragonfly
タグを付与しています。
sendfile
システムコール
sendfile
は、Unix系オペレーティングシステムで利用可能なシステムコールの一つで、ファイルディスクリプタから別のファイルディスクリプタへデータを直接転送する機能を提供します。このシステムコールの主な利点は、ユーザー空間のバッファを介さずにカーネル空間内でデータ転送が完結するため、CPUオーバーヘッドとメモリコピーの回数を削減し、I/Oパフォーマンスを向上させることができる点です。
ウェブサーバーなどで静的ファイルをクライアントに送信する際などに特に有効です。しかし、sendfile
システムコールの具体的な引数や挙動は、OSによって微妙に異なる場合があります。例えば、Linux、FreeBSD、macOS、そしてDragonFly BSDでは、それぞれ異なるsendfile
のシグネチャやセマンティクスを持つことがあります。このコミットでは、DragonFly BSDのsendfile
の特性に合わせた実装が追加されています。
DragonFly BSDのsendfile
の特性として、コミットのコードコメントにもあるように、以下の点が挙げられます。
remain
(残りのバイト数) に0を渡すとEOFまで送信するのではなく、指定されたバイト数を超えてループする可能性があるため、正確なバイト数を指定する必要がある。- ファイルの現在位置を使用せず、常にオフセットを指定する必要がある。
技術的詳細
このコミットの技術的な変更は、主にGoのnet
パッケージにおけるDragonFly BSDのサポート追加に集約されます。
-
既存ファイルのビルドタグへの
dragonfly
の追加:src/pkg/net/cgo_bsd.go
、src/pkg/net/cgo_unix.go
、src/pkg/net/dial_test.go
、src/pkg/net/dnsclient_unix.go
など、多数の既存のnet
パッケージ関連ファイルにおいて、ファイルの先頭にある+build
ディレクティブにdragonfly
が追加されています。これにより、これらのファイルがDragonFly BSD環境でもコンパイル対象となり、Goのnet
パッケージの基本的な機能がDragonFly BSD上で利用可能になります。例:
--- a/src/pkg/net/cgo_bsd.go +++ b/src/pkg/net/cgo_bsd.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // +build !netgo -// +build darwin freebsd +// +build darwin dragonfly freebsd package net
これは、DragonFly BSDが既存のBSD系OS(Darwin, FreeBSD)やUnix系OS(Linux, NetBSD, OpenBSD)と共通のコードパスを多く利用できることを示唆しています。
-
interface_dragonfly.go
の新規追加: このファイルは、DragonFly BSD固有のネットワークインターフェース関連の機能を提供するために追加されました。しかし、初期実装ではinterfaceMulticastAddrTable
関数がTODO
コメントと共にnil, nil
を返すスタブとなっています。これは、このコミットの時点ではマルチキャストアドレスのテーブル取得機能が未実装であることを示しています。// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package net // interfaceMulticastAddrTable returns addresses for a specific // interface. func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { // TODO(mikio): Implement this like other platforms. return nil, nil }
このスタブは、将来的にDragonFly BSDのシステムコールを利用してこの機能を実装するためのプレースホルダーです。
-
sendfile_dragonfly.go
の新規追加: このファイルは、DragonFly BSDのsendfile
システムコールを利用して、ファイルからネットワークソケットへ効率的にデータをコピーするためのsendFile
関数を実装しています。これはこのコミットにおける最も重要な追加の一つです。maxSendfileSize
: カーネルに一度にコピーを要求する最大チャンクサイズを定義しています(4MB)。sendFile
関数:io.LimitedReader
をサポートし、残りのバイト数を正確に計算します。os.File
からの読み込みを想定し、それ以外の場合はhandled = false
を返して通常のコピー処理にフォールバックさせます。- DragonFly BSDの
sendfile
の特性(正確なバイト数指定が必要、ファイルオフセットを明示的に管理する必要がある)に対応しています。 syscall.Sendfile
を呼び出し、エラーハンドリング(EAGAIN
、EINTR
など)を行います。c.pd.WaitWrite()
を呼び出して、書き込み準備ができるまで待機します。これは、非ブロッキングI/Oにおけるポーリングメカニズムの一部です。OpError
を返して、エラー発生時の詳細な情報を提供します。
この
sendFile
の実装は、DragonFly BSDのsendfile
システムコールの具体的な挙動に合わせて調整されており、Goのnet
パッケージがこのOS上で高性能なファイル転送を実現するための鍵となります。
これらの変更により、Goのnet
パッケージはDragonFly BSD環境でコンパイル可能となり、基本的なネットワーク機能に加えて、sendfile
による効率的なデータ転送もサポートされるようになりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下の2つの新規ファイルと、既存ファイルへのビルドタグの追加です。
-
src/pkg/net/interface_dragonfly.go
(新規追加) DragonFly BSD固有のネットワークインターフェース情報取得のためのスタブ関数が含まれています。 -
src/pkg/net/sendfile_dragonfly.go
(新規追加) DragonFly BSDのsendfile
システムコールを利用した効率的なファイル転送機能の実装が含まれています。これがこのコミットの機能的なハイライトです。 -
既存の
net
パッケージ関連ファイル群への+build dragonfly
タグの追加 例:src/pkg/net/cgo_bsd.go
,src/pkg/net/cgo_unix.go
,src/pkg/net/dial_test.go
,src/pkg/net/dnsclient_unix.go
,src/pkg/net/fd_poll_runtime.go
,src/pkg/net/sock_bsd.go
など、多数のファイル。
コアとなるコードの解説
最も重要な追加はsrc/pkg/net/sendfile_dragonfly.go
です。このファイルは、Goのnet
パッケージがDragonFly BSD上でsendfile
システムコールを効率的に利用するためのロジックをカプセル化しています。
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package net
import (
"io"
"os"
"syscall"
)
// maxSendfileSize is the largest chunk size we ask the kernel to copy
// at a time.
const maxSendfileSize int = 4 << 20 // 4MB
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
// if handled == true, sendFile returns the number of bytes copied and any
// non-EOF error.
//
// if handled == false, sendFile performed no work.
func sendFile(c *netFD, r io.Reader) (written int64, err error, handled bool) {
// DragonFly uses 0 as the "until EOF" value. If you pass in more bytes than the
// file contains, it will loop back to the beginning ad nauseam until it's sent
// exactly the number of bytes told to. As such, we need to know exactly how many
// bytes to send.
var remain int64 = 0
lr, ok := r.(*io.LimitedReader)
if ok {
remain, r = lr.N, lr.R
if remain <= 0 {
return 0, nil, true
}
}
f, ok := r.(*os.File)
if !ok {
return 0, nil, false
}
if remain == 0 {
fi, err := f.Stat()
if err != nil {
return 0, err, false
}
remain = fi.Size()
}
// The other quirk with DragonFly's sendfile implementation is that it doesn't
// use the current position of the file -- if you pass it offset 0, it starts
// from offset 0. There's no way to tell it "start from current position", so
// we have to manage that explicitly.
pos, err := f.Seek(0, os.SEEK_CUR)
if err != nil {
return 0, err, false
}
if err := c.writeLock(); err != nil {
return 0, err, true
}
defer c.writeUnlock()
dst := c.sysfd
src := int(f.Fd())
for remain > 0 {
n := maxSendfileSize
if int64(n) > remain {
n = int(remain)
}
pos1 := pos
n, err1 := syscall.Sendfile(dst, src, &pos1, n)
if n > 0 {
pos += int64(n)
written += int64(n)
remain -= int64(n)
}
if n == 0 && err1 == nil {
break
}
if err1 == syscall.EAGAIN {
if err1 = c.pd.WaitWrite(); err1 == nil {
continue
}
}
if err1 == syscall.EINTR {
continue
}
if err1 != nil {
// This includes syscall.ENOSYS (no kernel
// support) and syscall.EINVAL (fd types which
// don't implement sendfile together)
err = &OpError{"sendfile", c.net, c.raddr, err1}
break
}
}
if lr != nil {
lr.N = remain
}
return written, err, written > 0
}
このsendFile
関数は、以下の重要な側面を処理します。
io.Reader
インターフェースのサポート:sendFile
はio.Reader
を受け取りますが、内部的には*os.File
型への型アサーションを試みます。これは、sendfile
システムコールがファイルディスクリプタを直接操作するため、通常のio.Reader
では不十分だからです。*os.File
でない場合は、handled = false
を返して、呼び出し元が通常のio.Copy
などの方法で処理するように促します。io.LimitedReader
の考慮:io.LimitedReader
が渡された場合、残りのバイト数remain
を正確に把握し、その分だけsendfile
を呼び出すようにします。- DragonFly BSD
sendfile
の特性への対応:- 正確なバイト数指定: DragonFly BSDの
sendfile
は、転送バイト数に0を指定するとEOFまで転送するのではなく、無限ループに陥る可能性があるため、remain
変数で残りのバイト数を厳密に管理し、syscall.Sendfile
に渡すバイト数n
を正確に計算しています。 - ファイルオフセットの明示的な管理: DragonFly BSDの
sendfile
は、ファイルの現在位置を自動的に進めないため、f.Seek(0, os.SEEK_CUR)
で現在のオフセットpos
を取得し、syscall.Sendfile
の引数として&pos1
(pos
のコピー)を渡すことで、転送後にpos
を手動で更新しています。
- 正確なバイト数指定: DragonFly BSDの
- 非ブロッキングI/Oとエラーハンドリング:
c.writeLock()
とc.writeUnlock()
は、netFD
(ネットワークファイルディスクリプタ)への書き込み操作を同期するためのロック機構です。syscall.Sendfile
がsyscall.EAGAIN
(操作が完了せず、後で再試行する必要がある)を返した場合、c.pd.WaitWrite()
を呼び出して、ファイルディスクリプタが書き込み可能になるまで待機します。これは、Goのランタイムが非ブロッキングI/Oとイベントポーリング(kqueueなど)を組み合わせて効率的なI/Oを実現していることを示しています。syscall.EINTR
(システムコールがシグナルによって中断された)の場合も、操作を再試行します。- その他のエラー(
syscall.ENOSYS
など)が発生した場合は、OpError
を返してエラーを報告し、ループを終了します。
- 転送バイト数の追跡:
written
変数で実際に転送されたバイト数を追跡し、最終的にその値を返します。
このsendFile
の実装は、Goのクロスプラットフォーム戦略において、特定のOSのシステムコール特性に合わせた低レベルな最適化がいかに重要であるかを示しています。
関連リンク
- Go言語公式ウェブサイト: https://golang.org/
- DragonFly BSD公式ウェブサイト: https://www.dragonflybsd.org/
- Goのビルドタグに関する公式ドキュメント (Go 1.16以降は
//go:build
に移行していますが、このコミット当時は+build
が主流でした): https://pkg.go.dev/cmd/go#hdr-Build_constraints
参考にした情報源リンク
- Goのコミットメッセージと変更されたファイルの内容
- DragonFly BSDの
sendfile
システムコールに関する情報 (一般的なUnix系OSのsendfile
の挙動との比較) - Go言語のビルドタグに関する知識
- Go言語の
net
パッケージの一般的な構造とクロスプラットフォーム対応の仕組み