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

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

このコミットは、Go言語のランタイム(runtime)およびシステムコール(syscall)パッケージにおいて、システム定義(defs.htypes_*.hなど)の生成メカニズムをgodefsツールからcgoツールベースの方式へと移行するものです。これにより、GoがOSのシステムコールや構造体定義を扱う方法が根本的に変更され、より堅牢でメンテナンス性の高いシステムが構築されます。

コミット

  • コミットハッシュ: dd2abe51526867b7574a10708a028a24a3a41ad9
  • Author: Russ Cox rsc@golang.org
  • Date: Thu Nov 10 19:08:28 2011 -0500

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

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

元コミット内容

runtime, syscall: convert from godefs to cgo

R=golang-dev, mikioh.mikioh, r
CC=golang-dev
https://golang.org/cl/5348052

変更の背景

Go言語の初期のバージョンでは、OS固有の定数や構造体定義をGoコードから利用するために、godefsというカスタムツールが使用されていました。godefsはC言語のヘッダーファイル(.cファイル)を解析し、それに対応するGo言語の構造体や定数定義を自動生成する役割を担っていました。しかし、このアプローチにはいくつかの課題がありました。

  1. メンテナンスの複雑さ: godefsはC言語のパーサーを内蔵しており、C言語の構文やプリプロセッサの変更に対応する必要がありました。これは、Go言語のコア開発チームにとって追加のメンテナンス負担となっていました。
  2. 正確性の問題: C言語の複雑な型システムやプリプロセッサディレクティブを完全にエミュレートすることは困難であり、godefsが生成する定義が常にOSの実際の定義と完全に一致するとは限りませんでした。特に、異なるOSやアーキテクチャ間での差異を吸収するのが難しい場合がありました。
  3. GoとCの連携の進化: Go言語にはC言語のコードを呼び出すためのcgoツールが標準で提供されており、その機能は時間とともに成熟していました。cgoは、C言語のヘッダーファイルを直接読み込み、GoコードからCの関数や構造体を利用するためのバインディングを生成する能力を持っています。

これらの課題を解決し、Goのランタイムとシステムコールパッケージの堅牢性、正確性、メンテナンス性を向上させるため、godefsからcgoへの移行が決定されました。cgoを利用することで、GoはOSのCヘッダーファイルを直接参照し、より正確でOSネイティブな定義をGoコードに反映できるようになります。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

1. システムコール (System Call)

システムコールは、ユーザー空間で動作するプログラムが、カーネル空間で提供されるサービス(ファイルI/O、メモリ管理、プロセス制御など)を利用するためのインターフェースです。Go言語のような高レベル言語でOSの機能を利用する場合、最終的にはシステムコールを介してカーネルとやり取りします。Goのsyscallパッケージは、これらのシステムコールへの低レベルなアクセスを提供します。

2. Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理するGo言語の一部です。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ割り当て、そしてOSとの低レベルなインタラクション(システムコールを含む)などが含まれます。ランタイムは、Goプログラムが異なるOSやアーキテクチャ上で動作できるようにするための抽象化レイヤーを提供します。

3. godefs (旧ツール)

godefsは、Go言語の初期にGoプログラムがC言語の構造体や定数定義を利用するために開発されたカスタムツールです。C言語のヘッダーファイル(通常は.cファイルにCの定義を記述し、それをgodefsが解析する形式)を読み込み、Go言語のソースコード(.goファイル)として対応する定義を生成していました。このプロセスは、GoプログラムがOS固有のデータ構造や定数にアクセスするために必要でした。

4. cgo (GoとCの連携ツール)

cgoは、Go言語に標準で組み込まれているツールで、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムを提供します。cgoは、Goソースファイル内に記述されたCコードブロックを解析し、GoとCの間でデータをやり取りするためのバインディングコードを生成します。

このコミットでは、cgocdefsモードが活用されています。cdefsモードは、C言語のヘッダーファイルからGo言語の定数や構造体定義を自動生成するために使用されます。これにより、GoプログラムはOSのCヘッダーファイルを直接参照し、その定義をGoの型として利用できるようになります。

5. defs.hdefs.go ファイル

Goのランタイムやシステムコールパッケージでは、OS固有の定数や構造体の定義を管理するために、通常、以下のようなファイルが使用されます。

  • defs.h: C言語のヘッダーファイルで、OSのシステムコールやデータ構造に関する定数や型定義が含まれます。これらはGoランタイムやsyscallパッケージのC部分から利用されます。
  • defs.go: Go言語のファイルで、defs.hに対応するGo言語の定数や型定義が含まれます。これらはGoランタイムやsyscallパッケージのGo部分から利用されます。

このコミットの変更は、これらのdefs.hdefs.goファイルがどのように生成されるか、という点に焦点を当てています。以前はgodefsdefs.cからdefs.hdefs.goを生成していましたが、この変更により、defs.goがCの定義をインポートし、cgo -cdefsdefs.goからdefs.hを生成する形に変わります。

技術的詳細

このコミットの核心は、GoのランタイムとシステムコールパッケージがOS固有の定義(定数、構造体、シグナルなど)をどのように取得し、Goコードで利用可能にするかというメカニズムの変更です。

変更前 (godefsベース):

  1. 開発者は、OS固有の定数や構造体をC言語の形式で記述した.cファイル(例: src/pkg/runtime/darwin/defs.c)を用意します。
  2. godefsツールがこの.cファイルを解析し、対応するCヘッダーファイル(例: src/pkg/runtime/darwin/386/defs.h)とGoソースファイル(例: src/pkg/runtime/darwin/defs.go)を生成します。
  3. Goのランタイムやsyscallパッケージは、生成されたdefs.hdefs.goを利用してOSと連携します。

この方式では、godefsがC言語のパーサーとして機能し、Cの定義をGoの定義に「変換」していました。しかし、C言語の複雑なプリプロセッサや型システムを完全に再現することは難しく、特にポインタのサイズや構造体のアライメントなど、アーキテクチャやOSによって異なる詳細を正確に扱うのが困難でした。

変更後 (cgoベース):

  1. 開発者は、OS固有の定数や構造体をGo言語のファイル(例: src/pkg/runtime/darwin/defs.go)内に、import "C"ブロックを使ってC言語のヘッダーをインポートする形で記述します。Goの定数や型は、Cの対応する定義を直接参照する形(例: PROT_NONE = C.PROT_NONE)で定義されます。
  2. cgoツールが、このdefs.goファイルをcdefsモードで処理します。cgo -cdefs defs.goコマンドを実行すると、cgodefs.go内でインポートされているCヘッダーを実際にコンパイルし、その結果として得られるCの定数や構造体のオフセット、サイズなどの情報を基に、Goコードから利用するためのCヘッダーファイル(例: src/pkg/runtime/darwin/386/defs.h)を生成します。
  3. Goのランタイムやsyscallパッケージは、生成されたdefs.hと、元のdefs.goファイルを利用してOSと連携します。

この新しいアプローチの主な利点は以下の通りです。

  • 正確性の向上: cgoは実際のCコンパイラ(GCCやClangなど)のフロントエンドを利用してCヘッダーを解析するため、OSの実際の定義と完全に一致するCヘッダーを生成できます。これにより、GoとOS間のインターフェースの正確性が大幅に向上します。
  • メンテナンスの簡素化: Go開発チームは、独自のCパーサーであるgodefsのメンテナンスから解放されます。代わりに、既存の成熟したcgoツールとCコンパイラのインフラストラクチャを利用できます。
  • クロスプラットフォーム対応の改善: cgoは異なるOSやアーキテクチャのCコンパイラと連携できるため、Goのランタイムとsyscallパッケージがより広範なプラットフォームで正確に動作するようになります。
  • Goらしい記述: 定数や構造体の定義がGoファイル内に記述されることで、Go開発者にとってより自然な形でOS固有の定義を扱えるようになります。

この変更は、Goの低レベルなOS連携部分の基盤を強化し、将来的なGo言語の発展と安定性に大きく貢献するものです。特に、シグナルハンドリングやメモリ管理など、OSとの密接な連携が必要な部分において、より正確で信頼性の高い動作が期待できます。

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

このコミットでは、Goのランタイムとシステムコールパッケージにおける、OS固有の定数や構造体定義の生成方法が大きく変更されています。具体的には、以下のファイル群が影響を受けています。

  1. src/pkg/runtime/*/defs.c の削除:

    • src/pkg/runtime/darwin/defs.c
    • src/pkg/runtime/freebsd/defs.c
    • src/pkg/runtime/linux/defs.c
    • src/pkg/runtime/linux/defs1.c
    • src/pkg/runtime/linux/defs2.c
    • src/pkg/runtime/linux/defs_arm.c
    • src/pkg/runtime/openbsd/defs.c
    • src/pkg/runtime/windows/defs.c これらのファイルは、godefsツールへの入力として使用されていたC言語の定義ファイルであり、cgoへの移行に伴い不要となったため削除されました。
  2. src/pkg/runtime/*/defs.go の新規作成/変更:

    • src/pkg/runtime/darwin/defs.go (新規作成)
    • src/pkg/runtime/freebsd/defs.go (新規作成)
    • src/pkg/runtime/linux/defs.go (新規作成)
    • src/pkg/runtime/linux/defs1.go (新規作成)
    • src/pkg/runtime/linux/defs2.go (新規作成)
    • src/pkg/runtime/linux/defs_arm.go (新規作成)
    • src/pkg/runtime/openbsd/defs.go (新規作成)
    • src/pkg/runtime/windows/defs.go (新規作成) これらのファイルは、C言語のヘッダーをimport "C"でインポートし、Goの定数や型をCの定義にマッピングする役割を担います。cgo -cdefsはこのdefs.goファイルを読み込み、対応するCヘッダー(defs.h)を生成します。
  3. src/pkg/runtime/*/*/defs.h の変更:

    • src/pkg/runtime/darwin/386/defs.h
    • src/pkg/runtime/darwin/amd64/defs.h
    • src/pkg/runtime/linux/386/defs.h
    • src/pkg/runtime/linux/amd64/defs.h これらのヘッダーファイルは、godefsによって生成されていたものから、cgo -cdefsによって生成されるものへと変更されました。ファイルの内容自体は、OSの定数や構造体定義をC言語で記述したものですが、生成元のツールが変更されたことを示すコメント(// Created by cgo -cdefs - DO NOT EDIT)が追加されています。また、構造体のアライメントやパディングに関する変更も含まれています。
  4. src/pkg/runtime/*/signal.c の変更:

    • src/pkg/runtime/darwin/386/signal.c
    • src/pkg/runtime/darwin/amd64/signal.c シグナルハンドリングに関連するCコードが変更されています。特に、RegsMcontextなどの型定義が、godefsが生成していたものから、cgoが生成する新しい型定義(例: Regs32, Mcontext32, Regs64, Mcontext64)に合わせるように修正されています。これにより、シグナルハンドラがレジスタ情報やコンテキスト情報を正しく解釈できるようになります。
  5. src/pkg/syscall/types_*.c の削除:

    • src/pkg/syscall/types_darwin.c
    • src/pkg/syscall/types_freebsd.c
    • src/pkg/syscall/types_linux.c
    • src/pkg/syscall/types_openbsd.c これらのファイルもgodefsへの入力として使用されていたため、削除されました。
  6. src/pkg/syscall/types_*.go の新規作成/変更:

    • src/pkg/syscall/types_darwin.go (新規作成)
    • src/pkg/syscall/types_freebsd.go (新規作成)
    • src/pkg/syscall/types_linux.go (新規作成)
    • src/pkg/syscall/types_openbsd.go (新規作成) これらのファイルは、runtimeパッケージのdefs.goと同様に、import "C"を使ってC言語のヘッダーをインポートし、Goの型をCの定義にマッピングします。
  7. src/pkg/syscall/mkall.sh および src/pkg/syscall/mkerrors.sh の変更: これらのシェルスクリプトは、システムコール関連のファイルを生成するためのビルドプロセスの一部です。godefsの呼び出しがcgo -cdefsの呼び出しに置き換えられ、ビルドシステムが新しい生成メカニズムに対応するように更新されています。

これらの変更は、Goのビルドシステムと低レベルなOS連携部分のアーキテクチャを根本的に変更するものであり、Go言語の移植性とメンテナンス性を大幅に向上させるための重要なステップです。

コアとなるコードの解説

ここでは、変更の核心部分であるdefs.cからdefs.goへの移行と、それに伴うdefs.hの生成方法の変化に焦点を当てて解説します。

変更前 (godefsによる生成の例 - src/pkg/runtime/darwin/defs.c):

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
 * Input to godefs.
 *
 *	godefs -f -m64 defs.c >amd64/defs.h
 *	godefs -f -m32 defs.c >386/defs.h
 */

#define __DARWIN_UNIX03 0

#include <mach/mach.h>
#include <mach/message.h>
// ... その他のCヘッダー ...

enum {
	$PROT_NONE = PROT_NONE,
	// ... その他の定数 ...
};

typedef mach_msg_body_t	$MachBody;
// ... その他の型定義 ...

#ifdef __LP64__
// amd64
typedef x86_thread_state64_t	$Regs;
// ...
#else
// 386
typedef x86_thread_state32_t	$Regs;
// ...
#endif

typedef ucontext_t	$Ucontext;

このdefs.cファイルは、Goで利用したいCの定数や型を$プレフィックス付きで定義し、godefsがこれを解析してGoとCのヘッダーを生成していました。例えば、$PROT_NONE = PROT_NONEという記述は、CのPROT_NONEという定数をGoでPROT_NONEとして利用できるようにするための指示でした。

変更後 (cgoによる生成の例 - src/pkg/runtime/darwin/defs.go):

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Input to cgo.

GOARCH=amd64 cgo -cdefs defs.go >amd64/defs.h
GOARCH=386 cgo -cdefs defs.go >386/defs.h
*/

package runtime

/*
#define __DARWIN_UNIX03 0
#include <mach/mach.h>
#include <mach/message.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/mman.h>
*/
import "C"

const (
	PROT_NONE  = C.PROT_NONE
	PROT_READ  = C.PROT_READ
	PROT_WRITE = C.PROT_WRITE
	PROT_EXEC  = C.PROT_EXEC

	// ... その他の定数 ...
)

type MachBody C.mach_msg_body_t
type MachHeader C.mach_msg_header_t
// ... その他の型定義 ...

type Regs64 C.struct_x86_thread_state64
type FloatState64 C.struct_x86_float_state64
type ExceptionState64 C.struct_x86_exception_state64
type Mcontext64 C.struct_mcontext64

type Regs32 C.struct_i386_thread_state
type FloatState32 C.struct_i386_float_state
type ExceptionState32 C.struct_i386_exception_state
type Mcontext32 C.struct_mcontext32

type Ucontext C.struct_ucontext

この新しいdefs.goファイルでは、以下の点が重要です。

  1. import "C" ブロック: C言語のヘッダーファイルが直接インクルードされています。cgoは、このブロック内のCコードを通常のCコンパイラで処理します。
  2. Goの定数定義: constブロック内で、Goの定数がC.プレフィックスを使ってCの定数を直接参照しています(例: PROT_NONE = C.PROT_NONE)。これにより、Goの定数値がCの実際の値と常に同期されます。
  3. Goの型定義: typeキーワードを使って、Cの構造体や型にGoの型をエイリアスしています(例: type MachBody C.mach_msg_body_t)。C.mach_msg_body_tは、cgoがCのヘッダーを解析してGoからアクセス可能にしたCの型です。
  4. アーキテクチャ固有の型: Regs64, Regs32のように、64ビットと32ビットアーキテクチャで異なるレジスタ構造体やコンテキスト構造体が明示的に定義されています。これは、cgoがターゲットアーキテクチャに応じて適切なCの型を解決するためです。

このdefs.goファイルをcgo -cdefsで処理すると、以下のようなCヘッダーファイル(例: src/pkg/runtime/darwin/386/defs.h)が生成されます。

// Created by cgo -cdefs - DO NOT EDIT
// cgo -cdefs defs.go

// MACHINE GENERATED - DO NOT EDIT.

// Constants
enum {
	PROT_NONE	= 0x0,
	PROT_READ	= 0x1,
	// ... その他の定数 ...
};

// Types
typedef struct MachBody MachBody;
typedef struct MachHeader MachHeader;
// ... その他の型宣言 ...

#pragma pack on

struct MachBody {
	uint32	msgh_descriptor_count;
};
// ... その他の構造体定義 ...

struct Regs32 {
	uint32	eax;
	uint32	ebx;
	// ... その他のレジスタ ...
};
// ... その他の構造体定義 ...

#pragma pack off

生成されたdefs.hファイルには、Goのdefs.goで定義されたCの定数や型に対応するC言語の定義が含まれます。重要なのは、このdefs.hcgoによって実際のCコンパイラの知識に基づいて生成されるため、構造体のパディングやアライメント、ビットフィールドの解釈などがOSの実際の動作と一致する点です。

signal.cの変更点:

src/pkg/runtime/darwin/386/signal.cの変更を見ると、runtime·dumpregs関数やruntime·sighandler関数で使われている型が、RegsからRegs32へ、McontextからMcontext32へと変更されていることがわかります。

// 変更前
void
runtime·dumpregs(Regs *r)

// 変更後
void
runtime·dumpregs(Regs32 *r)

これは、cgoによって生成される新しい型定義に合わせるための修正です。これにより、シグナルハンドラが受け取るコンテキスト情報(レジスタの状態など)を、Goランタイムが正確に読み取れるようになります。

この一連の変更により、Goのランタイムとシステムコールパッケージは、OS固有の低レベルな詳細を扱う上で、より正確で信頼性の高い基盤を手に入れました。

関連リンク

参考にした情報源リンク

(注: godefsに関する直接的な公式ドキュメントは現在ほとんど残っていませんが、Goの歴史的な文脈で言及されることがあります。この解説は、Goの進化の過程における重要なマイルストーンとして、その役割とcgoへの移行の意義を説明しています。)