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

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

このコミットは、Go言語のosパッケージにおけるPlan 9オペレーティングシステム向けのファイルI/O処理に関する修正です。具体的には、Plan 9のI/O特性に合わせて、ゼロバイトの書き込み(zero-length writes)を避けるように変更されています。

コミット

commit 722ee1f4797a81916b19e80df479058897c44923
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date:   Fri Feb 22 23:06:25 2013 +0100

    os: Plan 9: avoid doing zero-length writes.
    
    Plan 9 I/O preserves message boundaries, while Go
    library code is written for UNIX-like operating
    systems which do not. Avoid doing zero-length
    writes in package os.
    
    R=rsc, rminnich, ality, rminnich, r
    CC=golang-dev
    https://golang.org/cl/7406046

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

https://github.com/golang/go/commit/722ee1f4797a81916b19e80df479058897c44923

元コミット内容

このコミットは、Go言語のosパッケージにおいて、Plan 9オペレーティングシステム上でのゼロバイト書き込みを回避するための変更を導入します。その理由は、Plan 9のI/Oがメッセージ境界を保持するのに対し、Goの標準ライブラリコードはUNIXライクなシステム(メッセージ境界を保持しない)向けに書かれているためです。

変更の背景

Go言語の標準ライブラリ、特にファイルI/Oを扱うosパッケージは、主にUNIXライクなオペレーティングシステム(Linux, macOS, BSDなど)のセマンティクスを前提として設計されています。これらのシステムでは、ファイルへの書き込みはバイトストリームとして扱われ、書き込みサイズがゼロであっても通常はエラーになりません(単に何も書き込まれないだけです)。

しかし、Plan 9というオペレーティングシステムは、そのI/OモデルがUNIXとは大きく異なります。Plan 9のファイルシステムプロトコル(9P)は、I/O操作を「メッセージ」として扱います。これは、書き込み操作が特定のデータブロック(メッセージ)として扱われ、そのメッセージが空(ゼロバイト)であることは、システムによっては無効な操作と見なされたり、予期せぬ挙動を引き起こしたりする可能性があります。

GoのライブラリがUNIXのセマンティクスに依存してゼロバイト書き込みを行う場合、Plan 9環境ではこの特性の不一致が問題となり、潜在的なバグや非効率性を引き起こす可能性がありました。このコミットは、このOS間のI/Oセマンティクスの違いを吸収し、Plan 9上でのGoプログラムの堅牢性を向上させることを目的としています。

前提知識の解説

Plan 9のI/Oモデルとメッセージ境界

Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。その設計思想の中心には「すべてがファイルである」というUNIXの哲学をさらに推し進めたものがあります。しかし、そのI/OモデルはUNIXとは異なります。

  • UNIXライクなI/O: UNIX系システムでは、ファイルやデバイスへのI/Oは基本的にバイトストリームとして扱われます。write()システムコールは指定されたバイト数を書き込もうとし、成功すれば書き込んだバイト数を返します。書き込みサイズがゼロの場合、通常は0を返し、エラーとはなりません。データは連続したバイト列として扱われ、個々のwrite()呼び出しが「メッセージ」として区切られることはありません。

  • Plan 9のI/Oとメッセージ境界: Plan 9では、ファイルシステムプロトコル(9P)を通じてI/Oが行われます。このプロトコルは、クライアントとサーバー間で「メッセージ」を交換することで動作します。write操作もまた、特定のサイズのデータを含むメッセージとして扱われます。この「メッセージ境界の保持」という特性は、UNIXのバイトストリームモデルとは根本的に異なります。ゼロバイトのメッセージは、プロトコルによっては意味を持たないか、あるいは不正な操作と解釈される可能性があります。

Go言語のsyscallパッケージ

Go言語のsyscallパッケージは、オペレーティングシステムの低レベルなシステムコールにアクセスするためのインターフェースを提供します。osパッケージのような高レベルなI/O操作は、内部的にこのsyscallパッケージを利用してOSの機能とやり取りしています。

  • syscall.Write(fd int, p []byte) (n int, err error): 指定されたファイルディスクリプタfdに対して、バイトスライスpの内容を書き込みます。書き込んだバイト数nとエラーを返します。
  • syscall.Pwrite(fd int, p []byte, off int64) (n int, err error): writeと同様ですが、ファイルディスクリプタfdの指定されたオフセットoffから書き込みを開始します。

これらのシステムコールは、OSのI/Oセマンティクスに直接依存します。したがって、Plan 9のような特殊なI/Oモデルを持つOSでは、Goの標準ライブラリがこれらのシステムコールを呼び出す際に、OSの特性を考慮した追加のロジックが必要になることがあります。

技術的詳細

このコミットが解決しようとしている問題は、GoのosパッケージがUNIXライクなシステムでの動作を前提としているために、Plan 9の「メッセージ境界を保持するI/O」という特性と衝突することです。

具体的には、Goのコードがwritepwriteのようなファイル書き込み関数を呼び出す際に、書き込むバイトスライスbの長さが0である場合、UNIX系システムではこれは単に「何も書き込まない」という操作として扱われ、通常は問題ありません。しかし、Plan 9では、ゼロバイトの書き込みが「空のメッセージ」として解釈され、これがプロトコルレベルで不正な操作と見なされたり、予期せぬエラーや挙動を引き起こす可能性がありました。

このコミットの解決策は非常にシンプルかつ効果的です。osパッケージ内のPlan 9固有のファイルI/O実装であるFile.writeおよびFile.pwriteメソッドにおいて、実際にsyscall.Writesyscall.Pwriteを呼び出す前に、書き込むバイトスライスbの長さがゼロであるかどうかをチェックします。もしlen(b)0であれば、実際のシステムコールを呼び出すことなく、直ちに0バイト書き込み成功として0, nil(書き込んだバイト数0、エラーなし)を返します。

これにより、Plan 9のI/Oシステムにゼロバイトのメッセージが送信されることを防ぎ、OSの特性に合わせた堅牢な動作を保証します。GoのライブラリコードはUNIXライクなセマンティクスを維持しつつ、Plan 9環境での互換性と安定性を確保しています。

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

変更はsrc/pkg/os/file_plan9.goファイルに集中しています。

--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -244,13 +244,23 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {

 // write writes len(b) bytes to the File.
 // It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
 func (f *File) write(b []byte) (n int, err error) {
+\tif len(b) == 0 {\n+\t\treturn 0, nil\n+\t}\n     	return syscall.Write(f.fd, b)
 }

 // pwrite writes len(b) bytes to the File starting at byte offset off.
 // It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
 func (f *File) pwrite(b []byte, off int64) (n int, err error) {
+\tif len(b) == 0 {\n+\t\treturn 0, nil\n+\t}\n     	return syscall.Pwrite(f.fd, b, off)
 }

コアとなるコードの解説

変更は、File構造体のwriteメソッドとpwriteメソッドの2箇所に適用されています。

  1. func (f *File) write(b []byte) (n int, err error)

    • このメソッドは、ファイルにバイトスライスbの内容を書き込むためのものです。
    • 追加された行:
      	if len(b) == 0 {
      		return 0, nil
      	}
      
    • このif文は、書き込もうとしているバイトスライスbの長さがゼロであるかどうかをチェックします。
    • もしlen(b)0であれば、実際のシステムコールsyscall.Writeを呼び出すことなく、直ちに0(書き込んだバイト数)とnil(エラーなし)を返します。これにより、Plan 9のI/Oシステムにゼロバイトの書き込み要求が送られることを防ぎます。
    • コメント// Since Plan 9 preserves message boundaries, never allow // a zero-byte write.が追加され、この変更の理由が明確に示されています。
  2. func (f *File) pwrite(b []byte, off int64) (n int, err error)

    • このメソッドは、指定されたオフセットoffからファイルにバイトスライスbの内容を書き込むためのものです。
    • writeメソッドと同様に、以下の行が追加されています。
      	if len(b) == 0 {
      		return 0, nil
      	}
      
    • ここでも、len(b)0であれば、syscall.Pwriteを呼び出すことなく0, nilを返します。
    • 同様のコメントが追加され、Plan 9のメッセージ境界の特性が強調されています。

これらの変更により、GoのosパッケージはPlan 9環境において、ゼロバイト書き込みに関する潜在的な問題を回避し、より堅牢で互換性のある動作を実現しています。これは、異なるOSのI/OセマンティクスをGoの標準ライブラリが適切に吸収するための典型的な例と言えます。

関連リンク

参考にした情報源リンク

  • (特になし。コミットメッセージと一般的なOSのI/O知識に基づいています。)