Linux内核同步机制之(八):mutex

一、Mutex锁简介

在linux内核中,互斥量(mutex,即mutual exclusion)是一种保证串行化的睡眠锁机制。和spinlock的语义类似,都是允许一个执行线索进入临界区,不同的是当无法获得锁的时候,spinlock原地自旋,而mutex则是选择挂起当前线程,进入阻塞状态。正因为如此,mutex无法在中断上下文使用。和mutex更类似的机制(无法获得锁时都会阻塞)是binary semaphores,当然,mutex有更严格的使用规则:

1、只有mutex的owner可以才可以释放锁

2、不可以多次释放同一把锁

3、不允许重复获取同一把锁,否则会死锁

4、必须使用mutex初始化API来完成锁的初始化,不能使用类似memset或者memcp之类的函数进行mutex初始化

5、不可以多次重复对mutex锁进行初始化

6、线程退出后必须释放自己持有的所有mutex锁

当配置了DEBUG_MUTEXES的时候,内核会对上面的规则进行检查,防止用户误用mutex,产生各种问题。

下面是一个简单的mutex工作原理图:

Linux内核同步机制之(八):mutex_第1张图片

传统的mutex只需要一个状态标记和一个等待队列就OK了,等待队列中是一个个阻塞的线程,thread owner当前持有mutex,当它离开临界区释放锁的时候,会唤醒等待队列中第一个线程(top waiter),这时候top waiter会去竞争持锁,如果成功,那么从等待队列中摘下,成为owner。如果失败,继续保持阻塞状态,等待owner释放锁的时候唤醒它。在owner task持锁过程中,如果有新的任务来竞争mutex,那么就会进入阻塞状态并插入等待队列的尾部。

相对于传统的mutex,linux内核进行了一些乐观自旋的优化,也就是说当线程持锁失败的时候,可以选择在mutex状态标记上自旋,等待owner释放锁,也可以选择进入阻塞状态并挂入等待队列。具体如何选择是在自旋等待的时间开销和进程上下文切换的开销之间进行平衡。此外为了防止多个线程自旋带来的性能问题,mutex的乐观自旋机制还引入了MCS锁,后面章节我们会详细描述。

二、数据结构

1、互斥量对象

互斥量对象用struct mutex来抽象,其成员描述如下:

成员

描述

atomic_long_t   owner

这个成员有两个作用:

1、记录该mutex对象被哪一个task持有(struct task_struct *)。如果等于NULL表示还没有被任何一个任务持有。

2、由于task struct地址是L1_CACHE_BYTES对齐的,因此这个成员的有若干的LSB可以被用于标记状态(在ARM64平台上,L1_CACHE_BYTES是64字节,因此LSB 6-bit会用于保存mutex状态信息)。具体的状态包括:

MUTEX_FLAG_WAITERS:wait_list非空,即有任务阻塞在该mutex锁上,owner在unlock的时候必须要执行唤醒动作。

MUTEX_FLAG_HANDOFF:为了防止mutex等待队列的任务饿死,在唤醒的时候top waiter会设置该flag(由于乐观自旋的task不断的插入,唤醒的top waiter也未必一定会获取到锁)。有了这个设定后,锁的owner 在解锁的时候会将该锁转交给top waiter,而不是唤醒top waiter去竞争锁。

MUTEX_FLAG_PICKUP:该flag表示mutex已经万事俱备(即完成了转交),只等待top waiter来持锁。

spinlock_t   wait_lock

用于保护等待链表wait_list操作的自旋锁

struct optimistic_spin_queue osq

在配置了MUTEX_SPIN_ON_OWNER的时候,mutex支持乐观自旋机制,osq成员就是乐观自旋需要持有的MCS锁队列。数据结构optimistic_spin_queue只有一个tail的成员,如果等于0,那么说明是一个空队列,没有乐观自旋的任务。否则tail指向一个节点是optimistic_spin_node对象队列的尾部(tail并不是一个指针,它是一个cpu number,optimistic_spin_node对象是per-cpu的,有了cpu number就可以定位到其optimistic_spin_node对象)。

struct list_head wait_list

Mutex是睡眠锁,当无法获取锁并且不具备乐观自旋条件的时候需要挂入这个等待队列,等待锁的owner释放锁。

void *magic

和debug相关的成员

Struct lockdep_map dep_map

和debug相关的成员

大部分的成员都非常好理解,除了osq这个成员,其工作原理示意图如下:

Linux内核同步机制之(八):mutex_第2张图片

字如其名,Optimistic spin queue就是乐观自旋队列的意思,也就是形成一组处于自旋状态的任务队列。和等待队列不一样,这个队列中的任务都是当前正在执行的任务。Osq并没有直接将这些任务的task struct形成队列结构,而是把per-CPU的mcs lock对象串联形成队列。Mcs lock中有cpu number,通过这些cpu number可以定位到指定cpu上的current thread,也就定位到了自旋的任务。

虽然

你可能感兴趣的:(程序员,编程,Java,架构,java,spring,mysql)