spinlock.c void acquire(struct spinlock *lk)

トップページ
jupiteroak.hatenablog.com


spinlock.c
https://github.com/mit-pdos/xv6-public/blob/master/spinlock.c#L24

void acquire(struct spinlock *lk)
{
  pushcli(); // disable interrupts to avoid deadlock.
  if(holding(lk))
    panic("acquire");

  // The xchg is atomic.
  while(xchg(&lk->locked, 1) != 0)
    ;

  // Tell the C compiler and the processor to not move loads or stores
  // past this point, to ensure that the critical section's memory
  // references happen after the lock is acquired.
  __sync_synchronize();

  // Record info about lock acquisition for debugging.
  lk->cpu = mycpu();
  getcallerpcs(&lk, lk->pcs);
}

acquire関数は、排他制御の対象となる共有資源に関連しているロックを取得します(ロックが取得できるまでループウェイトします)。

引数 struct spinlock *lk
取得対象となるロックのアドレスです。


処理の内容

ハードウェア割り込みを無効化する

pushcli();

デッドロックを避けるために、pushcliを呼び出してハードウェア割り込みを無効化します。

ロックを既に取得していないことを確認する

if(holding(lk))
  panic("acquire");

holding関数を呼び出して、現在この処理を実行しているプロセッサ(スレッド)が引数で渡されたロックlkを保持しているかどうかを確認します。
ロックを保持している場合→holding関数の戻り値が1の場合は、panic関数を呼び出してメッセージを出力します。
ロックを保持していない場合→holding関数の戻り値が0の場合は、if文内の処理に入らず次の処理へ進みます。

ロックを取得する

while(xchg(&lk->locked, 1) != 0);

xchg関数を呼び出して、lockedに格納されている値を戻り値として取得します。
xchg(&lk->locked, 1) != 0 が真となる場合→xchg関数の戻り値が1の場合→他のプロセッサ(スレッド)がロックを保持している場合は、whileループを繰り返します。
xchg(&lk->locked, 1) != 0 が偽となる場合→xchg関数の戻り値が0の場合→他のプロセッサ(スレッド)がロックを保持していない場合は、whileループから抜け出して次の処理へ進みます。

メモリバリアを作成する

__sync_synchronize();

__sync_synchronizeは、コンパイラやプロセッサが、この関数が定義された箇所を超えて、命令を並び替えないようにするためのメモリバリアを作成します。
コンパイラの最適化やプロセッサのアウトオブオーダーによって、lk->cpu = mycpu()の処理の後にxchg(&lk->locked, 1)の処理が行われるといったことを防ぐために、xchg(&lk->locked, 1)とlk->cpu = mycpu()の間にメモリバリアを作成します。

ロックを保持しているプロセッサ(スレッド)を記録する

lk->cpu = mycpu();

mycpu関数を呼び出して、現在この処理を実行しているプロセッサ(スレッド)に対応しているcpu構造体のアドレスをロックlkのcpuメンバに格納します(ロックlkを取得しているプロセッサ(スレッド)を記録します)。

スタックトレースを記録する

getcallerpcs(&lk, lk->pcs);

getcallerpcs関数を呼び出して、デバッグのためにスタックトレースを記録します。