第十九章 信号量(第二部分)
一、信号量控制块
每一个信号量都有自己的信号量控制块,信号量控制块中包含了信号量的所有信息,
1 struct rt_semaphore {
2 struct rt_ipc_object parent; /**< 继承自 ipc_object 类*/
3
4 rt_uint16_t value; /**< 信号量的值,最大为 65535 */
5 };
6 typedef struct rt_semaphore *rt_sem_t;
**rt_semaphore 对象从 rt_ipc_object 中派生,由 IPC 容器管理。 **
二、常用信号量函数
信息二值信号量的最大计数值为 1,并且都是使用 RT-Thread 的同一个释放与获取函数,所以在将信号量当二值信号量使用的时候要注意:用完信号量及时释放,并且不要调用多次信号量释放函数。
1、 信号量创建函数 rt_sem_create()
1 rt_sem_t rt_sem_create(const char *name, (1) **信号量名称。 **
2 rt_uint32_t value, (2) **可用信号量初始值。 **
3 rt_uint8_t flag) (3) **信号量标志。**
4 {
5 rt_sem_t sem;
6
7 RT_DEBUG_NOT_IN_INTERRUPT;
8
9 /* 分配内核对象 */
10 sem = (rt_sem_t)rt_object_allocate(RT_Object_Class_Semaphore, name);
11 if (sem == RT_NULL) (4) **分配消息队列对象,调用 rt_object_allocate
此函数将从对象系统分配对象,为创建的消息队列分配一个消息队列的对象,并且命名对象名称,
在系统中,对象的名称必须是唯一的。 **
12 return sem;
13
14 /* 初始化信号量对象 */
15 rt_ipc_object_init(&(sem->parent)); (5) **初始化信号量对象。
此处会初始化一个链表用于记录访问此信号量而阻塞的线程。**
16
17 /* 设置可用信号量的值 */
18 sem->value = value; (6) **设置可用信号量的初始值。
表示在创建成功的时候有多少个信号
量可用,**
19
20 /* 设置信号量模式 */
21 sem->parent.parent.flag = flag; (7) **(7):设置信号量的阻塞唤醒模式**
22
23 return sem; (8) **创建成功返回信号量句柄。 **
24 }
(5) 😗* 初始化信号量对象。此处会初始化一个链表用于记录访问此信号量而阻塞的线程
(6) 😗*设置可用信号量的初始值。表示在创建成功的时候有多少个信号
- 二值信号量,其取值范围为[0,1]
- 计数信号量,其取值范围为[0,65535]
(7) :设置信号量的阻塞唤醒模式,创建的信号量由于指定的 flag 不同,注意:
。在创建信号量的时候,是需要用户自己定义信号量的句柄的,定义了信号量的句柄并不等于创建了信号量,创建信号量必须是调用 rt_ sem_create()函数进行创建**
1 /* 定义信号量控制块 */
2 static rt_sem_t test_sem = RT_NULL;
3 /* 创建一个信号量 */
4 test_sem = rt_sem_create("test_sem",/* 信号量名字 */
5 1, /* 信号量初始值,默认有一个信号量 */
6 RT_IPC_FLAG_FIFO); /* 信号量模式 FIFO(0x00)*/
7 if (test_sem != RT_NULL)
8 rt_kprintf("信号量创建成功!\n\n");
2、 信号量删除函数 rt_sem_delete() `
信号量删除函数是根据信号量句柄直接删除的,删除之后这个信号量的所有信息都会被系统回收,并且用户无法再次使用这个信号量。
删除信号量的时候会把所有由于访问此信号量而阻塞的线程从阻塞链表中删除,并
且返回一个错误代码。
1 rt_err_t rt_sem_delete(rt_sem_t sem) //** sem 是 rt_sem_delete()传入的参数,
是信号量句柄,表示的是要删除哪个信号量**
2 {
3 RT_DEBUG_NOT_IN_INTERRUPT;
4
5 RT_ASSERT(sem != RT_NULL); (1) **:检查信号量是否被创建了,
如果是则可以进行删除操作。**
6
7 /* 恢复所有阻塞在此信号量的线程 */
8 rt_ipc_list_resume_all(&(sem->parent.suspend_thread)); (2) **将所有因为访问此信号量的而阻塞
的线程从阻塞态中恢复过来,线程得到信号量返回的错误代码**
9
10 /* 删除信号量对象 */
11 rt_object_delete(&(sem->parent.parent)); (3) **删除信号量对象并且释放信号量内核对象的内存,
释放内核对象内存在 rt_object_delete()函数中实现。 **
12
13 return RT_EOK;
14 }
15 RTM_EXPORT(rt_sem_delete);
(2):在删除的时候,应先确认所有的线程都无需再次访问此信号量,并且此时没有线程被此信号量阻塞,才进行删除操作。
调用这个函数时,系统将删除这个信号量。如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是 -
RT_ERROR)
例子:
1 /* 定义信号量控制块 */
2 static rt_sem_t test_sem = RT_NULL;
3
4 rt_err_t uwRet = RT_EOK;
5
6 uwRet = rt_sem_delete(test_sem);
7 if (RT_EOK == uwRet)
8 rt_kprintf("信号量删除成功!\n\n");
3、 信号量释放函数 rt_sem_release()
当信号量有效的时候,线程才能获取信号量,线程用好信息量后需要释放给其他线程用。
1 rt_err_t rt_sem_release(rt_sem_t sem) (1) **根据信号量句柄(sem)释放信号量。 **
2 {
3 register rt_base_t temp;
4 register rt_bool_t need_schedule;
5
6 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent)));
7
8 need_schedule = RT_FALSE; (2) **定义一个记录是否需要进行系统调度
的变量 need_schedule,默认为不需要调度。**
9
10 /* 关中断 */
11 temp = rt_hw_interrupt_disable();
12
13 RT_DEBUG_LOG(RT_DEBUG_IPC,("thread %s releases sem:%s, which value is: %d\n",
14 rt_thread_self()->name,
15 ((struct rt_object *)sem)->name,
16 sem->value));
17
18 if (!rt_list_isempty(&sem->parent.suspend_thread)) {
19 /* 恢复阻塞线程 */
20 rt_ipc_list_resume(&(sem->parent.suspend_thread)); (3) **恢复阻塞线程**
21 need_schedule = RT_TRUE; (4) **(4):恢复线程需要进行线程调度,
所以此变量应该为真**
22 } else
23 sem->value ++; /* 记录可用信号量个数 */ (5)
24
25 /* 开中断 */
26 rt_hw_interrupt_enable(temp);
27
28 /* 如果需要调度,则发起一次线程调度 */
29 if (need_schedule == RT_TRUE) (6) **:如果需要进行调度,
则调用 rt_schedule()函数进行一次线程切换。 **
30 rt_schedule();
31
32 return RT_EOK;
33 }
34 RTM_EXPORT(rt_sem_release);
(3):恢复阻塞线程。如果当前有线程等待这个信号量时,那现在进行信号量释放的时候,将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量,并且将其从阻塞中恢复。
恢复的过程是:将线程从阻塞列表中删除,添加到就绪列表中。
因此,当线程完成资源的访问后,应尽快释放它持有的信号量,使得其他线程能获得该信号量
注意:在中断中一样可以这样子调用信号量释放函数 rt_sem_release(),因为这个函数是非阻塞的。
例子
1 static void send_thread_entry(void* parameter)
2 {
3 rt_err_t uwRet = RT_EOK;
4 /* 线程都是一个无限循环,不能返回 */
5 while (1) { //如果 KEY2 被单击
6 if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
7 /* 释放一个计数信号量 */
8 uwRet = rt_sem_release(test_sem);
9 if ( RT_EOK == uwRet )
10 rt_kprintf ( "KEY2 被单击:释放 1 个停车位。\r\n" );
11 else
12 rt_kprintf ( "KEY2 被单击:但已无车位可以释放!\r\n" );
13 }
14 rt_thread_delay(20); //每 20ms 扫描一次
15 }
16 }
4、 信号量获取函数 rt_sem_take()
当信号量有效的时候,线程才能获取信号量,当线程获取了某个信号量的时候,该信号量的有效值就会减一,也就是说该信号量的可用个数就减一,当它减到 0 的时候,线程就无法再获取了,并且获取的线程会进入阻塞态(假如使用了等待时间的话)
每调用一次 rt_sem_take()函数获取信号量的时候,信号量的可用个数便减少一个,直至为 0 的时候,线程就无法成功获取信号量了
1 rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time) (1)
**sem 信号量对象的句柄;time 指定的等待时间,单位是操作系统时钟节拍(tick)。**
2 {
3 register rt_base_t temp;
4 struct rt_thread *thread;
5
6 RT_ASSERT(sem != RT_NULL); (2)
**检查信号量是否有效,如果有效则进行获取操作。**
7
8 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent)));
9
10 /* 关中断 */
11 temp = rt_hw_interrupt_disable();
12
13 RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s take sem:%s, which value is: %d\n",
14 rt_thread_self()->name,
15 ((struct rt_object *)sem)->name,
16 sem->value));
17
18 if (sem->value > 0) { (3)
**如果当前有可用的信号量,那么线程获取信号量成功,信号量可用个数减一,
然后直接跳到(11) 返回成功。 **
19 /* 有可用信号量 */
20 sem->value --;
21
22 /* 关中断 */
23 rt_hw_interrupt_enable(temp);
24 } else {
25 /* 不等待,返回超时错误 */
26 if (time == 0) { (4)
**(4)~(10)都是表示当前没有可用信号量,此时无法获取到信号量,
如 果 用 户 设 定 的 等 待 时间 为 0 , 那 么 线 程 获 取信 号 量 不 成 功 ,
直 接 返回 错 误 码 -RT_ETIMEOUT。**
27 rt_hw_interrupt_enable(temp);
28
29 return -RT_ETIMEOUT;
30 } else {
31 /*当前上下文检查 */
32 RT_DEBUG_IN_THREAD_CONTEXT;
33
34 /* 信号不可用,挂起当前线程 */
35 /* 获取当前线程 */
36 thread = rt_thread_self(); (5)
**如果用户设置了等待时间,那么在获取不到信号量的情况下,可
以将获取信号量的线程挂起,进行等待,这首先获取到当前线程,调用 rt_thread_self()函数
就是为了得到当前线程控制块。 **
37
38 /* 设在线程错误代码 */
39 thread->error = RT_EOK;
40
41 RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\n",
42 thread->name));
43
44 /* 挂起线程 */
45 rt_ipc_list_suspend(&(sem->parent.suspend_thread), (6) **:将线程挂起,rt_ipc_list_suspend()此函数将线程挂起到指定列表。 **
46 thread,
47 sem->parent.parent.flag);
48
49 /* 有等待时间,开始计时 */
50 if (time > 0) { (7) **如果有等待时间,那么需要计时,在时间到的时候恢复线程。 **
51 RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
52 thread->name));
53
54 /* 设置线程超时时间,并且启动定时器 */
55 rt_timer_control(&(thread->thread_timer), (8) **调用 rt_timer_control()函数设置当前线程的挂起的时间,时间 time
由用户设定。**
56 RT_TIMER_CTRL_SET_TIME,
57 &time);
58 rt_timer_start(&(thread->thread_timer)); (9) **启动定时器开始计时。 **
59 }
60
61 /* 开中断 */
62 rt_hw_interrupt_enable(temp);
63
64 /* 发起线程调度 */
65 rt_schedule(); (10)
66
67 if (thread->error != RT_EOK) {
68 return thread->error;
69 }
70 }
71 }
72
73 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent)));
74
75 return RT_EOK; (11)
76 }
77 RTM_EXPORT(rt_sem_take);
例子:
1 rt_sem_take(test_sem, /* 获取信号量 */
2 RT_WAITING_FOREVER); /* 等待时间:一直等 */
3
4 uwRet = rt_sem_take(test_sem, /* 获取一个计数信号量 */
5 0); /* 等待时间:0 */
6 if ( RT_EOK == uwRet )
7 rt_kprintf( "获取信号量成功\r\n" );
来源:CSDN
作者:无脸男&巧巧
链接:https://blog.csdn.net/qq_41070511/article/details/103642412