第二十章 互斥量2
五、互斥量函数
1、互斥量控制块
每一个互斥量都有自己的互斥量控制块,互斥量控制块中包含了互斥量的所有信息,
1 struct rt_mutex {
2 struct rt_ipc_object parent; (1) **可以将互斥量挂到系统对象容器里面**
3
4 rt_uint16_t value; (2)**互斥量的值。初始状态下互斥量的值为 1**
5
6 rt_uint8_t original_priority; (3)**当前持有互斥量的线程**
**持有互斥量线程的原始优先级,用来做优先级继承的保存。 **
7 rt_uint8_t hold; (4)
**持有互斥量的线程的持有次数,用于记录线程递归调用了多少次
获取互斥量。 **
8
9 struct rt_thread *owner; (5) **当前持有互斥量的线程。 **
10 };
11 typedef struct rt_mutex *rt_mutex_t;
2、互斥量创建函数 rt_mutex_create()
创建的时候需要我们自己定义互斥量句柄,
1 rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
2 {
3 struct rt_mutex *mutex;
4
5 RT_DEBUG_NOT_IN_INTERRUPT;
6
7 /* 分配对象 */
8 mutex = (rt_mutex_t)rt_object_allocate(RT_Object_Class_Mutex, name);
9 if (mutex == RT_NULL) (1) **分配互斥量对象**
10 return mutex;
11
12 /* 初始化 IPC 对象 */
13 rt_ipc_object_init(&(mutex->parent)); (2)
**初始化互斥量内核对象。调用 rt_ipc_object_init 会初始化一个链表
用于记录访问此互斥量而阻塞的线程。 **
14
15 mutex->value = 1; (3) **将互斥量的值初始化为 1, **
16 mutex->owner = RT_NULL; (4)
**初始化持有互斥量线程为 RT_NULL,因为现在是创建互斥量,肯
定没有线程持有该互斥量。 **
17 mutex->original_priority = 0xFF; (5) **持有互斥量线程的原始优先级默认为 0xFF,在获取的时候这个值
就会被重置为获取线程的优先级。 **
18 mutex->hold = 0; (6) **持有互斥量的线程的持有次数为 0。 **
19
20 /* 设置互斥量的等待模式 */
21 mutex->parent.parent.flag = flag; (7) **设置互斥量的阻塞唤醒模式,**
22
23 return mutex; (8) **返回互斥量句柄。**
24 }
25 RTM_EXPORT(rt_mutex_create);
(7):设置互斥量的阻塞唤醒模式
例子:
1 /* 定义互斥量控制块 */
2 static rt_mutex_t test_mux = RT_NULL;
3 /* 创建一个互斥量 */
4 test_mux = rt_mutex_create("test_mux",RT_IPC_FLAG_PRIO);
5
6 if (test_mux != RT_NULL)
7 rt_kprintf("互斥量创建成功!\n\n");
3、互斥量删除函数 rt_mutex_delete()
删除互斥量的时候会把所有阻塞在互斥量的线程唤醒,被唤醒的线程则会得到一个错误码-RT_ERROR
1 rt_err_t rt_mutex_delete(rt_mutex_t mutex) ** mutex 是互斥量句柄**
2 {
3 RT_DEBUG_NOT_IN_INTERRUPT;
4
5 RT_ASSERT(mutex != RT_NULL); (1) **检查互斥量是否被创建了,如果是则可以进行删除操作。 **
6
7 /* 解除所有挂起线程 */
8 rt_ipc_list_resume_all(&(mutex->parent.suspend_thread)); (2)
**所有因为访问此互斥量的而阻塞的线程从阻塞态中恢复过来,
线程将得到互斥量返回的错误代码**
9
10 /* 删除互斥量对象 */
11 rt_object_delete(&(mutex->parent.parent)); (3) **删除互斥量对象并且释放互斥量内核对象的内存**
12
13 return RT_EOK;
14 }
15 RTM_EXPORT(rt_mutex_delete);
当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是-RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间
例子:
1 /* 定义消息队列控制块 */
2 static rt_mutex_t test_mutex = RT_NULL;
3
4 rt_err_t uwRet = RT_EOK;
5
6 uwRet = rt_mutex_delete(test_mutex);
7 if (RT_EOK == uwRet)
8 rt_kprintf("互斥量删除成功!\n\n");
4、互斥量释放函数 rt_mutex_release()
1 rt_err_t rt_mutex_release(rt_mutex_t mutex) (1) **根据互斥量句柄释放互斥量**
2 {
3 register rt_base_t temp;
4 struct rt_thread *thread;
5 rt_bool_t need_schedule;
6
7 need_schedule = RT_FALSE;
8
9 /*只有持有的线程可以释放互斥量,因为需要测试互斥量的所有权 */
10 RT_DEBUG_IN_THREAD_CONTEXT;
11
12 /* 获取当前线程 */
13 thread = rt_thread_self(); (2) **:获取当前线程。只有已持有互斥量所有权的线程才能释放它,不然的话,会出现问题,**
14
15 /* 关中断 */
16 temp = rt_hw_interrupt_disable();
17
18 RT_DEBUG_LOG(RT_DEBUG_IPC,
19 ("mutex_release:current thread %s, mutex value: %d, hold: %d\n",
20 thread->name, mutex->value, mutex->hold));
21
22 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));
23
24 /* 互斥量只能被持有者释放 */
25 if (thread != mutex->owner) { (3)
**判断当前线程与互斥量持有者是不是同一个线程,只有互斥量持
有者才能释放互斥量,如果不是则返回错误代码-RT_ERROR。 **
26 thread->error = -RT_ERROR;
27
28 /* 开中断 */
29 rt_hw_interrupt_enable(temp);
30
31 return -RT_ERROR;
32 }
33
34 /* 减少持有量 */
35 mutex->hold --; (4)
**如果当前线程是该互斥量的持有者,那么互斥量可以被释放,将持有量减一。**
36 /* 如果没有持有量了 */
37 if (mutex->hold == 0) { (5)
**如果线程释放了互斥量,则会进行(5)-(15)操作。 **
38 /* 将持有者线程更改为原始优先级 */
39 if (mutex->original_priority != mutex->owner->current_priority) {
40 rt_thread_control(mutex->owner,
41 RT_THREAD_CTRL_CHANGE_PRIORITY,
42 &(mutex->original_priority)); (6)
**如果当前线程初始设置的优先级与互斥量保存的优先级不一样,
那么则要恢复线程初始化设定的优先级**
43 }
44
45 /* 唤醒阻塞线程 */
46 if (!rt_list_isempty(&mutex->parent.suspend_thread)) { (7) **如果有由于获取不到互斥量而进入阻塞的线程,那么此时互斥量
为开锁状态就需要将这些线程唤醒,执行(8)-(12) 操作。 **
47 /* 获取阻塞线程 */
48 thread = rt_list_entry(mutex->parent.suspend_thread.next,
49 struct rt_thread,
50 tlist); (8) **获取当前被阻塞的线程**
51
52 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_release: resume thread: %s\n",
53 thread->name));
54
55 /* 设置新的持有者线程与其优先级 */
56 mutex->owner = thread; (9)
**设置互斥量持有者为从阻塞中恢复的新线程,将 mutex->owner 执
行新线程的线程控制块。 **
57 mutex->original_priority = thread->current_priority; (10) **保存新持有互斥量线程的优先级。 **
58 mutex->hold ++; (11) **持有互斥量次数加一。 **
59
60 /* 恢复线程 */
61 rt_ipc_list_resume(&(mutex->parent.suspend_thread)); (12) **恢复被阻塞的线程,并且需要进行一次线程调度,然后执行 (16),进行线程调度。**
62
63 need_schedule = RT_TRUE;
64 } else {
65 /* 记录增加 value 的值 */
66 mutex->value ++; (13)
**如果释放了信号量,但此时没有线程被阻塞的话,将互斥量的值
加一,表示此时互斥量处于开锁状态,其他线程可以获取互斥量。 **
67
68 /* 清除互斥量信息 */
69 mutex->owner = RT_NULL; (14) **:持有互斥量的线程优先级恢复默认 0xff。 **
70 mutex->original_priority = 0xff; (15) **进行一次线程调度。 **
71 }
72 }
73
74 /* 开中断 */
75 rt_hw_interrupt_enable(temp);
76
77 /* 执行一次线程调度 */
78 if (need_schedule == RT_TRUE)
79 rt_schedule(); (16)
80
81 return RT_EOK;
82 }
83 RTM_EXPORT(rt_mutex_release);
使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。
如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。
例子:
1 /* 定义消息队列控制块 */
2 static rt_mutex_t test_mutex = RT_NULL;
3
4 rt_err_t uwRet = RT_EOK;
5
6 uwRet = rt_mutex_release(test_mutex);
7 if (RT_EOK == uwRet)
8 rt_kprintf("互斥量释放成功!\n\n");
5、互斥量获取函数 rt_mutex_take()
程 通 过 互 斥 量rt_mutex_take()函数获取互斥量的所有权。
线程对互斥量的所有权是独占的,任意时刻互斥量只能被一个线程持有,如果互斥量处于开锁状态,那么获取该互斥量的线程将成功获得该互斥量,并拥有互斥量的使用权;如果互斥量处于闭锁状态,获取该互斥量的线程将无法获得互斥量,线程将被挂起,直到持有互斥量线程释放它
而如果线程本身就持有互斥量,再去获取这个互斥量却不会被挂起,只是将该互斥量的持有值加 1,
1 rt_err_t rt_mutex_take(rt_mutex_t mutex, (1) **mutex 是互斥量对象句柄,在使用获取信号量之前必须先创建互斥
量。**
2 rt_int32_t time) (2) **time 是指定等待的时间。 **
3 {
4 register rt_base_t temp;
5 struct rt_thread *thread;
6
7 /* 即使 time = 0,也不得在中断中使用此功能 */
8 RT_DEBUG_IN_THREAD_CONTEXT;
9
10 RT_ASSERT(mutex != RT_NULL); (3) **判断互斥量是否有效。**
11
12 /* 获取当前线程 */
13 thread = rt_thread_self(); (4) **获取当前线程。系统要知道是哪个线程获取互斥量。 **
14
15 /* 关中断 */
16 temp = rt_hw_interrupt_disable(); (5) **关中断,防止下面的操作被打断。 **
17
18 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));
19
20 RT_DEBUG_LOG(RT_DEBUG_IPC,
21 ("mutex_take: current thread %s, mutex value: %d, hold: %d\n",
22 thread->name, mutex->value, mutex->hold));
23
24 /* 设置线程错误码 */
25 thread->error = RT_EOK;
26
27 if (mutex->owner == thread) {
28 /* 如果是同一个线程 */
29 mutex->hold ++; (6) **如果持有互斥量的线程与当前获取互斥量的线程是同一个线程的话,
则不会发生阻塞,将互斥量的持有次数加一**
30 } else {
31 __again:
32 /*
33 * 初始状态下互斥量的值为 1。 因此,如果该值大于 0,则表示可以使用互斥量。
34 */
35 if (mutex->value > 0) { (7) **:如果线程能获取到互斥量,则执行(7)-(10) 操作。初始状态下互
斥量 value 的值为 1。**
36 /* 互斥量可用 */
37 mutex->value --;
38
39 /* 记录申请互斥量的线程与它的初始化优先级 */
40 mutex->owner = thread; (8)
**记录当前获取到互斥量的线程信息,重置 owner 指向当前线程**
41 mutex->original_priority = thread->current_priority; (9) **记录当前获取到互斥量的线程的优先级信息**
42 mutex->hold ++; (10) **互斥量的持有次数加一。 **
43 } else {
44 /* 如果不等待,返回超时错误代码*/
45 if (time == 0) { (11)
**:如果没有申请到互斥量,执行(11)-(21) 操作。如果不设置等待
时间,则直接返回错误码。**
46 /* 设置线程错误码 */
47 thread->error = -RT_ETIMEOUT;
48
49 /* 开中断 */
50 rt_hw_interrupt_enable(temp);
51
52 return -RT_ETIMEOUT;
53 } else { (12)
**如果没有申请到,但是设置了等待时间,那么可以根据等待时间将线程挂起。 **
54 /* 互斥量不可用, 挂起线程 */
55 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",
56 thread->name));
57
58 /* 判断申请互斥量线程与持有互斥量线程的优先级关系 */
59 if (thread->current_priority < mutex->owner->current_priority) {
60 /* 改变持有互斥量的线程优先级 */ (13)
**判断当前线程与持有互斥量线程的优先级关系,如果当前线程优
先级比持有互斥量线程优先级高,这时候已经发生了优先级翻转了,
因为互斥量的原因,高优先级的线程(当前线程)被阻塞着,
所以,需要进行优先级继承操作。 **
61 rt_thread_control(mutex->owner,
62 RT_THREAD_CTRL_CHANGE_PRIORITY,
63 &thread->current_priority); (14) **发生优先级翻转,需要暂时改变持有互斥量的线程优先级,将其
优先级暂时提高到当前线程的优先级。 **
64 }
65
66 /* 挂起当前线程 */
67 rt_ipc_list_suspend(&(mutex->parent.suspend_thread),
68 thread,
69 mutex->parent.parent.flag); (15) **挂起当前线程,进行等待**
70
71 /* 有等待时间,开始计时 */
72 if (time > 0) { (16) **有等待时间,开始计时。 **
73 RT_DEBUG_LOG(RT_DEBUG_IPC,
74 ("mutex_take: start the timer of thread:%s\n",
75 thread->name));
76
77 /* 重置线程计时器的超时时间并启动它 */
78 rt_timer_control(&(thread->thread_timer),
79 RT_TIMER_CTRL_SET_TIME,
80 &time); (17)
**重置线程计时器的超时时间。 **
81 rt_timer_start(&(thread->thread_timer)); (18) **启动定时器。 **
82 }
83
84 /* 开中断 */
85 rt_hw_interrupt_enable(temp);
86
87 /* 发起线程调度 */
88 rt_schedule(); (19) **发起线程调度。 **
89
90 if (thread->error != RT_EOK) {
91 /*再试一次 */
92 if (thread->error == -RT_EINTR) goto __again; (20)**回到__again。 **
93
94 /* 返回错误代码 */
95 return thread->error;
96 } else {
97 /* 获取信号量成功 */
98 /* 关中断*/
99 temp = rt_hw_interrupt_disable(); (21) **获取信号量成功。 **
100 }
101 }
102 }
103 }
104
105 /* 开中断 */
106 rt_hw_interrupt_enable(temp);
107
108 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mutex->parent.parent)));
109
110 return RT_EOK;
111 }
112 RTM_EXPORT(rt_mutex_take);
例子
1 /* 定义消息队列控制块 */
2 static rt_mutex_t test_mutex = RT_NULL;
3
4 rt_err_t uwRet = RT_EOK;
5
6 rt_mutex_take(test_mux, /* 获取互斥量 */
7 RT_WAITING_FOREVER); /* 等待时间:一直等 */
8 if (RT_EOK == uwRet)
9 rt_kprintf("互斥量获取成功!\n\n");
六、使用注意事项
使用互斥量时候需要注意几点:
- 两个线程不能对同时持有同一个互斥量。如果某线程对已被持有的互斥量进行获
取,则该线程会被挂起,直到持有该互斥量的线程将互斥量释放成功,其他线程
才能申请这个互斥量。 - 互斥量不能在中断服务程序中使用。
- RT-Thread 作为实时操作系统需要保证线程调度的实时性,尽量避免线程的长时
间阻塞,因此在获得互斥量之后,应该尽快释放互斥量。 - 持有互斥量的过程中,不得再调用 rt_thread_control()等函数接口更改持有互斥量
线程的优先级。