[インデックス 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
に到達する前にページングフォルトを発生させ、より明確なスタックトレースを得られるようにすることが目的です。これにより、問題の根本原因を特定しやすくなります。
前提知識の解説
このコミットを理解するためには、以下の概念について理解しておく必要があります。
-
アトミック操作 (Atomic Operations): アトミック操作とは、複数のCPUコアやスレッドから同時にアクセスされた場合でも、その操作全体が不可分(中断されない)であることを保証する操作です。これにより、共有データに対する競合状態(データレース)を防ぎ、プログラムの正確性を保証します。Go言語では、
sync/atomic
パッケージがこれらの操作を提供します。例えば、CompareAndSwap
(CAS) は、メモリ位置の現在の値が期待される値と一致する場合にのみ、新しい値でそのメモリ位置を更新するアトミック操作です。 -
ARMアーキテクチャ: ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているCPUアーキテクチャです。RISC(Reduced Instruction Set Computer)の原則に基づいて設計されており、電力効率と性能のバランスが特徴です。ARMプロセッサは、特定の命令セットとレジスタセットを持っています。
-
__kuser_cmpxchg
:__kuser_cmpxchg
は、LinuxカーネルがARMアーキテクチャ向けに提供するユーザーヘルパー関数です。これは、ユーザー空間のアプリケーションがアトミックな比較交換(CAS)操作を効率的に実行できるようにするためのものです。特に、ARMv6以前のCPUでは、ハードウェアレベルで強力なアトミック命令が不足していたため、カーネルが提供するこのヘルパー関数が利用されました。この関数は、システムコールを介さずに直接ユーザー空間から呼び出せるように、カーネルメモリの固定された既知のアドレスに配置されています。これにより、システムコールによるオーバーヘッドを回避し、効率的なアトミック操作を可能にします。 -
ページングフォルト (Paging Fault): ページングフォルトは、仮想記憶システムにおいて発生する一種の例外(割り込み)です。プログラムがアクセスしようとした仮想メモリアドレスに対応する物理メモリページが、現在メインメモリに存在しない(ディスクにスワップアウトされている)、またはそのアドレスへのアクセス権限がない場合に発生します。オペレーティングシステムは、このフォルトを処理し、必要なページをメモリにロードしたり、アクセス違反としてプログラムを終了させたりします。
-
アセンブリ言語 (Assembly Language): アセンブリ言語は、CPUが直接実行できる機械語と1対1に対応する低レベルのプログラミング言語です。CPUのレジスタやメモリを直接操作するため、非常に高速なコードを記述できますが、特定のCPUアーキテクチャに依存し、可読性が低いという特徴があります。Goランタイムのパフォーマンスが重要な部分(特にアトミック操作やスケジューラなど)では、アセンブリ言語が使用されることがあります。
-
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
パッケージは、CompareAndSwapUint32
やkernelCAS64
といった関数でアトミック操作を実装しています。これらの関数は、引数として操作対象のメモリアドレス(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
レジスタに格納する操作です。
この命令の追加により、以下の効果が期待されます。
- 早期のページングフォルトトリガー:
__kuser_cmpxchg
を呼び出す前に、addr
が指すメモリアドレスへの読み込み操作が明示的に行われます。もしこのアドレスが無効であれば、このMOVW (R2), R0
命令の実行時にページングフォルトが即座に発生します。 - 明確なスタックトレース: ページングフォルトがGoのアセンブリコード内で直接発生するため、デバッガでスタックトレースを確認した際に、
__kuser_cmpxchg
の内部ではなく、Goランタイムのアトミック操作のアセンブリコードの該当箇所が指し示されます。これにより、無効なメモリアドレスがどこから渡されたのか、Goコードのどの部分が問題を引き起こしているのかをより容易に特定できるようになります。 - デバッグ効率の向上: 開発者は、より具体的なエラー情報に基づいて、問題のあるGoコードを迅速に特定し、修正することができます。
この変更は、アトミック操作のロジック自体を変更するものではなく、あくまでデバッグの容易性を向上させるためのものです。パフォーマンスへの影響は最小限であると予想されますが、無効なメモリアドレスへのアクセスが早期に検出されることで、プログラムの異常終了がより予測可能な形で行われるようになります。
コアとなるコードの変更箇所
変更は、src/pkg/sync/atomic/asm_linux_arm.s
ファイル内の2つの関数、·CompareAndSwapUint32
とkernelCAS64
に適用されています。
--- 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行は、·CompareAndSwapUint32
とkernelCAS64
の両方の関数に追加されています。
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
と同じです。
関連リンク
- Go言語
sync/atomic
パッケージのドキュメント: https://pkg.go.dev/sync/atomic - ARMアーキテクチャリファレンスマニュアル (ARMv7-A/R): https://developer.arm.com/documentation/ddi0406/latest/
- Linuxカーネルの
__kuser_cmpxchg
に関する情報 (例: Linuxカーネルソースコードや関連ドキュメント)
参考にした情報源リンク
- Go sync/atomic package ARM __kuser_cmpxchg paging fault: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHNCaOrSssm63PWrTjfLBBmdwPDFYv8N0v2g1H-L3ro9wv8uFPdCOcfctnCe9eJomsurwcVX9Ayh9Y--PF5AdWoOzE8uAmfxy2B7jmqFTHyINShnmfUvcKxg9ZcrHtxGgejBg==
- ARM __kuser_cmpxchg explanation: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEX0cZ30wFj88CukHuc1Mf2ImyH6Z-cXAES_3fmg6pHugHfPRAmV9Vdxzn-MTOfbGpp0wG5C2D_Gj4ZdEhJ2dlYiJBK21LFkeYj41rXofquCIIlYyaa_j17ZvaqNqDAxvsO8GWrpaXpLbtIUex3-3weMSac
- Go runtime atomic operations ARM Linux: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHmAOIFBqhZMh-_otO2nE60oHB29lDKuu9B5LM7EKAqsfXXEbCe84ZuC4rwFO4BxmEhe0ywhZzmk9JSKph5UerS1LnoGMnmSs08-9TNIZcCVPNKTdDlzrPH
- ARM assembly MOVW (R2), R0: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGeR2-LmkXfjQwFNkAMKIWoKB_xU7mWCl5q4ugFOpGvmUyWnM4e6X07cteO6fuH14i2p2t8jonrj4HxbcqPBAENiMfhry1d6dQ7FQFun9sGMVsFWC8vpcOBksrkBhGgXF49ceSAak93nvZC_keAwW8CqYMYJp4eubalsg==