一、互斥体概述


  • 直到最近,内核中唯一允许睡眠的锁是信号量。多数用户使用信号量只使用计数1,说白了是把其作为一个互斥的排他锁使用——好比允许睡眠的自旋锁
  • 不幸的是,信号量用途更通用, 没多少使用限制。这点使得信号量适合用于那些较复杂的、未明情况下的互斥访问,比如内核于用户空间复杂的交互行为。但这也意味着简单的锁定而使用信号量并不方便,并且信号量也缺乏强制的规则来行使任何形式的自动调试,即便受限的调试也不可能。​为了找到一个更简单睡眠锁,内核开发者们引入了互斥体​(mutex)。确实,这个名字容易和我们习惯称呼混淆。所以这里我们澄清一下,“互斥体(mutex)”这个称谓所指的是任何可以睡眠的强制互斥锁,比如使用计数是1的信号量。但在最新的Linux内核中,“互斥体(mutex)”这个称谓现在也用于一种实现互斥的特定睡眠锁。也就是说,​互斥体是一种互斥信号
  • mutex在内核中​对应数据结构mutex​,其​行为和使用计数为1的信号量类似​,但操作接口更简单,实现也更高效,而且使用限制更强

二、互斥体的使用

  • 静态​定义如下:
DEFINE_MUTEX(name);
  • 动态初始化​mutex,如下:
mutex_init(&mutex);
  • 对互斥锁​锁定和解锁​如下:
//加锁
mutex_lock(&mutex);

//临界区

//解锁
mutex_unlock(&mutex);

  • 可以看出,互斥体就是一个​简化版的信号量,因为不再需要管理任何使用计数
  • mutex操作列表如下:

Linux(内核剖析):32---内核同步之(互斥体(mutex))_互斥体(mutex)

  • mutex的简洁性和高效性源自于相比使用信号量更多的受限性。它不同于信号量,因为mutex仅仅实现了Dijkstra设计初衷中的最基本的行为。​因此mutex的使用场景相对而言更严格、 更定向了:

  • 任何时刻中只有一个任务可以持有mutex, 也就是说,mutex的使用计数永远是1
  • 给mutex锁者必须负责给其再解锁——你不能在一个上下文中锁定一个mutex,而在另 一个上下文中给它解锁。这个限制使得mutex不适合内核同用户空间复杂的同步场景。最 常使用的方式是:在同一上下文中上锁和解锁。
  • 递归地上锁和解锁是不允许的。也就是说,你不能递归地持有同一个锁,同样你也不能再去解锁一个已经被解开的mutex
  • 当持有一个mutex时 ,进程不可以退出
  • mutex不能在中断或者下半部中使用,即使使用mutex_trylock()也不行
  • mutex只能通过官方API管理:它只能使用上节中描述的方法初始化,不可被拷贝、手动 初始化或者重复初始化


内核配置选项

  • 也许mutex结构最有用的特色是:通过一个特殊的调试模式,内核可以采用编程方式检査和警告任何践踏其约束法则的不老实行为。当打开内核配置选项CONFIG_DEBUG_MUTEXES后,就会有多种检测来确保这些(还有别的一些)约束得以遵守。这些调试手段无疑能帮助你和其他mutex使用者们都能以规范式的、简单化的使用模式对其使用


三、信号量和互斥体

  • 互斥体和信号量很相似,内核中两者共存会令人混淆。所幸,它们的标准使用方式都有简单规范:除非mutex的某个约束妨碍你使用,否则相比信号量要优先使用mutex。当你写新代码时,只有碰到特殊场合(一般是很底层代码)才会需要使用信号量。因此建议 选mutex。如果发现不能满足其约束条件,且没有其他别的选择时,再考虑选择信号量

四、自旋锁和互斥体


  • 了解何时使用自旋锁,何时使用互斥体(或信号量)对编写优良代码很重要,但是多数情况下,并不需要太多的考虑,因为在中断上下文中只能使用自旋锁,而在任务睡眠时只能使用互斥体
  • 下标回顾了一下各种锁的需求情况

Linux(内核剖析):32---内核同步之(互斥体(mutex))_信号量_02