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

[インデックス 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形式には以下の問題がありました:

  1. 型安全性の欠如: 単純なint型のため、実際のエラー番号なのか他の値なのかが不明確
  2. Goのイディオムとの不整合: Go 1.0で導入されるerrorインターフェースに適合しない
  3. エラー処理の一貫性の欠如: 他のGoパッケージとは異なるエラー処理パターン

変更後の改善点

新しいerr error形式では:

  1. 統一されたエラーインターフェース: すべてのGoコードで一貫したエラー処理
  2. 型安全性の向上: コンパイル時にエラー処理の正確性を検証可能
  3. 条件付きエラー設定: 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ビット値(offlen)を32ビットARMシステムで扱うため、上位32ビットと下位32ビットに分割して渡しています。

関連リンク

公式ドキュメント

Go言語のエラーハンドリング

技術仕様

関連するGitHubイシュー

参考にした情報源リンク

Web検索結果

  1. Golang syscall package error handling research - syscallパッケージの公式ドキュメント
  2. Go 1.0 error interface introduction documentation - エラーハンドリングと Go についてのブログポスト
  3. Go 1 compatibility and backward compatibility - Go 1の互換性に関する公式ドキュメント
  4. Error interface design and implementation - エラー値に関するFAQ

追加調査資料

  • Goソースコードリポジトリでの関連コミット
  • Go開発者メーリングリストでの議論
  • ARM環境でのGo言語実装に関する技術文書
  • Unix系システムのerrno仕様に関する標準文書

このコミットは、Go言語の成熟に向けた重要なマイルストーンの一つであり、現代のGo言語エラーハンドリングの基礎を築いた歴史的な変更と言えます。