通用互斥子系统 【ChatGPT】

发布时间 2023-12-10 10:25:00作者: 摩斯电码

通用互斥子系统

由Ingo Molnar mingo@redhat.com发起

由Davidlohr Bueso davidlohr@hp.com更新

互斥锁是什么?

在Linux内核中,互斥锁指的是一种特定的锁原语,它在共享内存系统上强制进行串行化,而不仅仅是指学术界或类似理论教材中所指的“互斥排斥”一般术语。互斥锁是一种睡眠锁,其行为类似于二进制信号量,并且于2006年1作为这些信号量的替代品被引入。这种新的数据结构提供了许多优势,包括更简单的接口,以及当时更小的代码(参见缺点)。

实现

互斥锁由在include/linux/mutex.h中定义的'struct mutex'表示,并在kernel/locking/mutex.c中实现。这些锁使用原子变量(->owner)在其生命周期内跟踪锁状态。字段owner实际上包含当前锁的所有者'struct task_struct *',因此如果当前未被占用,则为NULL。由于task_struct指针至少对齐到L1_CACHE_BYTES,低位(3位)用于存储额外状态(例如,如果等待列表非空)。在其最基本的形式中,它还包括一个等待队列和一个用于串行访问的自旋锁。此外,CONFIG_MUTEX_SPIN_ON_OWNER=y系统使用一个自旋MCS锁(->osq),在下面的(ii)中进行了描述。

在获取互斥锁时,可以采取三种可能的路径,具体取决于锁的状态:

  • 快速路径:尝试通过使用cmpxchg()原子地将所有者与当前任务进行比较交换来快速获取锁。这仅在无争用的情况下有效(cmpxchg()针对0UL进行检查,因此上述所有3个状态位必须为0)。如果锁有争用,它将进入下一个可能的路径。

  • 中间路径:也称为乐观自旋,尝试在锁所有者正在运行且没有其他准备运行的任务具有更高优先级(need_resched)时自旋以获取锁。其基本原理是,如果锁所有者正在运行,则很可能很快释放锁。互斥自旋者使用MCS锁排队,以便只有一个自旋者可以竞争互斥锁。

  • 慢路径:作为最后的手段,如果仍然无法获取锁,则任务将被添加到等待队列,并在解锁路径唤醒之前进入睡眠状态。在正常情况下,它会阻塞为TASK_UNINTERRUPTIBLE。

虽然从形式上来说内核互斥锁是可睡眠锁,但是路径(ii)使得它们在实际上更像是一种混合类型。通过简单地不中断任务并忙等待几个周期,而不是立即进入睡眠状态,这种锁的性能在许多工作负载中已经被证实显著提高。请注意,这种技术也用于读写信号量。

语义

互斥子系统检查并强制执行以下规则:

  • 一次只能有一个任务持有互斥锁。

  • 只有所有者可以解锁互斥锁。

  • 不允许多次解锁。

  • 不允许递归锁定/解锁。

  • 互斥锁必须仅通过API进行初始化(见下文)。

  • 任务不得在持有互斥锁的情况下退出。

  • 持有的锁所在的内存区域不得被释放。

  • 持有的互斥锁不得重新初始化。

  • 互斥锁不得在硬件或软件中断上下文中使用,例如任务队列和定时器。

当启用CONFIG DEBUG_MUTEXES时,这些语义将得到完全执行。此外,互斥锁调试代码还实现了许多其他功能,使锁调试更加容易和快速:

  • 在调试输出中打印互斥锁的符号名称。

  • 获取时的跟踪,函数名称的符号查找,系统中所有持有的锁的列表,以及它们的打印输出。

  • 所有者跟踪。

  • 检测自递归锁,并打印所有相关信息。

  • 检测多任务循环死锁,并打印所有受影响的锁和任务(以及仅这些任务)。

接口

静态定义互斥锁:

DEFINE_MUTEX(name);

动态初始化互斥锁:

mutex_init(mutex);

获取互斥锁,不可中断:

void mutex_lock(struct mutex *lock);
void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
int mutex_trylock(struct mutex *lock);

获取互斥锁,可中断:

int mutex_lock_interruptible_nested(struct mutex *lock, unsigned int subclass);
int mutex_lock_interruptible(struct mutex *lock);

获取互斥锁,可中断,如果减少到0:

int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);

解锁互斥锁:

void mutex_unlock(struct mutex *lock);

测试互斥锁是否被占用:

int mutex_is_locked(struct mutex *lock);

缺点

与其最初的设计和目的不同,'struct mutex'是内核中最大的锁之一。例如:在x86-64上它占用32字节,而'struct semaphore'占用24字节,rw_semaphore占用40字节。较大的结构大小意味着更多的CPU缓存和内存占用。

何时使用互斥锁

除非互斥锁的严格语义不适用和/或关键区域阻止锁被共享,否则始终优先选择它们而不是任何其他锁原语。