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

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

このコミットは、Go言語のランタイム(src/pkg/runtime)のビルドプロセスにおける問題を修正するものです。具体的には、Makefile.autoという自動生成されるMakefileの設定を調整し、特定のビルドターゲットが並行して実行されないようにすることで、ビルドの失敗を防いでいます。

コミット

commit 72bdd8683506807a16b20f5e1be8740e2c258a73
Author: Russ Cox <rsc@golang.org>
Date:   Fri Dec 16 18:31:09 2011 -0500

    runtime: fix build on gri's machine
    
    Why it was not failing anywhere else I don't know,
    but the Makefile was definitely wrong.  The rules
    must not run in parallel.
    
    TBR=r
    CC=golang-dev
    https://golang.org/cl/5489069

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

https://github.com/golang/go/commit/72bdd8683506807a16b20f5e1be8740e2c258a73

元コミット内容

このコミットは、src/pkg/runtime/Makefile.auto ファイルに対して以下の変更を加えています。

  1. HFILES 変数の削除:

    • arch_GOARCH.h
    • os_GOOS.h
    • signals_GOOS.h
    • defs_GOOS_GOARCH.h
    • zasm_GOOS_GOARCH.h これらのヘッダーファイルをリストアップしていた HFILES 変数が削除されました。
  2. zruntime_defs_%.go および zasm_%.h ターゲットからの HFILES 依存関係の削除:

    • zruntime_defs_%.go ターゲットの依存関係から $(HFILES) が削除されました。
    • zasm_%.h ターゲットの依存関係から $(HFILES) が削除されました。
  3. 複数の cp コマンドの削除:

    • arch_GOARCH.h: arch_$(GOARCH).h
    • defs_GOOS_GOARCH.h: defs_$(GOOS)_$(GOARCH).h
    • os_GOOS.h: os_$(GOOS).h
    • signals_GOOS.h: signals_$(GOOS).h
    • zasm_GOOS_GOARCH.h: zasm_$(GOOS)_$(GOARCH).h これらのルールは、特定のアーキテクチャやOSに依存するヘッダーファイルを、より汎用的な名前でコピーするものでしたが、これらが削除されました。
  4. .NOTPARALLEL: ディレクティブの追加:

    • Makefileの最後に .NOTPARALLEL: という特殊なディレクティブが追加されました。
    • これには「mkgodefs.shmkasmh.sh が同じファイルを書き込むため、このファイルはターゲットを並行して実行してはならない」というコメントが付随しています。

変更の背景

コミットメッセージによると、この変更は「griの環境でのビルドの修正」を目的としています。他の環境では問題が発生していなかったものの、Makefileの設定が明らかに間違っており、ルールが並行して実行されるべきではないという認識がありました。

具体的な問題は、mkgodefs.shmkasmh.sh というスクリプトが、ビルドプロセス中に同じ一時ファイルや共有リソースに書き込みを行うことで競合が発生し、ビルドが不安定になったり失敗したりすることでした。GNU Makeはデフォルトで並行ビルドをサポートしており、複数のターゲットを同時に処理しようとします。この並行処理が、共有リソースへの非同期アクセスを引き起こし、データ破損や予期せぬビルドエラーにつながっていたと考えられます。

この問題は、特定の環境(griの環境)でのみ顕在化しましたが、これはビルド環境の特性(CPUコア数、ディスクI/O速度、Makeのバージョンなど)によって並行処理のタイミングが異なり、競合状態が再現しやすいかどうかが変わるためです。根本的な原因はMakefileの設計にあったため、他の環境でも潜在的な問題として存在していました。

前提知識の解説

  • Go言語のランタイム (Runtime): Go言語のプログラムが実行される際に、メモリ管理(ガベージコレクション)、スケジューリング(ゴルーチン)、システムコール、ネットワークI/Oなどを担当する低レベルな部分です。C言語とアセンブリ言語で書かれており、OSやアーキテクチャに依存する部分が多く含まれます。
  • Makefile: make ユーティリティが使用するビルド自動化スクリプトです。ソースコードのコンパイル、リンク、テストなどの手順を定義し、依存関係に基づいて必要な処理のみを実行します。
  • GNU Make: make ユーティリティの最も一般的な実装の一つです。多くのUnix系システムで標準的に使用されています。
  • 並行ビルド (Parallel Builds): make -jN のように -j オプションを使用することで、複数のジョブ(ターゲット)を並行して実行し、ビルド時間を短縮する機能です。しかし、ターゲット間に暗黙の依存関係や共有リソースへのアクセスがある場合、競合状態(Race Condition)が発生し、ビルドが失敗することがあります。
  • .NOTPARALLEL:: GNU Makeの特殊なターゲット名です。このターゲットがMakefile内に存在する場合、そのMakefile内のすべてのルールは並行して実行されなくなります。これは、特定のファイルやリソースへの排他的アクセスが必要な場合に、競合状態を防ぐためのメカニズムです。
  • mkgodefs.sh: Goランタイムのビルドプロセスで使用されるシェルスクリプトの一つです。C言語で定義された構造体や定数をGo言語の定義に変換するために使用されます。これにより、GoコードからCのランタイム構造にアクセスできるようになります。
  • mkasmh.sh: Goランタイムのビルドプロセスで使用される別のシェルスクリプトです。アセンブリ言語のコードからC言語の構造体フィールドのオフセットなどを参照できるように、ヘッダーファイルを生成します。アセンブリコードがCのデータ構造に正しくアクセスするために必要です。
  • TBR=rCC=golang-dev: Goプロジェクトでよく見られるコードレビューの慣習です。
    • TBR=r: "To Be Reviewed by r" の略で、r (Russ Cox) がこの変更のレビュー担当者であることを示します。
    • CC=golang-dev: "Carbon Copy to golang-dev" の略で、golang-dev メーリングリストにこの変更が通知されることを示します。
  • https://golang.org/cl/5489069: これはGerrit Change-IDです。Goプロジェクトでは、コードレビューシステムとしてGerritを使用しており、各変更セット(Change List, CL)には一意のIDが割り当てられます。このURLは、Gerrit上でこのコミットに対応する変更セットのページを指します。

技術的詳細

このコミットの核心は、Goランタイムのビルドにおける並行処理の安全性を確保することにあります。

Goランタイムは、C言語とアセンブリ言語で書かれた低レベルなコードを含んでおり、これらをGo言語のコードと連携させるために、いくつかの自動生成ステップが必要です。mkgodefs.shmkasmh.sh はその主要なスクリプトです。

  • mkgodefs.sh の役割: C言語のソースファイル(proc.c, iface.c, hashmap.c, chan.c など)を解析し、Go言語の構造体定義(zruntime_defs_GOOS_GOARCH.go のようなファイル)を生成します。これにより、Goのコードがランタイムの内部構造を型安全に扱えるようになります。
  • mkasmh.sh の役割: C言語のソースファイル(主に proc.c)を解析し、アセンブリ言語から参照するためのオフセット定義を含むヘッダーファイル(zasm_GOOS_GOARCH.h のようなファイル)を生成します。アセンブリコードは、これらのオフセットを使ってCの構造体の特定のフィールドにアクセスします。

問題は、これらのスクリプトが実行される際に、一時ファイルや共通の出力ディレクトリに書き込みを行う可能性があることです。もし make がこれらのターゲットを並行して実行しようとすると、両方のスクリプトが同時に同じファイルに書き込もうとしたり、互いの生成物を上書きしたりする可能性があります。これが競合状態であり、ビルドの失敗や不正なバイナリの生成につながります。

以前のMakefileでは、HFILES 変数を使って複数のヘッダーファイルをリストアップし、それらを zruntime_defs_%.gozasm_%.h の依存関係に含めていました。また、cp コマンドを使ってアーキテクチャ/OS固有のヘッダーファイルを汎用的な名前にコピーしていました。これらのコピー操作自体が並行処理の競合を引き起こす可能性があったり、あるいは mkgodefs.shmkasmh.sh がこれらのコピーされたファイルに依存していたりする中で、並行実行が問題を引き起こしていたのかもしれません。

このコミットでは、HFILES 変数とその関連する cp ルールを削除し、代わりに mkgodefs.shmkasmh.sh の実行が並行して行われないように .NOTPARALLEL: ディレクティブを追加しました。これにより、make はこれらのスクリプトが生成するファイルに対する競合を回避し、ビルドの安定性を確保します。HFILES の削除は、おそらくこれらのファイルが直接的な競合の原因ではなかったか、あるいは並行処理を禁止することで間接的に問題が解決されたため、不要になったと推測されます。最も重要な変更は、明示的に並行処理を禁止した点です。

.NOTPARALLEL: はGNU Makeの機能であり、Makefile全体に影響を与えます。このディレクティブが存在すると、make はそのMakefile内で定義されているすべてのターゲットを逐次的に実行します。これにより、ビルド時間は若干増加する可能性がありますが、競合によるビルド失敗のリスクは大幅に低減されます。Goランタイムのような低レベルでクリティカルなコンポーネントのビルドにおいては、安定性が速度よりも優先されるべきであるという判断が伺えます。

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

変更はすべて src/pkg/runtime/Makefile.auto ファイル内で行われています。

--- a/src/pkg/runtime/Makefile.auto
+++ b/src/pkg/runtime/Makefile.auto
@@ -50,13 +50,6 @@ AUTO=\
  	zsyscall_windows_amd64.c\
  	zversion.go\
  
-HFILES=\
-\tarch_GOARCH.h\\\
-\tos_GOOS.h\\\
-\tsignals_GOOS.h\\\
-\tdefs_GOOS_GOARCH.h\\\
-\tzasm_GOOS_GOARCH.h\\\
-\
 all: auto
 auto: $(AUTO)
  
@@ -90,29 +83,19 @@ zgoarch_%.go:\
  	(echo '// AUTO-GENERATED; run make -f Makefile.auto'; echo; echo 'package runtime'; echo; echo 'const theGoarch = "$*"') >$@
  
  # definitions of runtime structs, translated from C to Go
-zruntime_defs_%.go: proc.c iface.c hashmap.c chan.c $(HFILES) mkgodefs.sh
+zruntime_defs_%.go: proc.c iface.c hashmap.c chan.c mkgodefs.sh
  	./mkgodefs.sh $* proc.c iface.c hashmap.c chan.c >$@.tmp
  	mv -f $@.tmp $@
  
  # struct field offsets #defined for assembly
-zasm_%.h: mkasmh.sh proc.c $(HFILES)
+zasm_%.h: mkasmh.sh proc.c
  	./mkasmh.sh $* >$@.tmp
  	mv -f $@.tmp $@
  
  clean:
  	rm -f goc2c mkversion $(AUTO)
  
-arch_GOARCH.h: arch_$(GOARCH).h
-\tcp $^ $@
-\
-defs_GOOS_GOARCH.h: defs_$(GOOS)_$(GOARCH).h
-\tcp $^ $@
-\
-os_GOOS.h: os_$(GOOS).h
-\tcp $^ $@
-\
-signals_GOOS.h: signals_$(GOOS).h
-\tcp $^ $@
-\
-zasm_GOOS_GOARCH.h: zasm_$(GOOS)_$(GOARCH).h
-\tcp $^ $@
+# This file must not run targets in parallel:
+# mkgodefs.sh and mkasmh.sh scribble on the same files.
+# This is a magic name known to GNU make.
+.NOTPARALLEL:

コアとなるコードの解説

  1. HFILES 変数と関連ルールの削除:

    • 元のMakefileでは、HFILES という変数が定義され、複数のヘッダーファイル(arch_GOARCH.h など)をリストアップしていました。
    • これらのヘッダーファイルは、zruntime_defs_%.gozasm_%.h といったターゲットの依存関係として指定されていました。
    • また、これらのヘッダーファイルを汎用的な名前にコピーする複数の cp ルールも存在しました。
    • これらの削除は、おそらくこれらのヘッダーファイルのコピーや依存関係の指定が、直接的または間接的に並行ビルド時の競合を引き起こしていたか、あるいは並行処理を禁止することでこれらのステップが不要になったためと考えられます。特に、mkgodefs.shmkasmh.sh がこれらのコピーされたファイルに依存していた場合、コピーが完了する前にスクリプトが実行されると問題が発生する可能性があります。
  2. .NOTPARALLEL: ディレクティブの追加:

    • このコミットの最も重要な変更点です。
    • Makefile.auto の末尾に .NOTPARALLEL: という行が追加されました。
    • このディレクティブはGNU Makeに、このMakefile内のすべてのターゲットを並行して実行しないように指示します。つまり、すべてのビルドステップが逐次的に実行されるようになります。
    • 付随するコメント「# This file must not run targets in parallel: # mkgodefs.sh and mkasmh.sh scribble on the same files. # This is a magic name known to GNU make.」が、この変更の理由を明確に説明しています。mkgodefs.shmkasmh.sh が同じファイル(おそらく一時ファイルや共通の出力ファイル)に書き込むため、並行実行すると競合が発生し、ビルドが壊れる可能性があったということです。

この変更により、Goランタイムのビルドプロセスにおける競合状態が解消され、特定の環境でのビルド失敗が修正されました。ビルドの安定性を確保するために、並行処理のメリット(ビルド時間の短縮)を犠牲にした形ですが、ランタイムのような基盤コンポーネントにおいては安定性が最優先されます。

関連リンク

参考にした情報源リンク

  • Go言語のソースコードとコミット履歴
  • GNU Makeの公式ドキュメント
  • Go言語のビルドシステムに関する一般的な知識
  • Gerrit Code Reviewの仕組みに関する知識
  • 競合状態(Race Condition)に関する一般的なプログラミング知識