[インデックス 10339] ファイルの概要
このコミットは、Go言語のランタイム(runtime
)およびシステムコール(syscall
)パッケージにおいて、システム定義(defs.h
やtypes_*.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言語の構造体や定数定義を自動生成する役割を担っていました。しかし、このアプローチにはいくつかの課題がありました。
- メンテナンスの複雑さ:
godefs
はC言語のパーサーを内蔵しており、C言語の構文やプリプロセッサの変更に対応する必要がありました。これは、Go言語のコア開発チームにとって追加のメンテナンス負担となっていました。 - 正確性の問題: C言語の複雑な型システムやプリプロセッサディレクティブを完全にエミュレートすることは困難であり、
godefs
が生成する定義が常にOSの実際の定義と完全に一致するとは限りませんでした。特に、異なるOSやアーキテクチャ間での差異を吸収するのが難しい場合がありました。 - 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の間でデータをやり取りするためのバインディングコードを生成します。
このコミットでは、cgo
のcdefs
モードが活用されています。cdefs
モードは、C言語のヘッダーファイルからGo言語の定数や構造体定義を自動生成するために使用されます。これにより、GoプログラムはOSのCヘッダーファイルを直接参照し、その定義をGoの型として利用できるようになります。
5. defs.h
と defs.go
ファイル
Goのランタイムやシステムコールパッケージでは、OS固有の定数や構造体の定義を管理するために、通常、以下のようなファイルが使用されます。
defs.h
: C言語のヘッダーファイルで、OSのシステムコールやデータ構造に関する定数や型定義が含まれます。これらはGoランタイムやsyscall
パッケージのC部分から利用されます。defs.go
: Go言語のファイルで、defs.h
に対応するGo言語の定数や型定義が含まれます。これらはGoランタイムやsyscall
パッケージのGo部分から利用されます。
このコミットの変更は、これらのdefs.h
とdefs.go
ファイルがどのように生成されるか、という点に焦点を当てています。以前はgodefs
がdefs.c
からdefs.h
とdefs.go
を生成していましたが、この変更により、defs.go
がCの定義をインポートし、cgo -cdefs
がdefs.go
からdefs.h
を生成する形に変わります。
技術的詳細
このコミットの核心は、GoのランタイムとシステムコールパッケージがOS固有の定義(定数、構造体、シグナルなど)をどのように取得し、Goコードで利用可能にするかというメカニズムの変更です。
変更前 (godefs
ベース):
- 開発者は、OS固有の定数や構造体をC言語の形式で記述した
.c
ファイル(例:src/pkg/runtime/darwin/defs.c
)を用意します。 godefs
ツールがこの.c
ファイルを解析し、対応するCヘッダーファイル(例:src/pkg/runtime/darwin/386/defs.h
)とGoソースファイル(例:src/pkg/runtime/darwin/defs.go
)を生成します。- Goのランタイムや
syscall
パッケージは、生成されたdefs.h
とdefs.go
を利用してOSと連携します。
この方式では、godefs
がC言語のパーサーとして機能し、Cの定義をGoの定義に「変換」していました。しかし、C言語の複雑なプリプロセッサや型システムを完全に再現することは難しく、特にポインタのサイズや構造体のアライメントなど、アーキテクチャやOSによって異なる詳細を正確に扱うのが困難でした。
変更後 (cgo
ベース):
- 開発者は、OS固有の定数や構造体をGo言語のファイル(例:
src/pkg/runtime/darwin/defs.go
)内に、import "C"
ブロックを使ってC言語のヘッダーをインポートする形で記述します。Goの定数や型は、Cの対応する定義を直接参照する形(例:PROT_NONE = C.PROT_NONE
)で定義されます。 cgo
ツールが、このdefs.go
ファイルをcdefs
モードで処理します。cgo -cdefs defs.go
コマンドを実行すると、cgo
はdefs.go
内でインポートされているCヘッダーを実際にコンパイルし、その結果として得られるCの定数や構造体のオフセット、サイズなどの情報を基に、Goコードから利用するためのCヘッダーファイル(例:src/pkg/runtime/darwin/386/defs.h
)を生成します。- 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固有の定数や構造体定義の生成方法が大きく変更されています。具体的には、以下のファイル群が影響を受けています。
-
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
への移行に伴い不要となったため削除されました。
-
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
)を生成します。
-
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
)が追加されています。また、構造体のアライメントやパディングに関する変更も含まれています。
-
src/pkg/runtime/*/signal.c
の変更:src/pkg/runtime/darwin/386/signal.c
src/pkg/runtime/darwin/amd64/signal.c
シグナルハンドリングに関連するCコードが変更されています。特に、Regs
、Mcontext
などの型定義が、godefs
が生成していたものから、cgo
が生成する新しい型定義(例:Regs32
,Mcontext32
,Regs64
,Mcontext64
)に合わせるように修正されています。これにより、シグナルハンドラがレジスタ情報やコンテキスト情報を正しく解釈できるようになります。
-
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
への入力として使用されていたため、削除されました。
-
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の定義にマッピングします。
-
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
ファイルでは、以下の点が重要です。
import "C"
ブロック: C言語のヘッダーファイルが直接インクルードされています。cgo
は、このブロック内のCコードを通常のCコンパイラで処理します。- Goの定数定義:
const
ブロック内で、Goの定数がC.
プレフィックスを使ってCの定数を直接参照しています(例:PROT_NONE = C.PROT_NONE
)。これにより、Goの定数値がCの実際の値と常に同期されます。 - Goの型定義:
type
キーワードを使って、Cの構造体や型にGoの型をエイリアスしています(例:type MachBody C.mach_msg_body_t
)。C.mach_msg_body_t
は、cgo
がCのヘッダーを解析してGoからアクセス可能にしたCの型です。 - アーキテクチャ固有の型:
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.h
がcgo
によって実際の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固有の低レベルな詳細を扱う上で、より正確で信頼性の高い基盤を手に入れました。
関連リンク
- Go CL (Change List): https://golang.org/cl/5348052
参考にした情報源リンク
- Go Wiki: cgo (cgoの基本的な情報)
- Go Wiki: Go and the operating system (GoとOSの連携に関する一般的な情報)
- Go source code on GitHub (Go言語のソースコードリポジトリ)
- Understanding Go's runtime and syscall packages (Goランタイムとsyscallパッケージの内部構造に関する情報 - 一般的な知識として参照)
- The Go Programming Language Specification - Cgo (cgoの仕様に関する公式ドキュメント)
(注: godefs
に関する直接的な公式ドキュメントは現在ほとんど残っていませんが、Goの歴史的な文脈で言及されることがあります。この解説は、Goの進化の過程における重要なマイルストーンとして、その役割とcgo
への移行の意義を説明しています。)