一、信号量简介
信号量用于同步,任务间或者任务和中断间同步
互斥量用户互锁,用于保护同时只能有一个任务访问的资源,为资源上一把锁。
二值信号量:同步。如果存在两个线程,为线程1和线程2,如果线程1发送了信号量,线程2接收到了信号量,这两个线程同步工作,如果线程1没有发送信号量,那么线程2也就不工作(前提线程2中有信号量接收等待函数)。相当于一个标志位。
计数信号量:资源使用统计
互斥量:互斥信号是一个拥有优先级继承的二值信号量
递归互斥:互斥中嵌套互斥
优先级反转,由于高优先级任务在等低优先级任务释放信号量的过程中,低优先级被中优先级任务抢夺CPU,最终导致中优先级的任务比高优先级的任务先运行,也就是优先级翻转,而互斥信号量就可以将这个影响降到最低,当高优先级任务发现需要信号量被低级信号量占用需要等待时,就将低优先级任务变成和自己同级优先级,就不至于在等待的过程中被中优先级抢占CPU。
1 信号量的常规操作
信号量这个名字很恰当:
- 信号:起通知作用
- 量:还可以用来表示资源的数量
- 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
- 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
- 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
计数型信号量的典型场景是:
- 计数:事件产生时"give"信号量,让计数值加1;处理事件时要先"take"信号量,就是获得信号量,让计数值减1。
- 资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。
信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:
- 生产者为任务A、B,消费者为任务C、D
- 一开始信号量的计数值为0,如果任务C、D想获得信号量,会有两种结果:
- 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
- 即刻返回失败:不等
- 任务A、B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒
- 唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人
二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。
2 信号量跟队列的对比
差异列表如下:
队列 | 信号量 |
---|---|
可以容纳多个数据, 创建队列时有2部分内存: 队列结构体、存储数据的空间 | 只有计数值,无法容纳其他数据。 创建信号量时,只需要分配信号量结构体 |
生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值已经达到最大时返回失败 |
消费者:没有数据时可以阻塞 | 消费者:没有资源时可以阻塞 |
3 两种信号量的对比
信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。
差别列表如下:
二进制信号量 | 技术型信号量 |
---|---|
被创建时初始值为0 | 被创建时初始值可以设定 |
其他操作是一样的 | 其他操作是一样的 |
使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。
以上转自:韦东山freeRTOS系列教程之【第六章】信号量(semaphore)_freertos semaphore-CSDN博客
二、STM32CubeMX设置
1、根据上一章的步骤创建两个任务:
STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客
任务LED1用作发送,LED2用作接收。
2、创建信号量
在 Timers and Semaphores
进行配置。
(1)、创建二值信号量Binary Semaphore
- Semaphore Name: 信号量名称
- Allocation: 分配方式:
Dynamic
动态内存创建 - Conrol Block Name: 控制块名称
(2)、 创建计数信号量Counting Semaphore
要想使用计数信号量必须在 Config parameters 中把 USE_COUNTING_SEMAPHORES 选择 Enabled 来使能。
在 Timers and Semaphores
创建计数信号量
- Semaphore Name: 信号量名称
- Count: 计数信号量的最大值
- Allocation: 分配方式:
Dynamic
动态内存创建 - Conrol Block Name: 控制块名称
3、生成代码
三、程序编程
1、创建一个二值量、并返回一个ID:osSemaphoreCreate
函数 | osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count) |
---|---|
参数 | semaphore_def: 引用由osSemaphoreDef定义的信号量 count: 信号量数量 |
返回值 | 成功返回信号量ID,失败返回0 |
例:
osSemaphoreId myBinarySem01Handle;
/* definition and creation of myBinarySem01 */
osSemaphoreDef(myBinarySem01);
myBinarySem01Handle = osSemaphoreCreate(osSemaphore(myBinarySem01), 1);
2、删除一个信号量:osSemaphoreDelete
包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除信号量
函数 | osStatus osSemaphoreDelete (osSemaphoreId semaphore_id) |
---|---|
参数 | semaphore_id: 信号量ID |
返回值 | 错误码 |
例:
osSemaphoreDelete(myBinarySem01Handle);
3、释放信号量的宏:osSemaphoreRelease
释放的对象必须是已经创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex() 创建的互斥量。可用在中断服务程序中。
函数 | osStatus osSemaphoreRelease (osSemaphoreId semaphore_id) |
---|---|
参数 | semaphore_id: 信号量ID |
返回值 | 错误码 |
例:
osSemaphoreRelease(myBinarySem01Handle);
4、获取信号量:osSemaphoreWait
用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。可用在中断服务程序中。
5、二值信号量实验
运作机制:
创建信号量时,系统会为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数, 二值信号量的最大可用信号量个数为 1。
二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个二值信号量,获取成功则返回正确,否则任务会根据用户指定的阻塞超时时间来等待其它任务/中断释放信号量。在等待这段时间,系统将任务变成阻塞态,任务将被挂到该信号量的阻塞等待列表中。
假如某个时间中断/任务释放了信号量,那么,由于获取无效信号量而进入阻塞态的任务将获得信号量并且恢复为就绪态状态。
(1)、osSemaphoreRelease 释放宏
当计数大于5时,释放二值信号量。
void LED1_Task1(void const * argument)
{
/* USER CODE BEGIN LED1_Task1 */
/* Infinite loop */
osStatus xReturn;
int i=0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("led1 run ..%d\n",i++);
if(i>5){
xReturn = osSemaphoreRelease(myBinarySem01Handle);//给出二值信号量
if(osOK == xReturn)
{
printf("release!\r\n");
}
else
{
printf("BinarySem release fail!\r\n");
}
}
osDelay(1000);
}
/* USER CODE END LED1_Task1 */
}
(2)、获取二值信号量osSemaphoreWait
一直等待二值信号量
void LED2_Task03(void const * argument)
{
/* USER CODE BEGIN LED2_Task03 */
/* Infinite loop */
osStatus xReturn = osErrorValue;
int i=0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("wait Binary ..%d\n",i++);
xReturn = osSemaphoreWait(myBinarySem01Handle, /* 二值信号量句柄 */
osWaitForever); /* 等待时间 */
if(osOK == xReturn)
{
printf("BinarySem get!\n\n");
}
osDelay(600);
}
/* USER CODE END LED2_Task03 */
}
(3)、下载验证:
编译无误后下载到板子上可以看到LED2一直等待信号量,等LED1释放了二值信号量后才继续运行。
在编写程序的时候发现,如果多次释放二值信号量时,只有第一次会成功,而接收函数也只能接收到一次的二值信号量,也就是说二值信号量是一对一的,而且如果多个任务都接收同一个信号量,那么只有优先级高能接收到信号量,低优先级的会接收不到!因此引入了一下的计数信号量,可实现多个任务接收信号量。
6、计数信号量实验
运作机制:
使用计数型信号量时,可以多次释放信号量;当信号量的计数值达到最大时,再次释放信号量就会出错。
如果信号量计数值为n,就可以连续n次获取信号量,第(n+1)次获取信号量就会阻塞或失败。
(1)、osSemaphoreRelease 释放宏
当计数为5的倍数时,释放两次计数信号量。
void LED1_Task1(void const * argument)
{
/* USER CODE BEGIN LED1_Task1 */
/* Infinite loop */
osStatus xReturn;
int i=0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("led1 run ..%d\n",i++);
if(i%5==0){
xReturn = osSemaphoreRelease(myCountingSem01Handle);//给出计数信号量
if(osOK == xReturn)
{
printf("release 1! \r\n");
}
else
{
printf("BinarySem release fail!\r\n");
}
xReturn = osSemaphoreRelease(myCountingSem01Handle);//给出计数信号量
if(osOK == xReturn)
{
printf("release 2! \r\n");
}
else
{
printf("BinarySem release fail!\r\n");
}
}
osDelay(1000);
}
}
(2)、获取计数信号量osSemaphoreWait
两个任务分别一直等待计数信号量
void LED2_Task03(void const * argument)
{
/* USER CODE BEGIN LED2_Task03 */
/* Infinite loop */
osStatus xReturn = osErrorValue;
int i=0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("wait Binary ..%d\n",i++);
xReturn = osSemaphoreWait(myCountingSem01Handle, /* 计数信号量句柄 */
osWaitForever); /* 等待时间 */
osDelay(600);
}
/* USER CODE END LED2_Task03 */
}
void StartTask3(void const * argument)
{
/* USER CODE BEGIN StartTask3 */
/* Infinite loop */
osStatus xReturn = osErrorValue;
int i=0;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("wait Binary 33..%d\n",i++);
xReturn = osSemaphoreWait(myCountingSem01Handle, /* 二值信号量句柄 */
osWaitForever); /* 等待时间 */
osDelay(700);
}
/* USER CODE END StartTask3 */
}
(3)、下载验证:
编译无误后下载到板子上可以看到LED2一直等待信号量,等LED1释放了二值信号量后才继续运行。
四、参考文献
STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)_ossemaphorecreate-CSDN博客
韦东山freeRTOS系列教程之【第六章】信号量(semaphore)_freertos semaphore-CSDN博客