互斥量
互斥量又叫相互排斥的信号量,是一种特殊的二值性信号量。它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性。互斥量工作如 互斥量的工作示意图 所示。
互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起。这个特性与一般的二值信号量有很大的不同,在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。
使用信号量会导致的另一个潜在问题是线程优先级翻转。所谓优先级翻转问题即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。例如:有优先级为A、B和C的三个线程,优先级A> B > C。线程A,B处于挂起状态,等待某一事件触发,线程C正在运行,此时线程C开始使用某一共享资源M。在使用过程中,线程A等待的事件到来,线程A转为就绪态,因为它比线程C优先级高,所以立即执行。但是当线程A要使用共享资源M时,由于其正在被线程C使用,因此线程A被挂起切换到线程C运行。如果此时线程B等待的事件到来,则线程B转为就绪态。由于线程B的优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行。只有当线程C释放共享资源M后,线程A才得以执行。在这种情况下,优先级发生了翻转,线程B先于线程A运行。这样便不能保证高优先级线程的响应时间。
在RT-Thread操作系统中实现的是优先级继承算法。优先级继承是通过在线程A被阻塞的期间内,将线程C的优先级提升到线程A的优先级别,从而解决优先级翻转引起的问题。这样能够防止C(间接地防止A)被B抢占。优先级继承协议是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。
互斥量控制块的数据结构
- struct rt_mutex
- {
- struct rt_ipc_object parent; /* 继承自ipc_object类 */
- rt_uint16_t value; /* 互斥量的值 */
- rt_uint8_t original_priority; /* 持有线程的原始优先级 */
- rt_uint8_t hold; /* 持有线程的持有次数 */
- struct rt_thread *owner; /* 当前拥有互斥量的线程 */
- };
- /* rt_mutext_t为指向互斥量结构体的指针 */
- typedef struct rt_mutex* rt_mutex_t;
rt_mutex对象从rt_ipc_object中派生,由IPC容器管理。
互斥量相关接口
创建互斥量
创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作。创建互斥量使用下面的函数接口:
- rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);
可以调用rt_mutex_create函数创建一个互斥量,它的名字有name所指定。创建的互斥量由于指定的flag不同,而有不同的意义:使用PRIO优先级flag创建的IPC对象,在多个线程等待资源时,将由优先级高的线程优先获得资源。而使用FIFO先进先出flag创建的IPC对象,在多个线程等待资源时,将按照先来先得的顺序获得资源。
函数参数
- 参数 描述
- name 互斥量的名称;
- flag 互斥量标志,可以取如下类型的数值:
- #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO先进先出方式*/
- #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
函数返回
创建成功返回指向互斥量的互斥量句柄;否则返回RT_NULL。
删除互斥量
系统不再使用互斥量时,通过删除互斥量以释放系统资源。删除互斥量使用下面的函数接口:
- rt_err_t rt_mutex_delete (rt_mutex_t mutex);
当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是-RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。
函数参数
- 参数 描述
- mutex 互斥量对象的句柄;
函数返回
RT_EOK
初始化互斥量
静态互斥量对象的内存是在系统编译时由编译器分配的,一般放于数据段或ZI段中。在使用这类静态互斥量对象前,需要先进行初始化。初始化互斥量使用下面的函数接口:
- rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag);
使用该函数接口时,需指定互斥量对象的句柄(即指向互斥量控制块的指针),互斥量名称以及互斥量标志。互斥量标志可用上面创建互斥量函数里提到的标志。
函数参数
- 参数 描述
- mutex 互斥量对象的句柄,它由用户提供,并指向互斥量对象的内存块;
- name 互斥量名称;
- flag 互斥量标志,可以取如下类型的数值:
- #define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO先进先出方式*/
- #define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
函数返回
RT_EOK
脱离互斥量
脱离互斥量将把互斥量对象从内核对象管理器中删除。脱离互斥量使用下面的函数接口:
- rt_err_t rt_mutex_detach (rt_mutex_t mutex);
使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是-RT_ERROR),然后系统将该互斥量从内核对象管理器链表中删除。
函数参数
- 参数 描述
- mutex 互斥量对象的句柄;
函数返回
RT_EOK
获取互斥量
线程通过互斥量申请服务获取互斥量的所有权。线程对互斥量的所有权是独占的,某一个时刻一个互斥量只能被一个线程持有。获取互斥量使用下面的函数接口:
- rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);
如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。
函数参数
- 参数 描述
- mutex 互斥量对象的句柄;
- time 指定等待的时间。
函数返回
成功获得互斥量返回RT_EOK;超时返回-RT_ETIMEOUT;其他返回-RT_ERROR。
释放互斥量
当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。释放互斥量使用下面的函数接口:
- rt_err_t rt_mutex_release(rt_mutex_t mutex);
使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。
函数参数
- 参数 描述
- mutex 互斥量对象的句柄;
函数返回
RT_EOK
使用互斥量的例程如下例所示:
- /*
- * 程序清单:互斥量使用例程
- *
- * 这个例子将创建3个动态线程以检查持有互斥量时,持有的线程优先级是否
- * 被调整到等待线程优先级中的最高优先级。
- *
- * 线程1,2,3的优先级从高到低分别被创建,
- * 线程3先持有互斥量,而后线程2试图持有互斥量,此时线程3的优先级应该
- * 被提升为和线程2的优先级相同。线程1用于检查线程3的优先级是否被提升
- * 为与线程2的优先级相同。
- */
- #include <rtthread.h>
- #include "tc_comm.h"
- /* 指向线程控制块的指针 */
- static rt_thread_t tid1 = RT_NULL;
- static rt_thread_t tid2 = RT_NULL;
- static rt_thread_t tid3 = RT_NULL;
- static rt_mutex_t mutex = RT_NULL;
- /* 线程1入口 */
- static void thread1_entry(void* parameter)
- {
- /* 先让低优先级线程运行 */
- rt_thread_delay(10);
- /* 此时thread3持有mutex,并且thread2等待持有mutex */
- /* 检查thread2与thread3的优先级情况 */
- if (tid2->current_priority != tid3->current_priority)
- {
- /* 优先级不相同,测试失败 */
- tc_stat(TC_STAT_END | TC_STAT_FAILED);
- return;
- }
- }
- /* 线程2入口 */
- static void thread2_entry(void* parameter)
- {
- rt_err_t result;
- /* 先让低优先级线程运行 */
- rt_thread_delay(5);
- while (1)
- {
- /*
- * 试图持有互斥锁,此时thread3持有,应把thread3的优先级提升
- * 到thread2相同的优先级
- */
- result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
- if (result == RT_EOK)
- {
- /* 释放互斥锁 */
- rt_mutex_release(mutex);
- }
- }
- }
- /* 线程3入口 */
- static void thread3_entry(void* parameter)
- {
- rt_tick_t tick;
- rt_err_t result;
- while (1)
- {
- result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
- result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
- if (result != RT_EOK)
- {
- tc_stat(TC_STAT_END | TC_STAT_FAILED);
- }
- /* 做一个长时间的循环,总共50个OS Tick */
- tick = rt_tick_get();
- while (rt_tick_get() - tick < 50) ;
- rt_mutex_release(mutex);
- rt_mutex_release(mutex);
- }
- }
- int mutex_simple_init()
- {
- /* 创建互斥锁 */
- mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO);
- if (mutex == RT_NULL)
- {
- tc_stat(TC_STAT_END | TC_STAT_FAILED);
- return 0;
- }
- /* 创建线程1 */
- tid1 = rt_thread_create("t1",
- thread1_entry, /* 线程入口是thread1_entry */
- RT_NULL, /* 入口参数是RT_NULL */
- THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
- if (tid1 != RT_NULL)
- rt_thread_startup(tid1);
- else
- tc_stat(TC_STAT_END | TC_STAT_FAILED);
- /* 创建线程2 */
- tid2 = rt_thread_create("t2",
- thread2_entry, /* 线程入口是thread2_entry */
- RT_NULL, /* 入口参数是RT_NULL */
- THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
- if (tid2 != RT_NULL)
- rt_thread_startup(tid2);
- else
- tc_stat(TC_STAT_END | TC_STAT_FAILED);
- /* 创建线程3 */
- tid3 = rt_thread_create("t3",
- thread3_entry, /* 线程入口是thread3_entry */
- RT_NULL, /* 入口参数是RT_NULL */
- THREAD_STACK_SIZE, THREAD_PRIORITY + 1, THREAD_TIMESLICE);
- if (tid3 != RT_NULL)
- rt_thread_startup(tid3);
- else
- tc_stat(TC_STAT_END | TC_STAT_FAILED);
- return 0;
- }
- #ifdef RT_USING_TC
- static void _tc_cleanup()
- {
- /* 调度器上锁,上锁后,将不再切换到其他线程,仅响应中断 */
- rt_enter_critical();
- /* 删除线程 */
- if (tid1 != RT_NULL && tid1->stat != RT_THREAD_CLOSE)
- rt_thread_delete(tid1);
- if (tid2 != RT_NULL && tid2->stat != RT_THREAD_CLOSE)
- rt_thread_delete(tid2);
- if (tid3 != RT_NULL && tid3->stat != RT_THREAD_CLOSE)
- rt_thread_delete(tid3);
- if (mutex != RT_NULL)
- {
- rt_mutex_delete(mutex);
- }
- /* 调度器解锁 */
- rt_exit_critical();
- /* 设置TestCase状态 */
- tc_done(TC_STAT_PASSED);
- }
- int _tc_mutex_simple()
- {
- /* 设置TestCase清理回调函数 */
- tc_cleanup(_tc_cleanup);
- mutex_simple_init();
- /* 返回TestCase运行的最长时间 */
- return 100;
- }
- /* 输出函数命令到finsh shell中 */
- FINSH_FUNCTION_EXPORT(_tc_mutex_simple, sime mutex example);
- #else
- /* 用户应用入口 */
- int rt_application_init()
- {
- mutex_simple_init();
- return 0;
- }
- #endif
使用场合
互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于:
- 线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题;
- 可能会由于多线程同步而造成优先级翻转的情况;
另外需要切记的是互斥量不能在中断服务例程中使用。