general_lock

自旋锁

普通自旋锁 spinlock

相关结构体:

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

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

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

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

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

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

相关API:

DEFINE_SPINLOCK(lock) // 静态定义一个名为lock的自旋锁
spin_lock_init(lock)  // 自旋锁初始化(设置为未锁状态)

spin_lock(lock)                // 加锁操作,加锁成功后返回,否则一直自旋(忙等)
spin_lock_irqsave(lock, flags) // 加锁操作,并关闭硬中断
spin_lock_bh(lock)             // 加锁操作,并关闭软中断

spin_unlock(lock)                   // 解锁操作,解锁过程无竟争,因此必然会成功
spin_unlock_irqrestore(lock, flags) // 解锁操作,并打开硬中断
spin_unlock_bh(lock)                // 解锁操作,并打开软中断

读写自旋锁 rwlock_t

普通自旋锁的缺点:

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

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

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

读写自旋锁的改进:

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

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

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

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

相关API:

DEFINE_RWLOCK(lock) // 静态定义一个名为lock的自旋锁
rwlock_init(lock)   // 自旋锁初始化(设置为未锁状态)

read_lock(lock)     // 加读锁操作,加锁成功后返回,否则一直自旋
write_lock(lock)    // 加写锁操作,加锁成功后返回,否则一直自旋
read_unlock(lock)   // 解读锁操作,解锁过程无竟争,因此必然会成功
wite_unlock(lock)   // 解写锁操作,解锁过程无竟争,因此必然会成功

信号量

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

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

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

  • 工作原理与自旋锁相反

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

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

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

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

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

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

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

    • 信号量的缺点:

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

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

普通信号量

相关结构体:

 struct semaphore {
     raw_spinlock_t lock;
     unsigned int count;
     struct list_head wait_list;
 };
  • count标识信号量的状态 : 值为0表示忙(已锁),值为正代表自由(未锁,允许竞争者进入临界区)

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

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

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

相关API:

DEFINE_SEMAPHORE(sem)                          // 静态定义一个名为sem信号量
void sema_init(struct semaphore *sem, int val) // 初始化一个信号量sem,计数器初值为val

// 减少信号量sem的计数器(类似于获取锁)
// 如果失败(计数器己经是0),那么转入睡眠(状态为IASK_ UNINTERRUPTIBLE,不会被任何信号唤醒)并把当其进程挂到wait_list; 被唤醒后继续尝试获取锁
void down(struct semaphore *sem)
// 增加信号量sem的计数器(类似于释放锁),然后唤醒wait_list里面的第一个进程(如果有的话)
void up(struct semaphore *sem)

读写信号量

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

相关结构体:

struct rw_semaphore {
    long count;
    struct list_head wait_list;
    raw_spinlock_t wait_lock;

#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
    struct optimistic_spin_queue osq;
    struct task_struct *owner;
#endif
}

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

相关API:

DECLARE_RWSEN(sem) // 静态声明一个名为sem信号量
init_rwsen(sem)    // 初始化一个信号量sem

down_read(struct rw_semaphore *sem) // 读者减少信号量sem的计数器(类似于获取锁)
down_write(struct rw_semaphore *sem)// 写者减少信号量sem的计数器(类似于获取锁)
up_read(struct rw_semaphore *sem)   // 读者增加信号量sem的计数器(类似于释放锁)
up_write(struct rw_semaphore *sem)  // 写者增加信号量sem的计数器(类似于释放锁)

互斥量

互斥量是二值信号量

相关结构体:

struct mutex {
    atomic_t count;
    spinlock_t wait_lock;
    struct list_head wait_list;

#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    struct task_struct *owner;
    struct optimistic_spin_queue osq;
#endif
};

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

相关API:

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

DEFINE_MUTEX(mutex) // 静态定义一个名为 mutex的互斥量
mutex_init(mutex)   // 初始化一个互斥量 mutex,初始状态为未锁

// 对互斥量加锁,如果失败,那么转入睡眠(状态为 TASK_UNINTERRUPTIBLE,不会被任何信号唤醒)并将进程挂到wait_list
void mutex_lock(struct mutex *lock)
// 对互斥量解锁,然后唤醒wait_list里面的第一个进程(如果有的话)
void mutex_unlock(struct mutex *lock)

死锁原理

典型例子:

  • 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?