ARM Linuxでのspinlockの実現



1)spin lock構造体
typedef struct spinlock {
    union {
        struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
        struct {
            u8 __padding[LOCK_PADSIZE];
            struct lockdep_map dep_map;
        };
#endif
    };
} spinlock_t;

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
	unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;


typedef struct {
    union {
        u32 slock;
        struct __raw_tickets {
#ifdef __ARMEB__
            u16 next;
            u16 owner;
#else
            u16 owner;
            u16 next;
#endif
        } tickets;
    };
} arch_spinlock_t;

CONFIGマクロの定義はともかくarch_spinlock_tという構造体.
この構造体は32 bitsサイズです.unionを使いました.slockとnext ownerはこの32 bitsを共用している.
旧バージョンのカーネルspin lock構造体はu 32が1つしかなく、0であればロックが得られ、0でなければスピン待機することはありません.新しいスピンロックにnextとownerが追加され、スレッドはロックを取得する順序でキューに並ぶことができます.
2)、ロックを取得する
static inline void spin_lock(spinlock_t *lock)
{
	raw_spin_lock(&lock->rlock);
}

何も言えないままraw_spin_lockは、その後、中間に数回の呼び出し、またはマクロ定義(SMPとUPを区別するなど)を経た.
最終的に
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
	preempt_disable();
	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
	LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}

1,プリエンプトを閉じて、同じcpuの上でプリエンプトを閉じて同期の問題を解決して、UPの上でspin lockは実はプリエンプトを閉じます
2、これはコンパイル時の静的検査に関連し、ロック解除とロックペアの出現を保証する.
3,このマクロは最終的にdo_を呼び出しましたraw_spin_ロック、さらにarch_spin_lock
3)arch_spin_lock

static inline void arch_spin_lock(arch_spinlock_t *lock)
{
	unsigned long tmp;
	u32 newval;
	arch_spinlock_t lockval;

	__asm__ __volatile__(
"1:	ldrex	%0, [%3]
" " add %1, %0, %4
" " strex %2, %1, [%3]
" " teq %2, #0
" " bne 1b" : "=&r" (lockval), "=&r" (newval), "=&r" (tmp) : "r" (&lock->slock), "I" (1 << TICKET_SHIFT) : "cc"); while (lockval.tickets.next != lockval.tickets.owner) { wfe(); lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner); } smp_mb(); }

8行目からはasmアセンブリが埋め込まれています.volatile__キーワードは、次のコードが最適化されて変更されないことを確認します.
埋め込みアセンブリのルールに従って、%0はlockvalであり、タイプはarch_である.spinlock_tタイプ,%1%2はnewvalとtmpの順に類推される.
アセンブル
1行目:lock->slock値をlock_に保存val
2行目:newval=lockval+1<
             TICKET_SHIFTはarch_を示すために使用されますspinlock_tにおけるownerとnextがそれぞれどれだけのbitを占めるかは,16と定義すると,ownerとnextはそれぞれ16 bitを占める.
プラス1<
3行目、4行目:lock->slock=newval、strex戻り値を判断し、ロックの更新に成功したかどうかを確認し、更新に失敗した場合は、他のカーネルパスの挿入があることを示します. 
ldrexとstrexはこの方法で原子性を保証するので,同じ時間にlock->slockの値を正常に更新できるスレッドは1つしかない.
同じコアにおける原子操作も同様の方法で実現される.
5行目:更新に失敗した場合は1行に戻ります.
更新に成功したら、次のcコードに進みます.
nextがownerに等しいかどうかを判断し、等しい場合はロックを取得し、等しくない場合は説明が自分の番になっていない場合はwfe()を呼び出して現在のcpuを保留する.
ロック解除操作
static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
	smp_mb();
	lock->tickets.owner++;
	dsb_sev();
}

ロックを解放するのは簡単で、直接owner++で、sev命令はすべてのcpuに通知し、他のcpuはロックを取得しようとします.
なぜロックを解放するには原子操作でownerを操作する必要がなく、ロックを取得するにはnextを原子操作する必要があるのかを考えてみましょう.
 
さらに
smp_でmb()データメモリバリアは、乱順実行時のメモリ操作の順序を保証します.