[Linux Kernel] Mutex 互斥锁 官方文档

什么是互斥锁?

   在Linux内核中,互斥锁指的是在共享内存系统上强制序列化的特定锁定原语,而不仅仅是指学术界或类似理论教科书中提到的“互斥”的通用术语。

   互斥锁是一种休眠锁,其行为类似于二进制信号量,2006年[1]引入互斥锁作为它们的替代。这种新的数据结构提供了许多优点,包括更简单的接口,以及当时更小的代码(见缺点)。

[1] http://lwn.net/Articles/164802/

实现

   互斥量由“struct mutex”表示,在include/linux/mutex.h中定义,并在kernel/locking/mutex.c中实现。这些锁使用一个原子变量(->owner)来跟踪锁在其生命周期中的状态。“owner” 字段实际上包含当前锁所有者的 “struct task_struct *” ,因此如果当前不属于它,则为 NULL 。由于任务结构指针至少对齐一级缓存字节(L1_CACHE_BYTES),因此使用低位(3)存储额外的状态(例如,如果等待列表为非空)。它最基本的形式还包括一个等待队列和一个对其进行序列化访问的自旋锁。此外,如果配置 CONFIG_MUTEX_SPIN_ON_OWNER=y ,那么系统使用一个 spinner MCS lock (->osq), 将在下面的 (2) 中进行描述。

获取互斥锁时,根据锁的状态,可以采用三种可能的路径:

  1. fastpath: 尝试通过cmpxchg()对当前任务的所有者原子地获取锁。这只适用于无竞争的情况(cmpxchg()检查0UL,因此上面的3个状态位都必须为0)。如果锁被争夺,它将转到下一个可能的路径。

  2. midpath: 又名乐观旋转(aka optimistic spinning),尝试在锁所有者运行时旋转获取,并且没有其他准备运行的任务具有更高的优先级(need_resched)。理由是,如果锁所有者正在运行,则很可能会很快释放锁。 互斥锁微调器(Mutex Spinners)使用MCS锁定排队,因此只有一个微调器可以竞争该互斥锁。

    MCS锁(由Mellor-Crummey和Scott提出)是一种简单的自旋锁,具有理想的公平属性,并且每个cpu都试图获取在局部变量上旋转的锁。它避免了常见的测试和设置自旋锁实现招致的昂贵的高速缓存行反弹。类似于MCS的锁是专门为乐观旋转而量身定制的,用于实现睡眠锁。自定义MCS锁的一个重要功能是,它具有额外的属性,使微调程序可以在需要重新计划时退出MCS自旋锁队列。这进一步有助于避免需要重新安排时间的MCS微调器继续等待旋转互斥体所有者,而仅在获得MCS锁定后直接进入慢速路径的情况。

  3. slowpath: 不得已时,如果仍然无法获取锁定,则将任务添加到等待队列中并休眠直到被解锁路径唤醒。 通常情况下,它以 TASK_UNINTERRUPTIBLE 的形式阻塞。

虽然形式上的内核互斥锁是可睡眠的锁,但路径(2)使它们实际上更是一种混合类型。 通过简单地不中断任务并忙于等待几个周期而不是立即休眠,该锁的性能可以显着改善许多工作负载。 请注意,此技术还用于rw信号量。

Semantics

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

  • 一次只有一个任务可以获得互斥锁。
  • 只有所有者才能解锁互斥锁。
  • 不允许多次解锁。
  • 不允许递归锁定/解锁。
  • 互斥锁只能通过API初始化(请参见下文)。
  • 一个任务获得互斥锁后,可能不会退出。
  • 不能在获得锁的地方释放内存。(Memory areas where held locks reside must not be freed.)
  • 不能初始化获得的锁。
  • 互斥体不能在硬件或软件中断上下文中使用,例如tasklet和计时器。

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

  • 互斥锁在 Debug Output 时,总是输出互斥锁的符号名称。
  • 获取点跟踪,函数名称的符号查找,系统中所有锁的列表以及它们的打印输出。
  • Owner tracking. 跟踪所有者
  • 检测自我递归的锁并打印出所有相关信息。
  • 检测多任务循环死锁,并打印出所有受影响的锁和任务(仅打印那些任务)。

Interfaces

静态定义互斥锁:

  • DEFINE_MUTEX(name);

动态初始化互斥锁:

  • mutex_init(mutex);

获取互斥锁,uninterruptible:

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

获取互斥锁,interruptible:

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

Acquire the mutex, interruptible, if dec to 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);

Disadvantages

   Unlike its original design and purpose, ‘struct mutex’ is among the largest locks in the kernel. E.g: on x86-64 it is 32 bytes, where ‘struct semaphore’ is 24 bytes and rw_semaphore is 40 bytes. Larger structure sizes mean more CPU cache and memory footprint.

When to use mutexes

   Unless the strict semantics of mutexes are unsuitable and/or the critical region prevents the lock from being shared, always prefer them to any other locking primitive.

   简单来说就是不在中断里面用,在哪用都是比较优的选择。

参考

  • linux-5.4.37/Documentation/locking/mutex-design.rst
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值