[インデックス 10374] ファイルの概要
このコミットは、Linux ARM環境におけるGoのsyscallパッケージのビルドエラーを修正するための大規模な変更です。具体的には、システムコール関数の戻り値の型を従来のerrno int
から新しいerr error
に変更し、Go 1.0で導入されたerrorインターフェースに対応させるための再生成作業が行われました。
コミット
- コミットハッシュ: 45eef04ed462e52389195e960960bfbf40a7ec4b
- 作成者: Russ Cox rsc@golang.org
- 日時: 2011年11月14日 1:23:27 (EST)
- メッセージ: syscall: fix linux arm build / Regenerate system call file.
- レビュー: TBR=bradfitz (Trivial But Reviewed)
- 対象ファイル:
src/pkg/syscall/zsyscall_linux_arm.go
(744行変更、496行追加、248行削除)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/45eef04ed462e52389195e960960bfbf40a7ec4b
元コミット内容
ファイル変更の詳細
変更対象はsrc/pkg/syscall/zsyscall_linux_arm.go
で、Linux ARM環境向けのシステムコール関数の自動生成ファイルです。このファイルには100個以上のシステムコール関数が含まれており、すべての関数で以下のパターンの変更が行われました:
変更前のパターン:
func open(path string, mode int, perm uint32) (fd int, errno int) {
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(StringBytePtr(path))), uintptr(mode), uintptr(perm))
fd = int(r0)
errno = int(e1)
return
}
変更後のパターン:
func open(path string, mode int, perm uint32) (fd int, err error) {
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(StringBytePtr(path))), uintptr(mode), uintptr(perm))
fd = int(r0)
if e1 != 0 {
err = e1
}
return
}
変更の背景
Go 1.0に向けた準備
このコミットは2011年11月に行われており、Go 1.0リリース(2012年3月28日)の約4ヶ月前にあたります。Go 1.0では、エラーハンドリングの統一を図るため、組み込みのerror
インターフェースが導入される予定でした。
Linux ARM対応の必要性
2011年当時、ARM系プロセッサはモバイルデバイスの普及により急速に重要性を増していました。Goの汎用システムプログラミング言語としての位置づけを確立するために、ARM環境での安定動作は不可欠でした。
syscallパッケージの特殊な位置づけ
syscallパッケージは、Go言語からオペレーティングシステムの低レベル機能に直接アクセスするための重要なパッケージです。このパッケージの関数は通常、自動生成ツールによって作成されるため、一括での変更が必要でした。
前提知識の解説
Goのerrorインターフェース
Go 1.0で導入されたerrorインターフェースは、以下のシンプルな定義を持ちます:
type error interface {
Error() string
}
このインターフェースを実装する任意の型は、エラーとして扱うことができます。これにより、型安全性を保ちながら、エラー情報を柔軟に表現できるようになりました。
syscall.Errno型
Unix系システムでは、システムコール実行時のエラーはerrno
という整数値で表現されます。Goのsyscallパッケージでは、この概念をsyscall.Errno
型として表現しており、この型はerror
インターフェースを実装しています:
type Errno uintptr
func (e Errno) Error() string {
return "errno " + itoa(int(e))
}
システムコール関数の自動生成
zsyscall_linux_arm.go
は自動生成ファイルで、ファイルの最上部に「THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT」というコメントが含まれています。これは、手動での編集を禁止し、生成ツールによる一括更新を前提としていることを示しています。
技術的詳細
エラーハンドリングの変更内容
変更前の問題点
従来のerrno int
形式には以下の問題がありました:
- 型安全性の欠如: 単純なint型のため、実際のエラー番号なのか他の値なのかが不明確
- Goのイディオムとの不整合: Go 1.0で導入される
error
インターフェースに適合しない - エラー処理の一貫性の欠如: 他のGoパッケージとは異なるエラー処理パターン
変更後の改善点
新しいerr error
形式では:
- 統一されたエラーインターフェース: すべてのGoコードで一貫したエラー処理
- 型安全性の向上: コンパイル時にエラー処理の正確性を検証可能
- 条件付きエラー設定:
if e1 != 0
でエラーの有無を明確に判定
システムコール実行フローの詳細
変更前のフロー
1. Syscall() 実行
2. 戻り値を受け取り (r0, _, e1)
3. errno = int(e1) で無条件に設定
4. 成功時でも errno に値が設定される可能性
変更後のフロー
1. Syscall() 実行
2. 戻り値を受け取り (r0, _, e1)
3. if e1 != 0 でエラーの有無を判定
4. エラー時のみ err = e1 でエラーを設定
5. 成功時は err = nil が保証される
パフォーマンスへの影響
メモリアロケーション
syscall.Errno
型はerror
インターフェースを実装しているため、整数値からerror
インターフェースへの変換時に型アサーションが発生します。ただし、Errno
型は値型であり、インターフェース変換時の追加のヒープアロケーションは最小限に抑えられています。
実行時オーバーヘッド
条件分岐(if e1 != 0
)の追加により、わずかな実行時オーバーヘッドが発生しますが、これは現代的なプロセッサでは無視できるレベルです。むしろ、エラー時のみerror
インターフェースを設定することで、成功時のオーバーヘッドを削減できます。
コアとなるコードの変更箇所
代表的な関数の変更例
1. open システムコール
// 変更前
func open(path string, mode int, perm uint32) (fd int, errno int) {
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(StringBytePtr(path))), uintptr(mode), uintptr(perm))
fd = int(r0)
errno = int(e1)
return
}
// 変更後
func open(path string, mode int, perm uint32) (fd int, err error) {
r0, _, e1 := Syscall(SYS_OPEN, uintptr(unsafe.Pointer(StringBytePtr(path))), uintptr(mode), uintptr(perm))
fd = int(r0)
if e1 != 0 {
err = e1
}
return
}
2. Readシステムコール(スライス引数を持つ例)
// 変更前
func Read(fd int, p []byte) (n int, errno int) {
var _p0 unsafe.Pointer
if len(p) > 0 {
_p0 = unsafe.Pointer(&p[0])
} else {
_p0 = unsafe.Pointer(&_zero)
}
r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(_p0), uintptr(len(p)))
n = int(r0)
errno = int(e1)
return
}
// 変更後
func Read(fd int, p []byte) (n int, err error) {
var _p0 unsafe.Pointer
if len(p) > 0 {
_p0 = unsafe.Pointer(&p[0])
} else {
_p0 = unsafe.Pointer(&_zero)
}
r0, _, e1 := Syscall(SYS_READ, uintptr(fd), uintptr(_p0), uintptr(len(p)))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
3. 複雑な戻り値を持つシステムコール(Tee関数)
// 変更前
func Tee(rfd int, wfd int, len int, flags int) (n int64, errno int) {
r0, r1, e1 := Syscall6(SYS_TEE, uintptr(rfd), uintptr(wfd), uintptr(len), uintptr(flags), 0, 0)
n = int64(int64(r0)<<32 | int64(r1))
errno = int(e1)
return
}
// 変更後
func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
r0, r1, e1 := Syscall6(SYS_TEE, uintptr(rfd), uintptr(wfd), uintptr(len), uintptr(flags), 0, 0)
n = int64(int64(r0)<<32 | int64(r1))
if e1 != 0 {
err = e1
}
return
}
変更されていない関数の例
一部の関数(GetpidやGetuidなど)は、エラーを返さないため変更対象外です:
func Getpid() (pid int) {
r0, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
pid = int(r0)
return
}
コアとなるコードの解説
エラー処理パターンの統一
このコミットの最も重要な側面は、すべてのシステムコール関数で一貫したエラー処理パターンを導入したことです。
パターン1: 戻り値のみのシステムコール
func Close(fd int) (err error) {
_, _, e1 := Syscall(SYS_CLOSE, uintptr(fd), 0, 0)
if e1 != 0 {
err = e1
}
return
}
このパターンでは、システムコールの成功・失敗のみが重要で、戻り値は使用されません。
パターン2: 戻り値とエラーの両方を返すシステムコール
func Dup(oldfd int) (fd int, err error) {
r0, _, e1 := RawSyscall(SYS_DUP, uintptr(oldfd), 0, 0)
fd = int(r0)
if e1 != 0 {
err = e1
}
return
}
このパターンでは、新しいファイルディスクリプタ(成功時)とエラー情報(失敗時)の両方を返します。
パターン3: 複雑な戻り値を持つシステムコール
func mmap2(addr uintptr, length uintptr, prot int, flags int, fd int, pageOffset uintptr) (xaddr uintptr, err error) {
r0, _, e1 := Syscall6(SYS_MMAP2, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(pageOffset))
xaddr = uintptr(r0)
if e1 != 0 {
err = e1
}
return
}
メモリマッピングのような複雑な操作では、成功時には新しいアドレスを、失敗時にはエラーを返します。
Unsafeポインタ操作の保持
このコミットでは、エラー処理の変更のみが行われ、unsafeパッケージを使用したポインタ操作は保持されています。これは、システムレベルの操作において、パフォーマンスと安全性のバランスを取る重要な決定です。
func Read(fd int, p []byte) (n int, err error) {
var _p0 unsafe.Pointer
if len(p) > 0 {
_p0 = unsafe.Pointer(&p[0]) // スライスの先頭要素のアドレス
} else {
_p0 = unsafe.Pointer(&_zero) // 空スライス用のダミーポインタ
}
// ...
}
ARM固有の考慮事項
ARM環境では、64ビット値の扱いに特別な配慮が必要です。例えば、Fallocate
関数では:
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
_, _, e1 := Syscall6(SYS_FALLOCATE, uintptr(fd), uintptr(mode), uintptr(off>>32), uintptr(off), uintptr(len>>32), uintptr(len))
if e1 != 0 {
err = e1
}
return
}
64ビット値(off
とlen
)を32ビットARMシステムで扱うため、上位32ビットと下位32ビットに分割して渡しています。
関連リンク
公式ドキュメント
Go言語のエラーハンドリング
技術仕様
関連するGitHubイシュー
- Issue #37627: Not all os/error's handled in syscall.Errno.Is
- Issue #29054: errors: no obvious way to map error to errno values
- Issue #26907: syscall: errno handling for write call may inappropriate
参考にした情報源リンク
Web検索結果
- Golang syscall package error handling research - syscallパッケージの公式ドキュメント
- Go 1.0 error interface introduction documentation - エラーハンドリングと Go についてのブログポスト
- Go 1 compatibility and backward compatibility - Go 1の互換性に関する公式ドキュメント
- Error interface design and implementation - エラー値に関するFAQ
追加調査資料
- Goソースコードリポジトリでの関連コミット
- Go開発者メーリングリストでの議論
- ARM環境でのGo言語実装に関する技術文書
- Unix系システムのerrno仕様に関する標準文書
このコミットは、Go言語の成熟に向けた重要なマイルストーンの一つであり、現代のGo言語エラーハンドリングの基礎を築いた歴史的な変更と言えます。