硬核C++实现64位操作系统 Spinlock(3)

Aug 15, 2020

本节先实现一个保护共享资源基础设施

class Spinlock
{
public:
    inline void lock()
    {
        asm volatile("movq $1, %%rcx	            \n\t"
                     "spin_lock_retry:              \n\t"
                     "xorq %%rax, %%rax	            \n\t"
                     "lock; cmpxchgq %%rcx, %0      \n\t"
                     "pause                         \n\t"
                     : "=m"(this->lock_val)
                     :: "memory", "rcx", "rax");
        asm volatile("jnz    spin_lock_retry    \n\t");
    }

    inline void unlock()
    {
        this->lock_val = 0;
    }

private:
    volatile uint64_t lock_val = 0; // 0:unlock, 1:lock
};

使用一个uint64_t变量作为锁的数据结构即可

lock()

首先介绍cmpxchgq指令 这里直接上AMD文档

简单的讲,就是将RAX与第一个操作数比较,如果相等的话,把第二个操作数复制给第一个,否则就把第一个操作数复制给RAX

首先把rcx设置为1

movq $1, %rcx

 随后在每一个拿锁失败的loop中,设置rax为0

spin_lock_retry:       
	xorq %%rax, %%rax	   

接下来就是做一个交换,这里ATT汇编语法跟INTEL操作数位置是相反的

首先比较%0(也就是lock_val)与rax(值为0)

1. 如果相等,把rcx复制给lock_val, 此时lock_val值被赋为1,设置ZF

2. 如果不相等,rax的值会被设置为%0(也就是0),清ZF

lock; cmpxchgq %%rcx, %0

这里需要使用lock前缀使cmpxchg变成原子操作,参考AMD文档

随后的jnz指令根据上述比较结果跳转

jnz    spin_lock_retry

可以看到 ZF没有置位则起跳,也就是上面情况2

unlock()

解锁就比较简单了

inline void unlock()
{
        this->lock_val = 0;
        asm volatile("mfence");
}

直接赋值就ok,后面加个mfence同步顺序并且刷掉write buffer

总结

这个锁实际上不适合SMP,会出现多个CPU竞争一个lock的时候,对同一个内存变量疯狂进行cmpxchgq的情况。可以升级成ticket lock改善。

另外,锁在进程线程环境下还需要考虑中断,这个以后再加入。

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.