互斥量

互斥量(mutex) 是一个内核对象,它实现了一个传统的可重入互斥量。互斥量允许多个线程安全地共享一个关联的软件或者硬件资源。

概念

可以定义任意数量的互斥量。每个互斥量通过其内存地址进行引用。

互斥量的关键属性如下:

  • 锁计数:表示锁定该互斥量的线程对该互斥量锁定的次数。0 表示该互斥量没有被锁定。
  • 拥有线程: 用来标识当互斥量被锁定时锁定该互斥量的线程。

互斥量必须先初始化再使用。初始化时会将其锁计数设为 0。

当一个线程想使用共享资源时,它必须先通过 锁定 关联的互斥量以获得专有的访问权限。如果该互斥量已被另一个线程锁定,请求线程可以等待该互斥量被解锁。

锁定互斥量后,线程可以长时间地安全地使用相关联的资源;不过,时间尽可能短地持有互斥量总是一个好的实践做法,因为它能尽量避免对其它需要使用这些资源的线程造成影响。当线程不再需要使用资源时,它必须将互斥量 解锁,以允许其它线程可以使用该资源。

多个线程可以同时等待某个被锁定的互斥量。当该互斥量被解锁后,它会被优先级最高的、等待时间最久的线程所使用。

注解

互斥量对象 不是 为 ISR 设计的。

可重入锁(Reentrant Locking)

线程可以锁定一个它已经锁定的互斥量。这样做的好处是线程可以在它执行的某个时刻(互斥量可能被锁定也可能未被锁定)访问该互斥量所关联的资源。

互斥量被一个线程多次锁定后,它必须被解锁相同的次数后才能被其它线程所获取到。

优先级继承

已锁定互斥量的线程具有:dfn:优先级继承(priority inheritance) 的能力。这意味着,如果有一个高优先级的线程开始等待这个互斥量,内核将 临时 提升该线程的优先级。这样做的好处是,占用互斥量的线程可以以与等待线程相同的优先级继续执行而不会被其抢占,因此可以更快速地执行它的工作并释放互斥量。互斥量一旦被解锁后,该线程的优先级会被恢复至锁定该互斥量前的优先级。

注解

内核由于优先级继承而提升线程的优先级时,配置选项 CONFIG_PRIORITY_CEILING 会限制其所能提升的最大优先级。默认值 0 允许内核可以对其进行无限制的提升。

当两个或多个线程等待一个被低优先级锁定的互斥量时,内核会在这些线程每次开始等待(或者放弃等待)时调整互斥量占用线程的优先级。当这个互斥量最终被解锁后,解锁的线程的优先级会被恢复到它原先未被提升时的优先级。

当一个线程同时占用了两个或者多个互斥量时,内核 不会 完全支持优先级继承。这种情形会导致当所有的互斥量被释放后该线程的优先级不能恢复到它原先未被提升时的优先级。因此,当多个互斥量在不同的优先级的线程之间共享时,建议每个线程在同一时刻只锁定一个互斥量。

实现

定义互斥量

使用类型为 struct k_mutex 的变量可以定义互斥量。互斥量定以后必须使用函数 k_mutex_init() 对其进行初始化。

下面的代码定义并初始化了一个互斥量。

struct k_mutex my_mutex;

k_mutex_init(&my_mutex);

也可以使用宏 K_MUTEX_DEFINE 在编译时定义并初始化一个互斥量。

下面的代码与上面的代码段具有系统的效果。

K_MUTEX_DEFINE(my_mutex);

锁定互斥量

函数 k_mutex_lock() 用于锁定互斥量。

下面的代码基于上面的例程之上。如果该互斥量已被另一个线程锁定,则会等待一段不确定的时间,直到互斥量有效。

k_mutex_lock(&my_mutex, K_FOREVER);

下面的代码会最多等待 100 毫秒。如果互斥量依然无效,将打印一条警告消息。

if (k_mutex_lock(&my_mutex, K_MSEC(100)) == 0) {
    /* mutex successfully locked */
} else {
    printf("Cannot lock XYZ display\n");
}

解锁互斥量

函数 k_mutex_unlock() 用于解锁互斥量。

下面的代码基于上面的例程之上,它会对线程所锁定的互斥量进行解锁。

k_mutex_unlock(&my_mutex);

建议的用法

使用互斥量提供对资源(例如物理设备)的专有访问。

配置选项

相关配置选项:

  • CONFIG_PRIORITY_CEILING

API

头文件 kernel.h 提供了如下的互斥量 API:

  • K_MUTEX_DEFINE
  • k_mutex_init()
  • k_mutex_lock()
  • k_mutex_unlock()