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

[インデックス 17305] ファイルの概要

このコミットは、GoランタイムにおけるNetBSD/amd64, 386, armアーキテクチャ向けのネットワークポーラーを統合するものです。具体的には、NetBSDが提供するkqueueシステムコールを利用して、Goの非同期ネットワークI/O処理を効率的に行うための基盤を整備しています。これにより、NetBSD上でのGoアプリケーションのネットワーク性能と安定性が向上します。

コミット

commit c4cdd35e6ef3f01ddace59630b9077d2048cdd94
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sat Aug 17 12:11:29 2013 +0900

    runtime: integrated network pollster for netbsd/amd64,386,arm
    
    Original CL by minux (9545044).
    
    Update #6146
    
    R=golang-dev, rsc
    CC=golang-dev, minux.ma
    https://golang.org/cl/12949045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c4cdd35e6ef3f01ddace59630b9077d2048cdd94

元コミット内容

このコミットは、minux氏による元の変更リスト(CL 9545044)を統合したものです。元のコミットメッセージは直接提供されていませんが、このコミットの目的がNetBSD向けのネットワークポーラーの統合であることが示されています。

変更の背景

Goランタイムは、効率的な並行処理を実現するために、ネットワークI/Oを非同期で処理するメカニズムを持っています。Goのgoroutineから見ると、ネットワークI/O操作(例: conn.Read()conn.Write())はブロッキングに見えますが、内部的には非ブロッキングソケットとOSが提供するイベント通知メカニズム(Linuxのepoll、macOS/BSD系のkqueueなど)を組み合わせています。

NetBSDは、他のBSD系OSと同様にkqueueを主要なイベント通知インターフェースとして提供しています。GoランタイムがNetBSD上で効率的に動作するためには、このkqueueをGoのネットワークポーラーに統合し、ネットワークイベントの監視とgoroutineのスケジューリングを適切に行う必要がありました。

このコミット以前は、NetBSDの特定のアーキテクチャ(amd64, 386, arm)では、kqueueベースのネットワークポーラーが完全に統合されていなかったか、あるいは効率的な実装が不足していた可能性があります。そのため、GoアプリケーションがNetBSD上で高いネットワーク性能を発揮するためには、この統合が不可欠でした。

また、コミットメッセージに「Update #6146」とあることから、GoのIssue 6146に関連する変更であることが示唆されます。これは、NetBSDにおけるネットワークポーラーのサポートに関する課題を解決するためのものであったと考えられます。

前提知識の解説

1. Goのネットワークポーラー (Netpoller)

Goランタイムのネットワークポーラーは、Goの並行処理モデルにおいて非常に重要な役割を担っています。Goのgoroutineは、I/O操作が完了するまでブロックされるかのように動作しますが、これはGoランタイムが内部的に非ブロッキングI/Oとイベント通知メカニズムを組み合わせて実現しています。

  • 非ブロッキングソケット: Goランタイムは、ネットワークファイルディスクリプタを非ブロッキングモードに設定します。これにより、I/O操作がすぐに完了しない場合でも、システムコールが即座に制御を返し、goroutineがブロックされるのを防ぎます。
  • イベント通知メカニズム: OSが提供するイベント通知メカニズム(kqueue, epoll, select/pollなど)を利用して、ファイルディスクリプタが読み書き可能になったことを効率的に検知します。
  • goroutineのパーク/アンパーク: I/O操作がすぐに完了しない場合、Goランタイムは該当するgoroutineを「パーク」(一時停止)させ、ネットワークポーラーにそのファイルディスクリプタの監視を依頼します。ファイルディスクリプタが準備完了になると、ネットワークポーラーは対応するgoroutineを「アンパーク」(再開)させ、Goスケジューラがそのgoroutineを再度実行キューに入れます。
  • 専用のポーラースレッド: ネットワークポーラーは通常、専用のOSスレッドで動作し、イベント通知システムコールを呼び出してネットワークイベントを待ち続けます。

このメカニズムにより、Goは多数の同時ネットワーク接続を効率的に処理でき、接続ごとにOSスレッドを割り当てるような非効率な設計を回避しています。

2. kqueue

kqueueは、FreeBSD、NetBSD、OpenBSD、macOSなどのBSD系オペレーティングシステムで利用される、高性能なイベント通知インターフェースです。複数のファイルディスクリプタやその他のカーネルオブジェクト(プロセス、シグナル、タイマーなど)に対する様々なイベントを効率的に監視できます。

  • kqueue(): 新しいkqueueインスタンスを作成し、そのファイルディスクリプタを返します。このファイルディスクリプタを通じてイベントの登録と取得を行います。
  • kevent(): kqueueの主要なインターフェースです。
    • イベントの登録/変更/削除: changelist引数を通じて、監視したいイベント(例: ソケットの読み込み可能、書き込み可能)をカーネルに登録します。
    • イベントの取得: eventlist引数を通じて、発生したイベントをカーネルから取得します。イベントがない場合は、指定されたタイムアウト期間までブロックできます。
  • struct kevent: keventシステムコールで使用される構造体で、監視対象のイベントや発生したイベントの詳細を記述します。主要なフィールドには以下のようなものがあります。
    • ident: イベントの識別子(ファイルディスクリプタなど)。
    • filter: 監視するイベントの種類(例: EVFILT_READEVFILT_WRITE)。
    • flags: イベントの動作を制御するフラグ(例: EV_ADDでイベントを追加、EV_DELETEで削除、EV_CLEARでイベント発生後にクリア)。
    • fflags: フィルタ固有のフラグ。
    • data: フィルタ固有のデータ(例: 読み込み可能なバイト数)。
    • udata: ユーザーが指定できる任意のデータポインタ。Goランタイムでは、これにPollDesc構造体へのポインタを格納し、イベント発生時に対応するgoroutineを特定するために使用します。

kqueueは、selectpollといった古いAPIに比べて、イベントの登録と取得を効率的に行えるため、多数の同時接続を扱うサーバーアプリケーションなどで広く利用されています。

技術的詳細

このコミットの技術的詳細は、GoランタイムがNetBSDのkqueueをどのように利用するように変更されたかに集約されます。

  1. kqueue関連定数と構造体の定義:

    • src/pkg/runtime/defs_netbsd.goおよび対応するアーキテクチャ固有のヘッダファイル(defs_netbsd_386.h, defs_netbsd_amd64.h, defs_netbsd_arm.h)に、kqueue関連の定数(EV_ADD, EV_DELETE, EV_CLEAR, EV_ERROR, EVFILT_READ, EVFILT_WRITEなど)と、struct keventに対応するKevent型が追加されています。
    • EINTREFAULTのエラーコードの定義も、kqueueの利用に合わせて整理されています。
  2. kqueueシステムコールのラッパー:

    • src/pkg/runtime/sys_netbsd_386.s, src/pkg/runtime/sys_netbsd_amd64.s, src/pkg/runtime/sys_netbsd_arm.sに、kqueueおよびkeventシステムコールをGoランタイムから呼び出すためのアセンブリ言語ラッパーが追加されています。
    • runtime·kqueuekqueue()システムコールを呼び出してkqueueインスタンスのファイルディスクリプタを取得します。
    • runtime·keventkevent()システムコールを呼び出してイベントの登録や取得を行います。引数としてkqkqueueファイルディスクリプタ)、changelist(変更リスト)、nchanges(変更数)、eventlist(イベントリスト)、nevents(イベント数)、timeout(タイムアウト)を取ります。
    • runtime·closeonexecも追加されており、ファイルディスクリプタにFD_CLOEXECフラグを設定することで、execシステムコール時にファイルディスクリプタが子プロセスに継承されないようにします。これはセキュリティとリソース管理の観点から重要です。
  3. ネットワークポーラーのビルドタグ変更:

    • src/pkg/runtime/netpoll.gocのビルドタグにnetbsdが追加され、NetBSDでもGoのネットワークポーラーが有効になるように変更されています。
    • src/pkg/runtime/netpoll_kqueue.cのビルドタグにもnetbsdが追加され、NetBSDがkqueueベースのポーラーを使用するように設定されています。
    • src/pkg/runtime/netpoll_stub.cからはnetbsdが削除され、NetBSDがスタブ実装ではなく実際のkqueueポーラーを使用するようになります。
  4. kevent_udata型の定義:

    • src/pkg/runtime/os_netbsd.hにおいて、kevent構造体のudataフィールドの型としてuintptrが定義されています。これは、GoのポインタをCのvoid*のような形でudataに格納し、イベント発生時にGoのPollDesc構造体へのポインタとして利用するためです。他のOS(darwin, freebsd, openbsd)ではbyte*が使われていますが、NetBSDではuintptrが選択されています。

これらの変更により、GoランタイムはNetBSD上でkqueueを直接利用してネットワークイベントを効率的に処理できるようになり、GoアプリケーションのネットワークI/O性能が向上します。

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

このコミットで変更された主要なファイルとコードの概要は以下の通りです。

  • src/pkg/runtime/defs_netbsd.go:
    • sys/event.hのインクルードを追加。
    • EINTREFAULTの定義位置を移動し、EFAULTを追加。
    • kqueue関連の定数(EV_ADD, EV_DELETE, EV_CLEAR, EV_RECEIPT, EV_ERROR, EVFILT_READ, EVFILT_WRITE)を追加。
    • Kevent型(C.struct_keventに対応)を追加。
  • src/pkg/runtime/defs_netbsd_386.h, src/pkg/runtime/defs_netbsd_amd64.h, src/pkg/runtime/defs_netbsd_arm.h:
    • 各アーキテクチャのヘッダファイルに、kqueue関連の定数とstruct Keventの定義を追加。Kevent構造体のフィールド(ident, filter, flags, fflags, data, udata)が定義されています。amd64版ではidentudatauint64、386/arm版ではuint32となっています。
  • src/pkg/runtime/netpoll.goc:
    • ビルドタグにnetbsdを追加し、NetBSDでもネットワークポーラーが有効になるように変更。
  • src/pkg/runtime/netpoll_kqueue.c:
    • ビルドタグにnetbsdを追加し、NetBSDがkqueueベースのポーラーを使用するように変更。
    • os_GOOS.hのインクルードを追加。
    • runtime·netpollopen関数内でkevent構造体のudataフィールドにkevent_udata型をキャストしてpdPollDescポインタ)を代入するように変更。
  • src/pkg/runtime/netpoll_stub.c:
    • ビルドタグからnetbsdを削除し、NetBSDがスタブポーラーを使用しないように変更。
  • src/pkg/runtime/os_netbsd.h:
    • kevent_udata型をuintptrとして定義。
  • src/pkg/runtime/os_darwin.h, src/pkg/runtime/os_freebsd.h, src/pkg/runtime/os_openbsd.h:
    • kevent_udata型をbyte*として定義(NetBSDとは異なる)。
  • src/pkg/runtime/sys_netbsd_386.s, src/pkg/runtime/sys_netbsd_amd64.s, src/pkg/runtime/sys_netbsd_arm.s:
    • runtime·kqueueruntime·keventruntime·closeonexecの各システムコールラッパーをアセンブリ言語で実装。各アーキテクチャのシステムコール番号と引数の渡し方が考慮されています。

コアとなるコードの解説

defs_netbsd.go および defs_netbsd_*.h

これらのファイルは、GoランタイムがC言語のシステムコールや構造体と連携するための定義を提供します。kqueueの統合にあたり、sys/event.hをインクルードし、kqueueシステムコールで使用される定数(EV_ADDなど)やkevent構造体をGoの型として定義することが不可欠です。これにより、GoコードからこれらのCの要素を安全に参照・操作できるようになります。特にKevent構造体の定義は、keventシステムコールに渡すイベント情報を構築するために直接使用されます。

netpoll.goc, netpoll_kqueue.c, netpoll_stub.c

これらのファイルのビルドタグの変更は、GoランタイムがNetBSD上でどのネットワークポーラー実装を使用するかを決定します。

  • netpoll.gocnetbsdが追加されたことで、Goの汎用ネットワークポーラーインターフェースがNetBSD向けに有効になります。
  • netpoll_kqueue.cnetbsdが追加されたことで、NetBSDはkqueueベースのポーラー実装を使用するようになります。これは、FreeBSDやDarwinなど他のBSD系OSと共通のコードベースを利用できることを意味します。
  • netpoll_stub.cからnetbsdが削除されたことで、NetBSDはもはやダミーのポーラー実装ではなく、実際のkqueueポーラーを使用するようになります。

netpoll_kqueue.c内のruntime·netpollopen関数におけるudataのキャスト変更は、kevent構造体のudataフィールドにGoのPollDesc構造体へのポインタを正しく格納するためのものです。udataはユーザーデータを格納するためのフィールドであり、Goランタイムはこれを利用して、イベントが発生したファイルディスクリプタに対応するGoのPollDesc(ポーリング記述子)を素早く特定し、関連するgoroutineを再開させます。

os_netbsd.h および sys_netbsd_*.s

os_netbsd.hkevent_udatauintptrとして定義されているのは、GoのポインタがCのポインタと互換性を持つようにするためです。uintptrはポインタを保持できる整数型であり、Goのガベージコレクタが管理するポインタをCコードに渡す際に、そのポインタが移動しないようにするための特別な考慮が必要な場合がありますが、ここでは単にアドレス値を渡す目的で使用されています。

sys_netbsd_*.sファイル群は、Goランタイムが直接OSのシステムコールを呼び出すためのアセンブリ言語コードを含んでいます。

  • runtime·kqueueは、kqueue()システムコールを呼び出し、新しいkqueueインスタンスのファイルディスクリプタを返します。これはネットワークポーラーの初期化時に一度だけ呼び出されます。
  • runtime·keventは、kevent()システムコールを呼び出し、イベントの登録や発生したイベントの取得を行います。ネットワークポーラーのメインループで繰り返し呼び出され、ネットワークI/Oの準備ができたファイルディスクリプタを監視します。
  • runtime·closeonexecは、fcntl(fd, F_SETFD, FD_CLOEXEC)システムコールを呼び出すためのラッパーです。これにより、新しく開かれたファイルディスクリプタが子プロセスに意図せず継承されるのを防ぎ、リソースリークやセキュリティ上の問題を回避します。

これらのアセンブリ言語ルーチンは、GoランタイムがOSカーネルと直接対話するための低レベルなインターフェースを提供し、GoのネットワークポーラーがNetBSDのkqueueメカニズムを最大限に活用できるようにします。

関連リンク

参考にした情報源リンク