Linux内核——自旋锁 spinlock

跟着简叔学的,可以B站搜索 简说linux

与之前学的信号量相比,自旋锁是一种死等的机制。而信号量不会。只有一个执行单元获取锁并进入到临界区,其他的都给我死等。可以在中断上下文执行,因为是不睡眠的。中断上下文代码不允许睡眠,也不允许调用那些可能会引起睡眠的函数。这种死等的实现是不同的架构有不一样的方法。

自旋锁的实现

  1. 查看结构体定义struct spinlock (/include/linux/spinlock_types.h)1678091181583

    然后再去看看,raw_spinlock

    1678091233794

    然后再去看看arch_spinlock_t!!!!然后发现这东西的定义适合cpu架构相关的,多层封装是为了增加灵活性。。以arm为例:

    路径(/arch/arm/include/asm/spinlock_types.h)

    1678091414902

    其中owner表示持有这个数字的thread可以获取自旋锁,next表示如果后续有thread请求获取这个自旋锁,给他分配这个数字。

  2. 自旋锁的初始化 按照上述所说就是把那两个东西置为0,对spinlock的初始化一层层再找到arm里的初始化实现,就长这样1678091817407

  3. 基本所有实现都是spin—>raw_spin->arch_spin. 由不同的架构去实现,实现的逻辑就是。

    • 刚开始owner=next=0;
    • 第一个thread获取spinlock,可获取成功,此时owner==0,next=0;
    • 第二个thread获取spinlock,如果第一个thread还没有释放spinlock,则next++, next变为1;
    • 第三个thread获取spinlock,如果第一个thread还没有释放spinlock,则next++, next变为2;
      此时第一个thread释放spinlock,则执行ownerowner=1;
    • 虽然此时第二个thread和第三个thread都在等待spinlock,但是因为第二个thread的next=owner,所以第二个thread可以获取到spinlock,第三个thread则继续等待。这样保证了spinlock的唤醒机制是先到先唤醒,后到后唤醒,保证了公平性。
  4. 另外,还有一种自旋锁叫做读写自旋锁。读写自旋锁可以让多个读一起读,但是经常让写死等。

自旋锁的使用

继续修改我们的hello驱动。

  1. 首先定义自旋锁与临界资源。

    1
    2
    spinlock_t count_lock;
    int open_count=0;
  2. 然后在hello_init中进行初始化

    1
    2
    //自旋锁初始化
    spin_lock_init(&count_lock);
  3. open与close操作中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    int hello_open(struct inode *p, struct file *f)
    {

    spin_lock(&count_lock);
    if(!open_count>=1)
    {
    spin_enlock(&count_lock);
    printk(KERN_EMERG"device is busy,helloopen fall!!\r\n");
    return -EBUSY;
    }
    open_count++;
    spin_lock(&count_lock);
    printk(KERN_EMERG"hello_open ok~~~\r\n");
    return 0;
    }

    int hello_close(struct inode *inode ,struct file *filp)
    {
    if(!open_count!=1)
    {
    printk(KERN_EMERG"device is busy,helloopen fall!!\r\n");
    return -EBUSY;
    }
    spin_enlock(&count_lock);
    printk(KERN_INFO"hello_close ok");
    return 0;
    }

    之前也说过了自旋锁的特性,尽量要让临界区执行的快一点。不然cpu会经常死等。

    结果和信号量结果相同。因为从应用程序来看确实本质区别也不大。