general_lock

自旋锁

普通自旋锁 spinlock

相关结构体:

typedef struct spinlock(
    union {
        struct raw_spinlock rlock:
    }
} spinlock t:

struct raw_spinlock {
    arch_spinlock_t raw_lock;
}
  • 获取锁的过程(即上锁的过程)是自旋(忙等)的,不会引起睡眠和调度

  • 持有自旋锁的临界区中不允许调度和睡眠,自旋锁的加锁操作会禁止抢占,解锁操作时再恢复抢占

    自旋锁既可以用于进程上下文,又可以用于中断上下文

  • 自旋锁的主要用途是多处理器之间的并发控制,适用于锁竞争不太激烈的场景

    如果锁竞争非常激烈,那么大量的时间会浪费在加锁自旋中,导致整体性能下降

相关API:

读写自旋锁 rwlock_t

普通自旋锁的缺点:

  • 对所有的竞争者不做区分

  • 很多情况下,有些竞争者并不会修改共享资源(只读不写)

  • 普通自旋锁总是会限制只有一个内核路径持有锁,而实际上这种限制是没有必要的

读写自旋锁的改进:

  • 允许多个读者同时持有读锁(允许多个读者同时进入读临界区)

  • 只允许一个写者同时持有写锁(只允许一个写者同时进入写临界区)

  • 不允许读者和写者同时持有锁

  • 与普通自旋锁相比,读写自旋锁更适合读者多,写者少的应用场景

相关API:

信号量

  • Linux内核中应用最广泛的同步方式: 自旋锁、信号量(semaphore)

  • 自旋锁和信号量是一种互补的关系,它们有各自适用的场景

  • 信号量可以是多值的,当其用作二值信号量时,类似于锁 : 一个值代表未锁,另一个值代表已锁

  • 工作原理与自旋锁相反

    • 获取锁的过程中,若不能立即得到锁,就会发生调度,转入睡眠

    • 另外的内核执行路径释放锁时,唤醒等待该锁的执行路径

  • 自旋锁的使用限制及信号量的解决方法

    • 持有自旋锁的临界区不允许调度和睡眠 : 竟争激烈时整体性能不好

    • 信号量解决了以上两个问题:

      • 锁的竟争者不是忙等,信号量的临界区允许调度和睡眠而不会导致死锁

      • 锁的竞争者会转入睡眠,从而让出CPU资源给别的内核执行路径,因此对锁的竞争不会影响整体性能

    • 信号量的缺点:

      • 中断上下文要求整体运行时间可预测(不能太长),而信号量临界区可能发生调度,因此不能用于中断上下文(只能用于进程上下文)

      • 如果抢锁的过程很短,那么用信号量并不合算,因为进程睡眠加上唤醒的代价太大,消耗的CPU资源可能远远大于短时间的忙等

普通信号量

相关结构体:

  • count标识信号量的状态 : 值为0表示忙(已锁),值为正代表自由(未锁,允许竞争者进入临界区)

  • count的初值就是最大允许进入临界区的进程数目,初值为1的信号量就是二值信号量

  • 二值信号量类似于一个普通的锁,而多值信号量类似于一个允许一定并发性的锁

  • wait_list字段是当信号量为忙时,所有等待信号量的进程列表,而lock则是保护wait_list的自旋锁

相关API:

读写信号量

读写信号量的引入原因类似于读写自旋锁,是为了区分不同的竟争者(读者和写者),以便允许读者共享而写者互斥

相关结构体:

主要字段 count、 wait_list 和 wait_lock的含义与普通信号量基本相同,owner指向写者进程

相关API:

互斥量

互斥量是二值信号量

相关结构体:

在数据结构上与信号量几乎相同

相关API:

互斥量提供了一套新的API,这套API专为二值的互斥量优化

死锁原理

典型例子:

  • spinlock, rwlock, mutex 在 linux内核中是不能递归,如果产生递归会发生死锁

    例如: 调用spinlock(A),在本cpu上发生中断,中断上下文也调用到 spinlock(A)

  • 多个CPU相互等待lock A/B

    --------CPU1------------------------------CPU2---------

    获取 lock A -> ok ----------------- 获取 lock B -> ok

    获取 lock B -> spin --------------- 获取 lock A -> spin

Last updated

Was this helpful?