[インデックス 1555] ファイルの概要
このコミットは、Go言語の初期開発段階において、os.FD
型のファイルディスクリプタへのアクセス方法を修正し、コンパイラのバグに依存しない堅牢なコードベースを構築することを目的としています。具体的には、os.FD
構造体内のファイルディスクリプタを保持するフィールドの可視性を変更し、それに伴い net
パッケージ内の PollServer
コードにおけるアクセス方法を更新しています。
コミット
commit a01bdb4ae0410f9ff81defbe16461e7efebe2077
Author: Ian Lance Taylor <iant@golang.org>
Date: Mon Jan 26 11:10:14 2009 -0800
Add an accessor function os.FD.Fd() to get the file
descriptor. Use it in the PollServer code.
6g currently accepts this code without this change, but it
should not. Test case for the bug is bug133.go.
R=rsc
DELTA=10 (0 added, 0 deleted, 10 changed)
OCL=23451
CL=23486
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a01bdb4ae0410f9ff81defbe16461e7efebe2077
元コミット内容
Add an accessor function os.FD.Fd() to get the file
descriptor. Use it in the PollServer code.
6g currently accepts this code without this change, but it
should not. Test case for the bug is bug133.go.
変更の背景
このコミットの主な背景は、Go言語の初期コンパイラである 6g
に存在したバグにあります。当時、os
パッケージの FD
構造体には、ファイルディスクリプタを格納するための fd
という名前のフィールドがありました。Go言語の可視性ルールでは、小文字で始まるフィールドはパッケージプライベート(アンエクスポート)であり、そのパッケージ内からのみアクセス可能であるべきです。しかし、6g
コンパイラはこのルールを正しく適用せず、net
パッケージのような外部パッケージから os.FD
のプライベートフィールド fd
に直接アクセスすることを許容してしまっていました。
これは言語仕様に反する動作であり、潜在的なバグや将来の互換性の問題を引き起こす可能性がありました。コミットメッセージに Test case for the bug is bug133.go.
とあるように、この不正なアクセスがバグとして認識され、その修正のためにこの変更が導入されました。ファイルディスクリプタへのアクセスを、Goの言語仕様に準拠した正しい方法で行う必要がありました。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびオペレーティングシステムの基本的な概念を理解しておく必要があります。
-
Go言語のパッケージとエクスポート/アンエクスポート (Go Language Packages and Exported/Unexported): Go言語では、識別子(変数名、関数名、型名、構造体のフィールド名など)の最初の文字が大文字である場合、その識別子は「エクスポート」され、他のパッケージからアクセス可能になります。一方、最初の文字が小文字である場合、その識別子は「アンエクスポート」(またはプライベート)であり、同じパッケージ内からのみアクセス可能です。このルールは、Goにおけるカプセル化とモジュール性を実現するための基本的なメカニズムです。
-
ファイルディスクリプタ (File Descriptor - FD): ファイルディスクリプタは、オペレーティングシステムがファイルやI/Oリソース(ソケット、パイプなど)を識別するために使用する、整数値の抽象的なハンドルです。プログラムがファイルを開いたり、ネットワーク接続を確立したりすると、OSは対応するファイルディスクリプタを返します。プログラムは、このファイルディスクリプタを使用して、読み書きやクローズなどのI/O操作を行います。
-
os.FD
型 (Go'sos.FD
type): Goの標準ライブラリos
パッケージにおける、ファイルディスクリプタをラップするための構造体です。この構造体は、ファイルディスクリプタの値を内部に保持し、ファイル操作に関連するメソッドを提供します。このコミットの時点では、この構造体がどのようにファイルディスクリプタを公開するかが問題となっていました。 -
PollServer
(ポーリングサーバー):PollServer
は、I/O多重化(I/O multiplexing)を実現するためのメカニズムの一部です。I/O多重化とは、単一のスレッドで複数のI/O操作(例えば、複数のネットワークソケットからのデータ受信)を効率的に監視し、データが利用可能になったI/O操作のみを処理する手法です。これにより、多数の同時接続を扱うサーバーアプリケーションのパフォーマンスを向上させることができます。select
,poll
,epoll
(Linux),kqueue
(BSD/macOS) などのシステムコールがその基盤となります。この文脈では、Goのネットワークスタックの一部として、効率的なI/O処理のために使用される内部コンポーネントと考えられます。 -
6g
コンパイラ (6g Compiler):6g
は、Go言語の初期のコンパイラの一つです。Goのツールチェーンは、異なるCPUアーキテクチャをターゲットとする複数のコンパイラを含んでいました(例:6g
はamd64アーキテクチャ用、8g
は386アーキテクチャ用)。このコミットは2009年に行われており、Go言語がまだ活発な初期開発段階にあったことを示唆しています。当時のコンパイラには、現在のGoの厳密な言語仕様とは異なる挙動を示すバグが存在することがありました。 -
syscall
パッケージ: Goのsyscall
パッケージは、オペレーティングシステムのシステムコールに直接アクセスするための機能を提供します。これにより、Goプログラムは低レベルのOS機能(ファイルディスクリプタのクローズ、読み書きなど)を直接操作できます。
技術的詳細
このコミットは、Go言語の初期におけるカプセル化の強制と、コンパイラのバグ修正という二重の側面を持っています。
問題の核心:
以前の os.FD
構造体は、ファイルディスクリプタの値を fd
という名前の int64
型のフィールドとして保持していました。この fd
フィールドは小文字で始まるため、Goの可視性ルールに従えば、os
パッケージ内からのみアクセス可能であるべきでした。しかし、当時の 6g
コンパイラにはバグがあり、net
パッケージのような os
パッケージとは異なるパッケージから、このプライベートな fd
フィールドに直接アクセスできてしまっていました。これはGoの言語仕様に違反する動作であり、プログラムの意図しない挙動や、将来のコンパイラや言語仕様の変更によってコードが壊れる可能性を秘めていました。
解決策と実装: この問題を解決するために、以下の変更が行われました。
-
os.FD
構造体のフィールド名変更:src/lib/os/os_file.go
において、FD
構造体の定義がtype FD struct { fd int64 }
からtype FD struct { Fd int64 }
に変更されました。この変更は非常に重要です。フィールド名をfd
(小文字) からFd
(大文字) に変更することで、Goの可視性ルールに従い、このフィールドが「エクスポート」されるようになりました。これにより、os
パッケージ外の他のパッケージ(例えばnet
パッケージ)から、os.FD
インスタンスのFd
フィールドに合法的に直接アクセスできるようになります。 -
PollServer
コードの更新:src/lib/net/fd.go
にあるPollServer
の実装において、os.FD
インスタンスが保持するファイルディスクリプタにアクセスするすべての箇所が修正されました。具体的には、s.pr.fd
やs.pw.fd
のようにプライベートフィールドに直接アクセスしていたコードが、新しくエクスポートされたフィールドs.pr.Fd
やs.pw.Fd
を使用するように変更されました。これにより、PollServer
はGoの言語仕様に準拠した正しい方法でファイルディスクリプタにアクセスするようになりました。
「アクセサ関数」の解釈について:
コミットメッセージには「accessor function os.FD.Fd() を追加」と記載されていますが、実際のコード変更はフィールド名を fd
から Fd
に変更し、それを直接使用するようにしたものです。Go言語では、エクスポートされたフィールドを直接参照することが一般的であり、多くの場合、別途アクセサ関数を定義する必要はありません。このコミットメッセージの表現は、当時のGoの慣習や、エクスポートされたフィールドが実質的にその値への「アクセサ」として機能するという概念を反映している可能性があります。あるいは、将来的に複雑なロジックを伴う真のアクセサ関数が追加される可能性を示唆していたのかもしれません。しかし、このコミットの直接的な目的は、コンパイラのバグによって不正にアクセスされていたプライベートフィールドへのアクセスを、Goの言語仕様に則った正しい方法(エクスポートされたフィールドへの直接アクセス)に切り替えることでした。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルに集中しています。
-
src/lib/os/os_file.go
FD
構造体の定義が変更されました。
これにより、--- a/src/lib/os/os_file.go +++ b/src/lib/os/os_file.go @@ -9,7 +9,7 @@ import os "os" // FDs are wrappers for file descriptors type FD struct { - fd int64 + Fd int64 }
FD
構造体の内部ファイルディスクリプタフィールドがプライベート (fd
) からエクスポート (Fd
) に変更されました。FD
構造体のメソッド内(Close
,Read
,Write
,WriteString
)で、自身のfd
フィールドにアクセスしていた箇所が、新しいエクスポートされたフィールド名Fd
を使用するように修正されました。--- a/src/lib/os/os_file.go +++ b/src/lib/os/os_file.go @@ -48,8 +48,8 @@ func (fd *FD) Close() *Error { if fd == nil { return EINVAL } - r, e := syscall.Close(fd.fd); - fd.fd = -1; // so it can\'t be closed again + r, e := syscall.Close(fd.Fd); + fd.Fd = -1; // so it can\'t be closed again return ErrnoToError(e) } // ... (Read, Write, WriteString メソッドでも同様の変更)
-
src/lib/net/fd.go
_PollServer
の初期化関数_NewPollServer()
およびRun()
メソッド内で、パイプのファイルディスクリプタ (s.pr
,s.pw
) にアクセスする際に、プライベートフィールドfd
への直接アクセスを、エクスポートされたフィールドFd
へのアクセスに修正。--- a/src/lib/net/fd.go +++ b/src/lib/net/fd.go @@ -84,19 +84,19 @@ func _NewPollServer() (s *_PollServer, err *os.Error) { if s.pr, s.pw, err = os.Pipe(); err != nil { return nil, err } - if err = _SetNonblock(s.pr.fd); err != nil { + if err = _SetNonblock(s.pr.Fd); err != nil { Error: s.pr.Close(); s.pw.Close(); return nil, err } - if err = _SetNonblock(s.pw.fd); err != nil { + if err = _SetNonblock(s.pw.Fd); err != nil { goto Error } if s.poll, err = NewPollster(); err != nil { goto Error } - if err = s.poll.AddFD(s.pr.fd, \'r\', true); err != nil { + if err = s.poll.AddFD(s.pr.Fd, \'r\', true); err != nil { s.poll.Close(); goto Error } @@ -142,7 +142,7 @@ func (s *_PollServer) Run() { print("_PollServer WaitFD: ", err.String(), "\\n"); return } - if fd == s.pr.fd { + if fd == s.pr.Fd { // Drain our wakeup pipe. for nn, e := s.pr.Read(scratch); nn > 0; { nn, e = s.pr.Read(scratch)
コアとなるコードの解説
このコミットの核心は、Go言語における「エクスポートされた識別子」の概念を正しく適用することにあります。
src/lib/os/os_file.go
における FD
構造体のフィールド名変更 (fd
-> Fd
) は、Goの言語仕様に準拠するための重要なステップです。これにより、os.FD
型のインスタンスが持つファイルディスクリプタの値が、os
パッケージの外部からも直接アクセス可能なパブリックなフィールドとして公開されることになります。これは、Goがカプセル化を実現する上で、フィールド名の先頭文字の大小を厳密に区別していることの表れです。
src/lib/net/fd.go
の変更は、この os.FD
構造体の変更に追従したものです。_PollServer
は、パイプ(s.pr
, s.pw
)のファイルディスクリプタを操作するために os.FD
型のインスタンスを使用しています。以前は、6g
コンパイラのバグにより、本来アクセスできないはずのプライベートフィールド fd
に直接アクセスしていました。このコミットにより、_SetNonblock
関数への引数や poll.AddFD
メソッドへの引数、さらには fd == s.pr.fd
のような比較において、新しくエクスポートされた Fd
フィールド (s.pr.Fd
, s.pw.Fd
) を使用するように修正されました。
この一連の変更により、PollServer
のコードは、コンパイラのバグに依存することなく、Goの言語仕様に則った正しい方法でファイルディスクリプタにアクセスするようになりました。これは、Go言語の初期段階において、言語仕様の厳密な適用と、それに基づくコードの健全性を確保するための重要な修正であったと言えます。
関連リンク
-
Go言語の可視性ルール (Exported identifiers in Go): Go言語における識別子のエクスポートに関する公式ドキュメントのセクションです。このコミットの背景にある言語仕様の理解に役立ちます。 https://go.dev/doc/effective_go#names
-
Go言語の初期のコンパイラ (
6g
など) について: Go言語の歴史や初期のツールチェーンに関する情報は、Goの公式ブログや初期の設計ドキュメント、またはGoのソースコードリポジトリの古いコミット履歴から見つけることができます。
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のGitHubリポジトリ (特にコミット履歴と関連するIssue)
- ファイルディスクリプタ、I/O多重化(
poll
など)に関するオペレーティングシステムの一般的な概念に関する資料。