文章目录
- 一、信号量的特性
- 1.1 使用场景
- 1.2 什么是信号量
- 1.3 信号量和队列的区别
- 1.4 两种信号量的对比
- 二、二值信号量/计数信号量
- 2.1 什么是二值信号量
- 2.2 什么是计数信号量
- 2.2 (二值信号量/计数信号量) 相关API
- 三、互斥量(mutex)
- 3.2 什么是优先级翻转
- 3.3 互斥量的使用场合
- 3.4 互斥量API
- 四、递归锁
- 4.1 死锁的概念
- 4.2 自我死锁
- 4.3 API函数
- 五、注意事项
一、信号量的特性
信号量分为以下几种
- 二值信号量
- 计数信号量
- 互斥信号量
- 递归互斥信号量
1.1 使用场景
前面介绍的队列(queue)可以用于传输数据:在任务之间、任务和中断之间。 有时候我们只需要传递状态,并不需要传递具体的信息,比如:
- 我的事做完了,通知一下你
- 卖包子了、卖包子了,做好了1个包子!做好了2个包子!做好了3个包子!
- 这个停车位我占了,你们只能等着
在这种情况下我们可以使用 信号量(semaphore)
,它更节省内存。
1.2 什么是信号量
信号量(Semaphore)
,是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代码段不被并发调用。
信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还可以用来表示资源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有两个状态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。
信号量也是队列的一种
1.3 信号量和队列的区别
队列 | 信号量 |
---|---|
可以容纳多个数据, 创建队列时有2部分内存: 队列结构体、存储数 据的空间 | 只有计数值,无法容纳其他数据。 创建信号量时,只需要分配信号量结构体 |
生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值已经达到最大时 返回失败 |
消费者:没有数据时可以阻塞 | 消费者:没有资源时可以阻塞 |
1.4 两种信号量的对比
信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最 大值不是1,它就是计数型信号量。 差别列表如下:
二值信号量 | 计数信号量 |
---|---|
被创建时初始值为0 | 被创建时初始值可以设定 |
其他操作是一样的 | 其他操作是一样的 |
二、二值信号量/计数信号量
2.1 什么是二值信号量
二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我们用它来进行互斥访问或任务同步。
互斥访问:比如门钥匙,只有获取到钥匙才可以开门。
任务同步:比如我录完视频你才可以看视频。
2.2 什么是计数信号量
计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。
2.2 (二值信号量/计数信号量) 相关API
-
创建
使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:
创建方式 二值信号量 计数信号量 动态创建 xSemaphoreCreateBinary xSemaphoreCreateCounting 静态创建 xSemaphoreCreateBinaryStatic xSemaphoreCreateBinaryStatic 创建二进制信号量的函数原型如下:
/* 创建一个二进制信号量,返回它的句柄。 * 此函数内部会分配信号量结构体 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateBinary( void ); /* 创建一个二进制信号量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
创建计数型信号量的函数原型如下:
/* 创建一个计数型信号量,返回它的句柄。 * 此函数内部会分配信号量结构体 * uxMaxCount: 最大计数值 * uxInitialCount: 初始计数值 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount); /* 创建一个计数型信号量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * uxMaxCount: 最大计数值 * uxInitialCount: 初始计数值 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
-
删除
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。 vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:
/* * xSemaphore: 信号量句柄,你要删除哪个信号量 */ void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
-
获取/释放**( give/take)**
操作方式 任务中使用在任务中使用 中断中使用在中断中使用 释放信号量(give) xSemaphoreGive xSemaphoreGiveFromISR 获取信号量(take) xSemaphoreTake xSemaphoreTakeFromISR xSemaphoreGive
的函数原型如下:BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
xSemaphoreGive
函数的参数与返回值列表如下:参数 说明 xSemaphore
信号量句柄,释放哪个信号量 返回值 pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返回失败pxHigherPriorityTaskWoken
的函数原型如下:BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken);
xSemaphoreGiveFromISR
函数的参数与返回值列表如下:参数 说明 xSemaphore 信号量句柄,释放哪个信号量 pxHigherPriorityTaskWoken 如果释放信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE 返回值 pdTRUE表示成功,
如果二进制信号量的计数值已经是1,再次调用此函数则返回失 败;
如果计数型信号量的计数值已经是最大值,再次调用此函数则返 回失败xSemaphoreTake
的函数原型如下:BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
xSemaphoreTake
函数的参数与返回值列表如下:参数 说明 xSemaphore 信号量句柄,获取哪个信号量 xTicksToWait 如果无法马上获得信号量,阻塞一会:
0:不阻塞,马上返回
portMAX_DELAY: 一直阻塞直到成功
其他值: 阻塞的Tick个数,
可以使用 pdMS_TO_TICKS() 来指定阻塞时间为若干 ms返回值 pdTRUE表示成功 xSemaphoreTakeFromISR的函数原型如下:
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );
xSemaphoreTakeFromISR函数的参数与返回值列表如下:
参数 说明 xSemaphore 信号量句柄,获取哪个信号量 pxHigherPriorityTaskWoken 如果获取信号量导致更高优先级的任务变为了就绪态, 则*pxHigherPriorityTaskWoken = pdTRUE 返回值 pdTRUE表示成功
三、互斥量(mutex)
## 3.1 什么是互斥量?
在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上 二值型信号量用于同步,而 互斥型信号量用于资源保护。
互斥型信号量和二值型信号量还有一个 最大的区别,互斥型信号量可以有效解决优先级反转现象。
3.2 什么是优先级翻转
实质:低优先级任务将二值信号量获取,导致高优先级无法获取二值信号量阻塞,而不用获取信号量的中优先级任务能够打断低优先级任务,等到中优先级任务执行完后,得到信号量的低优先级任务继续执行,等到执行完后归还二值信号量,高优先级任务获取信号量后才能执行,这种现象称之为优先级翻转。
以上图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L通过信号量机 制,共享资源。目前任务L占有资源,锁定了信号量,Task H运行后将被阻塞,直到Task L释放信号量后, Task H才能够退出阻塞状态继续运行。但是Task H在等待Task L释放信号量的过程中,中等优先级任务M抢 占了任务L,从而延迟了信号量的释放时间,导致Task H阻塞了更长时间,这种现象称为优先级倒置或反转。
优先级继承: 当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
3.3 互斥量的使用场合
在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。 比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信 息混杂在一起。
3.4 互斥量API
要想使用互斥量,需要在配置文件 FreeRTOSConfig.h
中定义:
#define configUSE_MUTEXES 1
-
创建
互斥量是一种特殊的二进制信号量。 使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。 创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:
/* 创建一个互斥量,返回它的句柄。 * 此函数内部会分配互斥量结构体 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateMutex( void ); /* 创建一个互斥量,返回它的句柄。 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针 * 返回值: 返回句柄,非NULL表示成功 */ SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
-
其他函数
要注意的是,互斥量不能在ISR中使用。 各类操作函数,比如删除、give/take,跟一般是信号量是一样的。
/* * xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量 */ void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); /* 释放 */ BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 释放(ISR版本) */ BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken); /* 获得 */ BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait); /* 获得(ISR版本) */ xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken);
四、递归锁
4.1 死锁的概念
日常生活的死锁:我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!
假设有2个互斥量M1
、M2
,2个任务A
、B
:
- A获得了互斥量M1
- B获得了互斥量M2
- A还要获得互斥量M2才能运行,结果A阻塞
- B还要获得互斥量M1才能运行,结果B阻塞
- A、B都阻塞,再无法释放它们持有的互斥量
- 死锁发生!
4.2 自我死锁
假设这样的场景:
- 任务A获得了互斥锁M
- 它调用一个库函数
- 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
- 死锁发生!
4.3 API函数
怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:
- 任务A获得递归锁M后,它还可以多次去获得这个锁
"take"
了N次,要"give"N
次,这个锁才会被释放
递归锁的函数根一般互斥量的函数名不一样,参数类型一样,列表如下:
动作 | 递归锁 | 一般互斥量 |
---|---|---|
创建 | xSemaphoreCreateRecursiveMutex | xSemaphoreCreateMutex |
获得 | xSemaphoreTakeRecursive | xSemaphoreTake |
释放 | xSemaphoreGiveRecursive | xSemaphoreGive |
- 函数原型如下:
/* 创建一个递归锁,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );
/* 获得 */
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait);
五、注意事项
使用互斥量的两个任务是相同优先级时的注意事项
文章是自己总结而记录,有些知识点没说明白的,请各位看官多多提意见,多多交流,欢迎大家留言
如果技术交流可以加以下群,方便沟通
QQ群:370278903
点击链接加入群聊【蜡笔小芯的嵌入式交流群】