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

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

このコミットは、Go言語のランタイムにおけるsync/atomicパッケージのARM Linux向けアセンブリコードに対する変更です。具体的には、src/pkg/sync/atomic/asm_linux_arm.sファイルが修正されています。このファイルは、GoプログラムがARMアーキテクチャ上でアトミック操作(複数のCPUコアから同時にアクセスされても、その操作が途中で中断されずに完全に実行されることを保証する操作)を実行する際に使用される低レベルのアセンブリコードを含んでいます。

コミット

commit 66c58cea67cb48d1cce2f96036557dafbfbf8c19
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Aug 13 21:15:47 2013 +0400

    sync/atomic: trigger paging fault early on linux/arm
    so that we don't need to traceback through __kuser_cmpxchg
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12869043

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

https://github.com/golang/go/commit/66c58cea67cb48d1cce2f96036557dafbfbf8c19

元コミット内容

diff --git a/src/pkg/sync/atomic/asm_linux_arm.s b/src/pkg/sync/atomic/asm_linux_arm.s
index 4b6b69c505..3d1edfe0bf 100644
--- a/src/pkg/sync/atomic/asm_linux_arm.s
+++ b/src/pkg/sync/atomic/asm_linux_arm.s
@@ -32,6 +32,9 @@ TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0
 // Implement using kernel cas for portability.
 TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0-13
 	MOVW	addr+0(FP), R2
+	// trigger potential paging fault here,
+	// because we don't know how to traceback through __kuser_cmpxchg
+	MOVW	(R2), R0
 	MOVW	old+4(FP), R0
 casagain:
 	MOVW	new+8(FP), R1
@@ -102,6 +105,9 @@ TEXT cas64<>(SB),NOSPLIT,$0
 TEXT kernelCAS64<>(SB),NOSPLIT,$0-21
 	// int (*__kuser_cmpxchg64_t)(const int64_t *oldval, const int64_t *newval, volatile int64_t *ptr);
 	MOVW	addr+0(FP), R2 // ptr
+	// trigger potential paging fault here,
+	// because we don't know how to traceback through __kuser_cmpxchg64
+	MOVW	(R2), R0
 	// make unaligned atomic access panic
 	AND.S	$7, R2, R1
 	BEQ 	2(PC)

変更の背景

このコミットの背景には、GoランタイムがARM Linux上でアトミック操作を実行する際に、無効なメモリアドレスにアクセスした場合のデバッグの困難さがありました。

Go言語のsync/atomicパッケージは、共有メモリ上の変数を安全に操作するためのアトミックなプリミティブを提供します。ARM Linux環境では、これらのアトミック操作の一部は、カーネルが提供するヘルパー関数である__kuser_cmpxchg(Compare-and-Exchange)を利用していました。これは、特に古いARM CPU(ARMv6以前)でハードウェアレベルのアトミック命令が不足していた場合に、ユーザー空間からアトミック操作を実行するための効率的な手段でした。

しかし、問題は、__kuser_cmpxchgを介して無効なメモリアドレスにアクセスしようとした際に発生するページングフォルト(ページフォールト)の処理にありました。ページングフォルトは、プログラムが現在メモリにマッピングされていない、またはアクセス権限のないメモリページにアクセスしようとしたときに発生する例外です。通常、このようなフォルトが発生すると、オペレーティングシステムはエラーを検出し、プログラムを終了させるか、デバッガに制御を渡します。

当時のGoランタイムの実装では、__kuser_cmpxchg内部でページングフォルトが発生した場合、そのフォルトの発生源を正確に特定し、デバッグすることが困難でした。スタックトレースが__kuser_cmpxchgの内部を指し示してしまい、Goコードのどの部分が無効なメモリアドレスを渡したのかを追跡するのが難しかったのです。

このコミットは、このデバッグの困難さを解消するために導入されました。__kuser_cmpxchgを呼び出す前に、意図的にメモリアドレスをデリファレンス(参照外し)することで、無効なアドレスであれば__kuser_cmpxchgに到達する前にページングフォルトを発生させ、より明確なスタックトレースを得られるようにすることが目的です。これにより、問題の根本原因を特定しやすくなります。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  1. アトミック操作 (Atomic Operations): アトミック操作とは、複数のCPUコアやスレッドから同時にアクセスされた場合でも、その操作全体が不可分(中断されない)であることを保証する操作です。これにより、共有データに対する競合状態(データレース)を防ぎ、プログラムの正確性を保証します。Go言語では、sync/atomicパッケージがこれらの操作を提供します。例えば、CompareAndSwap (CAS) は、メモリ位置の現在の値が期待される値と一致する場合にのみ、新しい値でそのメモリ位置を更新するアトミック操作です。

  2. ARMアーキテクチャ: ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。RISC(Reduced Instruction Set Computer)の原則に基づいて設計されており、電力効率と性能のバランスが特徴です。ARMプロセッサは、特定の命令セットとレジスタセットを持っています。

  3. __kuser_cmpxchg: __kuser_cmpxchgは、LinuxカーネルがARMアーキテクチャ向けに提供するユーザーヘルパー関数です。これは、ユーザー空間のアプリケーションがアトミックな比較交換(CAS)操作を効率的に実行できるようにするためのものです。特に、ARMv6以前のCPUでは、ハードウェアレベルで強力なアトミック命令が不足していたため、カーネルが提供するこのヘルパー関数が利用されました。この関数は、システムコールを介さずに直接ユーザー空間から呼び出せるように、カーネルメモリの固定された既知のアドレスに配置されています。これにより、システムコールによるオーバーヘッドを回避し、効率的なアトミック操作を可能にします。

  4. ページングフォルト (Paging Fault): ページングフォルトは、仮想記憶システムにおいて発生する一種の例外(割り込み)です。プログラムがアクセスしようとした仮想メモリアドレスに対応する物理メモリページが、現在メインメモリに存在しない(ディスクにスワップアウトされている)、またはそのアドレスへのアクセス権限がない場合に発生します。オペレーティングシステムは、このフォルトを処理し、必要なページをメモリにロードしたり、アクセス違反としてプログラムを終了させたりします。

  5. アセンブリ言語 (Assembly Language): アセンブリ言語は、CPUが直接実行できる機械語と1対1に対応する低レベルのプログラミング言語です。CPUのレジスタやメモリを直接操作するため、非常に高速なコードを記述できますが、特定のCPUアーキテクチャに依存し、可読性が低いという特徴があります。Goランタイムのパフォーマンスが重要な部分(特にアトミック操作やスケジューラなど)では、アセンブリ言語が使用されることがあります。

  6. MOVW 命令 (ARM Assembly): ARMアセンブリにおけるMOVW命令は、16ビットの即値(定数)をレジスタの下位16ビットにロードし、上位16ビットをクリアするために使用されます。通常、MOVT命令と組み合わせて32ビットの即値をレジスタにロードするために使われます。 しかし、このコミットで追加されたMOVW (R2), R0という形式は、一般的なMOVW命令の構文とは異なります。MOVWは即値をロードする命令であり、メモリからのロードを行う命令ではありません。メモリからのロードを行う命令はLDR(Load Register)です。 この文脈でのMOVW (R2), R0は、Goのアセンブラ(Plan 9アセンブラ)の特殊な構文であり、実際にはLDR R0, [R2](R2が指すメモリアドレスから32ビットの値をR0レジスタにロードする)に相当する操作を意図しています。これは、Goのアセンブラが一般的なARMアセンブラとは異なる独自のニーモニックと構文を使用しているためです。

技術的詳細

このコミットの技術的な核心は、アトミック操作の前に意図的にメモリアクセスを挿入することで、ページングフォルトの発生タイミングを制御し、デバッグを容易にすることにあります。

Goのsync/atomicパッケージは、CompareAndSwapUint32kernelCAS64といった関数でアトミック操作を実装しています。これらの関数は、引数として操作対象のメモリアドレス(addr)を受け取ります。ARM Linux環境では、これらの操作は最終的にカーネルの__kuser_cmpxchgまたは__kuser_cmpxchg64ヘルパー関数を呼び出して実行されます。

変更前は、addrが指すメモリアドレスが無効であった場合、その無効なアドレスへのアクセスは__kuser_cmpxchgの内部で発生していました。これにより、ページングフォルトが発生した際に、スタックトレースが__kuser_cmpxchgの内部を指し示し、Goコードのどこで無効なアドレスが渡されたのかを特定するのが困難でした。

このコミットでは、__kuser_cmpxchgを呼び出す直前に、以下の命令が追加されました。

MOVW (R2), R0

ここで、R2レジスタにはアトミック操作の対象となるメモリアドレス(addr)が格納されています。前述の通り、GoのアセンブラにおけるMOVW (R2), R0は、一般的なARMアセンブラのLDR R0, [R2]に相当します。つまり、R2が指すメモリアドレスから値を読み込み、R0レジスタに格納する操作です。

この命令の追加により、以下の効果が期待されます。

  1. 早期のページングフォルトトリガー: __kuser_cmpxchgを呼び出す前に、addrが指すメモリアドレスへの読み込み操作が明示的に行われます。もしこのアドレスが無効であれば、このMOVW (R2), R0命令の実行時にページングフォルトが即座に発生します。
  2. 明確なスタックトレース: ページングフォルトがGoのアセンブリコード内で直接発生するため、デバッガでスタックトレースを確認した際に、__kuser_cmpxchgの内部ではなく、Goランタイムのアトミック操作のアセンブリコードの該当箇所が指し示されます。これにより、無効なメモリアドレスがどこから渡されたのか、Goコードのどの部分が問題を引き起こしているのかをより容易に特定できるようになります。
  3. デバッグ効率の向上: 開発者は、より具体的なエラー情報に基づいて、問題のあるGoコードを迅速に特定し、修正することができます。

この変更は、アトミック操作のロジック自体を変更するものではなく、あくまでデバッグの容易性を向上させるためのものです。パフォーマンスへの影響は最小限であると予想されますが、無効なメモリアドレスへのアクセスが早期に検出されることで、プログラムの異常終了がより予測可能な形で行われるようになります。

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

変更は、src/pkg/sync/atomic/asm_linux_arm.sファイル内の2つの関数、·CompareAndSwapUint32kernelCAS64に適用されています。

--- a/src/pkg/sync/atomic/asm_linux_arm.s
+++ b/src/pkg/sync/atomic/asm_linux_arm.s
@@ -32,6 +32,9 @@ TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0
 // Implement using kernel cas for portability.
 TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0-13
 	MOVW	addr+0(FP), R2
+	// trigger potential paging fault here,
+	// because we don't know how to traceback through __kuser_cmpxchg
+	MOVW	(R2), R0
 	MOVW	old+4(FP), R0
 casagain:
 	MOVW	new+8(FP), R1
@@ -102,6 +105,9 @@ TEXT cas64<>(SB),NOSPLIT,$0
 TEXT kernelCAS64<>(SB),NOSPLIT,$0-21
 	// int (*__kuser_cmpxchg64_t)(const int64_t *oldval, const int64_t *newval, volatile int64_t *ptr);
 	MOVW	addr+0(FP), R2 // ptr
+	// trigger potential paging fault here,
+	// because we don't know how to traceback through __kuser_cmpxchg64
+	MOVW	(R2), R0
 	// make unaligned atomic access panic
 	AND.S	$7, R2, R1
 	BEQ 	2(PC)

コアとなるコードの解説

追加されたコードは以下の通りです。

	// trigger potential paging fault here,
	// because we don't know how to traceback through __kuser_cmpxchg
	MOVW	(R2), R0

この3行は、·CompareAndSwapUint32kernelCAS64の両方の関数に追加されています。

  • MOVW addr+0(FP), R2: これは、関数フレームポインタ(FP)からのオフセットでaddr引数の値(メモリアドレス)をR2レジスタにロードする命令です。R2は、アトミック操作の対象となるメモリアドレスを保持します。
  • // trigger potential paging fault here, ...: これはコメントであり、この後の命令がページングフォルトを早期にトリガーすることを意図していることを説明しています。また、__kuser_cmpxchgを介したトレースバックが困難であるという背景も示しています。
  • MOVW (R2), R0: この命令が今回の変更の核心です。Goのアセンブラの構文では、MOVW (R2), R0は、R2レジスタが指すメモリアドレスから32ビットの値を読み込み、それをR0レジスタに格納する操作を意味します。これは一般的なARMアセンブラのLDR R0, [R2]に相当します。

この命令が実行されることで、R2に格納されているアドレスが有効なメモリ領域を指していない場合、__kuser_cmpxchgが呼び出される前にページングフォルトが発生します。これにより、デバッガはGoランタイムのアセンブリコード内のこの正確な場所でフォルトを検出し、より有用なスタックトレースを提供できるようになります。

kernelCAS64関数にも同様の変更が加えられていますが、これは64ビットのアトミック操作(__kuser_cmpxchg64を使用)に対応するためのものです。基本的な考え方はCompareAndSwapUint32と同じです。

関連リンク

参考にした情報源リンク