使用x86_64汇编写一个自旋锁

Posted akyna-zh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用x86_64汇编写一个自旋锁相关的知识,希望对你有一定的参考价值。

一、理论分析

自旋锁,顾名思义,即自己不断旋转重复进行的锁,当多个线程访问同一资源时,为实现互斥访问,必须给目标资源加锁,此时只允许一个线程访问,此时其他线程无法访问,并且一直重复请求访问,直到该锁被释放。访问完资源的线程及时释放锁以供其他资源访问。

自旋锁可以通过比较替换算法实现:设锁为1时被占用,为0时空闲。当一个线程请求锁时,即进入请求锁循环“spinlock”,设预期值为0,修改值为1,让锁值与预期值比较,若锁值等于预期值,则锁空闲,将锁值置为修改值,退出spinlock循环;若锁值不等于预期值,则证明锁被占用,继续spinlock循环。

为验证是否成功实现自旋,开启一个释放锁线程,请求锁线程自旋一段时间后,释放锁线程进行锁的释放,即把锁值置为预期值0。此时,请求锁线程成功获得锁并退出spinlock循环。

二、设计与实现

使用x86_64汇编实现自旋锁:

Intel语法

// 尝试获取锁
void lock(long *p) 
    long a = 0, c = 1;
    printf("try to get lock...\\n");
    __asm__(
        "push rax \\n\\t"
        "push rcx \\n\\t"
        "spin_lock: \\n\\t"
        "mov rcx, %[c] \\n\\t"
        "mov rax, %[a] \\n\\t"
        // 比较并替换算法,若p==rax==0则获得锁并使p=rcx(==1),若p(==1)!=rax则进入自旋。
        "lock cmpxchg %[p], rcx \\n\\t"
        "jne spin_lock \\n\\t"
        "pop rcx \\n\\t"
        "pop rax \\n\\t"
        : [p]"+m"(*p)
        : [a]"r"(a), [c]"r"(c)
        : "rcx", "rax"
    );

// 释放锁
void unlock(long *p) 
    __asm__(
        "mov %[p], 0; \\n\\t"
        : [p]"+m"(*p)
    );

AT&T语法

void lock(long *p) 
    long a = 0, c = 1;
    printf("try to get lock...\\n");
    __asm__(
        "pushq %%rax \\n\\t"
        "pushq %%rcx \\n\\t"
        "spin_lock: \\n\\t"
        "movq %1, %%rcx \\n\\t"
        "movq %2, %%rax \\n\\t"
        "lock cmpxchg %%rcx, %0 \\n\\t"
        "jne spin_lock \\n\\t"
        "popq %%rcx \\n\\t"
        "popq %%rax \\n\\t"
        : "+m"(*p)
        : "r"(c), "r"(a)
        : "%rcx", "%rax"
    );

void unlock(long *p) 
    __asm__(
        "movq $0, %0; \\n\\t"
        : "+r"(*p)
    );

测试自旋锁

初始化锁值为1,主线程尝试获取锁,进入自旋,子线程在一段时间后释放锁,锁值置为0,接着,主线程获得锁并把锁置为1。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// intel语法实现自旋锁 > gcc -pthread -masm=intel -o s spinlock.c
// 尝试获取锁
void lock(long *p) 
    long a = 0, c = 1;
    printf("try to get lock...\\n");
    __asm__(
        "push rax \\n\\t"
        "push rcx \\n\\t"
        "spin_lock: \\n\\t"
        "mov rcx, %[c] \\n\\t"
        "mov rax, %[a] \\n\\t"
        // 比较并替换算法,若p==rax==0则获得锁并使p=rcx(==1),若p(==1)!=rax则进入自旋。
        "lock cmpxchg %[p], rcx \\n\\t"
        "jne spin_lock \\n\\t"
        "pop rcx \\n\\t"
        "pop rax \\n\\t"
        : [p]"+m"(*p)
        : [a]"r"(a), [c]"r"(c)
        : "rcx", "rax"
    );

// 释放锁
void unlock(long *p) 
    __asm__(
        "mov %[p], 0; \\n\\t"
        : [p]"+m"(*p)
    );


// 释放锁线程
void *mythread(void* args) 
    long* p = (long*) args;
    // 推迟释放锁,此时自旋在进行中
    sleep(2);
    // 释放锁
    unlock(p);
    printf("after unlock: %ld\\n", *p);


int main() 
    long a = 1; // 设刚开始锁已被获取
    long *p = &a;
    // 开启一个用于释放锁的线程
    pthread_t t1;
    pthread_create(&t1, NULL, mythread, (void*)p);
    printf("before lock: %ld\\n", *p);
    // 主线程尝试获取
    lock(p);
    pthread_join(t1, NULL);    
    printf("after lock: %ld\\n", *p);
    return 0;

这里采用intel语法编写的自旋锁进行测试,执行命令gcc -pthread -masm=intel -o s spinlock.c进行编译。

若采用AT&T,执行命令gcc -pthread -o s spinlock.c进行编译,无需-masm=intel,因为gcc底层默认采用AT&T。

三、运行结果

运行结果如下:

before lock: 1
try to get lock...
after unlock: 0
after lock: 1

一开始锁值为1,请求锁线程(即主线程)请求获得锁,进入自旋。2s后释放锁线程进行锁的释放,接着请求锁线程成功获得锁,锁值又被置为1,成功实现自旋与锁的释放。

over.

以上是关于使用x86_64汇编写一个自旋锁的主要内容,如果未能解决你的问题,请参考以下文章

使用 GNU 汇编器在 x86_64 中调用 printf

x86 中“PAUSE”指令的目的是啥?

使用头文件编译 C++ 程序时出错:体系结构 x86_64 的未定义符号

AT&T x86_32 汇编_002_使用C库函数

如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用

编译时错误“架构 x86_64 的未定义符号”是啥意思?