LoginSignup
2

More than 5 years have passed since last update.

Linuxのロック共通関数とspinlock

Posted at

詳解Linuxカーネルの5章カーネルの同期処理に関連する、Linuxのロックについて調べた。
ロックは共通関数が多そうなので、それらの仕組みから調べてみる。

カーネルバージョン

v4.4

lock_acquire

lockdep.hに実装されているマクロから、lock_acquire()はspinlock, rwlock(読み書き用spinlock), シークエンスロック, mutex, 読み書き用semaphoreで利用されているらしい。
これはlockdep-designのカーネルドキュメントにもあるように、ロックが正しいことを検証するためのデバッグのための関数である。

#define lock_acquire_exclusive(l, s, t, n, i)       lock_acquire(l, s, t, 0, 1, n, i)
#define lock_acquire_shared(l, s, t, n, i)      lock_acquire(l, s, t, 1, 1, n, i)
#define lock_acquire_shared_recursive(l, s, t, n, i)    lock_acquire(l, s, t, 2, 1, n, i)

LOCK_CONTENDEDマクロ

lock_acquire()のあとに呼ばれているマクロで、(少なくとも)スピンロックと読み書き用セマフォで使われているようだ。
try()は取得できないとエラー終了するロック取得関数、lock()は何らかの方法でロック取得を待つという違いがあると思われる。つまりlockdepのポリシーに従うと、次のロック取得のフローがある。(参考:LKML

ロック競合がある場合
lock_acquire() -> *(try)()[失敗] -> lock_contended() -> *(lock)() -> lock_acquired()
ロック競合がない場合
lock_acquire() -> *(try)() -> lock_acquired()

ソースコードは次のようになっている。つまり、上位から関数ポインタとして渡されるlockがそれぞれのロックの違いが一番現れるところかな。

extern void lock_contended(struct lockdep_map *lock, unsigned long ip);
extern void lock_acquired(struct lockdep_map *lock, unsigned long ip);

#define LOCK_CONTENDED(_lock, try, lock)            \
do {                                \
    if (!try(_lock)) {                  \
        lock_contended(&(_lock)->dep_map, _RET_IP_);    \
        lock(_lock);                    \
    }                           \
    lock_acquired(&(_lock)->dep_map, _RET_IP_);         \
} while (0)

spinlockの実装

lockはarch_spin_lock()を渡す。下記はx86のコードである。スピンロックなので、for(;;)で待つところがメインの処理だろうと思う。
ループを抜ける条件は__tickets_equal(inc.head, inc.tail)で、自分の番号tail == lock.headとなった時に、ロックが取れたことになる。

static __always_inline void arch_spin_lock(arch_spinlock_t *lock)
{
    register struct __raw_tickets inc = { .tail = TICKET_LOCK_INC };

    inc = xadd(&lock->tickets, inc); ここでtailに加算、戻り値は元の値
    if (likely(inc.head == inc.tail))
        goto out;

    for (;;) {
        unsigned count = SPIN_THRESHOLD;

        do {
            inc.head = READ_ONCE(lock->tickets.head);
            if (__tickets_equal(inc.head, inc.tail)) 元のtailと、今のheadが一致
                goto clear_slowpath;
            cpu_relax();
        } while (--count);
        __ticket_lock_spinning(lock, inc.tail);
    }
clear_slowpath:
    __ticket_check_and_clear_slowpath(lock, inc.head);
out:
    barrier();  /* make sure nothing creeps before the lock is taken */
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2