予約してメモリを書き換える

マルチコアの制御を行うプログラムでcmpxchgという関数があった。

この関数でプログラムが停止して、いろいろ面倒なことがあった。

アセンブラで書かれている関数でよくわからないなと思って調べたら、マルチコアのシステム特有の処理らしい。


この関数はメモリ上の値がある値であることを確認したら書き換えるというものである。

Compare and Exchange の略で cmpxchg という名前だったらしい。

で、ARMのA32命令で書くとこんな感じの関数である。

# 引数: r1=ptr, r2=old, r3=new
# 返り値: r0
1:  ldrex   r0, [r1]
    mov     r4, #0
    teq     r0, r2
    strexeq r4, r3, [r1]
    teq     r4, #0
    bne     1b

ldrex命令を実行してアドレスr1の値を取得してr0レジスタに格納する。

ただのロード命令と違うのは、書換の予約をするということである。

r2レジスタには当該アドレスで想定されている値が格納されている。

ldrex命令で取得した値と不一致ならば、書換をせずに終わり。

一致していれば、r3レジスタに格納された値への書換を試みる。

それがstrex命令(eqを付けてr0=r2の場合のみ実行としている)である。

strex命令はアドレスr1の値がldrexでの予約後に他のコアが触っていないか見て、

触っていなければr3への書換を実行してr4に0を格納する。

もし触っていれば書き換えずにr4に1を格納し、その場合はldrexからやり直す。

ちなみに bne命令の分岐先1bというのは上にあるラベル1という意味らしい。

逆に下にあるラベル2は2fと書ける。そういうアセンブラの記法があると。


この方式を Load-Link/Store-Conditional(LL/SC) と呼ぶそうである。

Load-Linkが読み出して予約するというのでldrex命令に相当する。

Store-Conditionalが予約後に書換がない場合に書き込むというのでstrex命令に相当する。

そもそもこのような方法が必要になるのはマルチコアだからである。

シングルコアであれば割り込み禁止にしておけば、他のプログラムに書き換えられることはない。

でも、マルチコアの場合、他のコアが書き換える可能性がある。

打開策はいくつかあるが、LL/SCは予約先への他のコアのアクセスを監視することで解決する方法である。

もしも予約先に他のコアがアクセスしてたら書き換えずにやり直すと。


このcmpxchg関数の使用例として、こんなものがある。

if(cmpxchg(&lottery,0,1)==0){
   something(); }

lotteryの初期値が0であるのが前提だが、

最初に実行したコアが0→1の書換をしてsomething()に書いた処理を実行し、

それ以降に実行したコアはすでに1なので何もしないと。

めったに競合しないとは思うが、もし競合すればcmpxchg関数の中でやり直すことになる。


この方式は予約したアドレスへの他のコアのアクセス有無がわかればよいが、

この仕組みが働く領域と働かない領域があるようで、

その都合でcmpxchg関数でプログラムが停止する問題が起きていた。

他のコアのアクセスを監視するというところに何らかの制約があったよう。

シンプルそうに見えるのだが、意外に難しい仕組みである。