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

[インデックス 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のサポート追加に集約されます。

  1. 既存ファイルのビルドタグへのdragonflyの追加: src/pkg/net/cgo_bsd.gosrc/pkg/net/cgo_unix.gosrc/pkg/net/dial_test.gosrc/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)と共通のコードパスを多く利用できることを示唆しています。

  2. 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のシステムコールを利用してこの機能を実装するためのプレースホルダーです。

  3. sendfile_dragonfly.goの新規追加: このファイルは、DragonFly BSDのsendfileシステムコールを利用して、ファイルからネットワークソケットへ効率的にデータをコピーするためのsendFile関数を実装しています。これはこのコミットにおける最も重要な追加の一つです。

    • maxSendfileSize: カーネルに一度にコピーを要求する最大チャンクサイズを定義しています(4MB)。
    • sendFile関数:
      • io.LimitedReaderをサポートし、残りのバイト数を正確に計算します。
      • os.Fileからの読み込みを想定し、それ以外の場合はhandled = falseを返して通常のコピー処理にフォールバックさせます。
      • DragonFly BSDのsendfileの特性(正確なバイト数指定が必要、ファイルオフセットを明示的に管理する必要がある)に対応しています。
      • syscall.Sendfileを呼び出し、エラーハンドリング(EAGAINEINTRなど)を行います。
      • c.pd.WaitWrite()を呼び出して、書き込み準備ができるまで待機します。これは、非ブロッキングI/Oにおけるポーリングメカニズムの一部です。
      • OpErrorを返して、エラー発生時の詳細な情報を提供します。

    このsendFileの実装は、DragonFly BSDのsendfileシステムコールの具体的な挙動に合わせて調整されており、GoのnetパッケージがこのOS上で高性能なファイル転送を実現するための鍵となります。

これらの変更により、GoのnetパッケージはDragonFly BSD環境でコンパイル可能となり、基本的なネットワーク機能に加えて、sendfileによる効率的なデータ転送もサポートされるようになりました。

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

このコミットにおけるコアとなるコードの変更箇所は、主に以下の2つの新規ファイルと、既存ファイルへのビルドタグの追加です。

  1. src/pkg/net/interface_dragonfly.go (新規追加) DragonFly BSD固有のネットワークインターフェース情報取得のためのスタブ関数が含まれています。

  2. src/pkg/net/sendfile_dragonfly.go (新規追加) DragonFly BSDのsendfileシステムコールを利用した効率的なファイル転送機能の実装が含まれています。これがこのコミットの機能的なハイライトです。

  3. 既存の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関数は、以下の重要な側面を処理します。

  1. io.Readerインターフェースのサポート: sendFileio.Readerを受け取りますが、内部的には*os.File型への型アサーションを試みます。これは、sendfileシステムコールがファイルディスクリプタを直接操作するため、通常のio.Readerでは不十分だからです。*os.Fileでない場合は、handled = falseを返して、呼び出し元が通常のio.Copyなどの方法で処理するように促します。
  2. io.LimitedReaderの考慮: io.LimitedReaderが渡された場合、残りのバイト数remainを正確に把握し、その分だけsendfileを呼び出すようにします。
  3. DragonFly BSD sendfileの特性への対応:
    • 正確なバイト数指定: DragonFly BSDのsendfileは、転送バイト数に0を指定するとEOFまで転送するのではなく、無限ループに陥る可能性があるため、remain変数で残りのバイト数を厳密に管理し、syscall.Sendfileに渡すバイト数nを正確に計算しています。
    • ファイルオフセットの明示的な管理: DragonFly BSDのsendfileは、ファイルの現在位置を自動的に進めないため、f.Seek(0, os.SEEK_CUR)で現在のオフセットposを取得し、syscall.Sendfileの引数として&pos1posのコピー)を渡すことで、転送後にposを手動で更新しています。
  4. 非ブロッキングI/Oとエラーハンドリング:
    • c.writeLock()c.writeUnlock()は、netFD(ネットワークファイルディスクリプタ)への書き込み操作を同期するためのロック機構です。
    • syscall.Sendfilesyscall.EAGAIN(操作が完了せず、後で再試行する必要がある)を返した場合、c.pd.WaitWrite()を呼び出して、ファイルディスクリプタが書き込み可能になるまで待機します。これは、Goのランタイムが非ブロッキングI/Oとイベントポーリング(kqueueなど)を組み合わせて効率的なI/Oを実現していることを示しています。
    • syscall.EINTR(システムコールがシグナルによって中断された)の場合も、操作を再試行します。
    • その他のエラー(syscall.ENOSYSなど)が発生した場合は、OpErrorを返してエラーを報告し、ループを終了します。
  5. 転送バイト数の追跡: written変数で実際に転送されたバイト数を追跡し、最終的にその値を返します。

このsendFileの実装は、Goのクロスプラットフォーム戦略において、特定のOSのシステムコール特性に合わせた低レベルな最適化がいかに重要であるかを示しています。

関連リンク

参考にした情報源リンク

  • Goのコミットメッセージと変更されたファイルの内容
  • DragonFly BSDのsendfileシステムコールに関する情報 (一般的なUnix系OSのsendfileの挙動との比較)
  • Go言語のビルドタグに関する知識
  • Go言語のnetパッケージの一般的な構造とクロスプラットフォーム対応の仕組み