一、信号量简介
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。
用停车场原形介绍:
空车位 :信号量资源数(计数值)
让出停车位:释放信号量(计数值++)
占用停车位:获取信号量(计数值--)
当计数值大于0,代表信号量资源:
当释放信号量,信号量计数值(资源数)+1;
当获取信号量,信号量计数值(资源数)-1;
信号量的计数值有限制:限定最大值。
如果最大值被限定为1,那么他就是二值信号量;
如果最大值不是1,那就是计数型信号量。
信号量:用于传递状态
队列与信号量的区别:
二、二值信号量
1.简介
二值信号量本质上是队列长度为1的队列,该队列只有空和满的情况,这就是二值。
二值信号量常用于互斥访问与任务同步,与互斥信号量类似,但是很可能造成优先级翻转问题,所以二值信号量更适用于任务的同步。
优先级翻转:高优先级的任务因等待低优先级的任务而被阻塞的情况。
2.相关API函数
使用二值信号量的过程:创建二值信号量——>释放二值信号量——>获取二值信号量
二值信号量函数简介:
动态创建二值信号量函数:SemaphoreHandle_t xSemaphoreCreateBinary(void)
释放二值信号量函数:BaseType_t xSemaphoreGive(xSemaphore)
获取二值信号量函数:BaseType_t xSemaphoreTake(xSemaphore,xBlockTime)
3.二值信号量实验
工程代码:
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_TASK_STACK_SIZE 128
//任务句柄
TaskHandle_t start_task_handler;
//开始任务
void start_task(void *pvParameters);
//任务优先级
#define TASK1_PRIO 2
//任务堆栈大小
#define TASK1_STACK_SIZE 128
//任务句柄
TaskHandle_t task1_handler;
//LED0任务
void task1(void *pvParameters);
//任务优先级
#define TASK2_PRIO 3
//任务堆栈大小
#define TASK2_STACK_SIZE 128
//任务句柄
TaskHandle_t task2_handler;
//LED1任务
void task2(void *pvParameters);
QueueHandle_t semphore_handle;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
GPIO_INIT();
KEY_INIT();
uart_init(9600);
//创建开始任务
xTaskCreate( (TaskFunction_t ) start_task, //任务函数指针
(char * ) "start_task", //任务函数名称
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数(一般为空)
(UBaseType_t ) START_TASK_PRIO, //任务优先级
(TaskHandle_t * ) &start_task_handler ); //任务句柄
//开启任务调度
vTaskStartScheduler();
}
开始任务函数
void start_task(void *pvParameters)
{
semphore_handle = xSemaphoreCreateBinary(); //使用动态的方式处理二值信号量
if(semphore_handle != NULL)
{
printf("\r\n二值信号量创建成功\r\n");
}
//创建LED0任务
xTaskCreate( (TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
//创建LED1任务
xTaskCreate( (TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
//删除自己
vTaskDelete(NULL);
}
//任务一释放二值信号量
void task1(void *pvParameters)
{
u8 key = 1;
BaseType_t err;
while(1)
{
if(key == KEY_SCAN(0))
{
if(semphore_handle != NULL)
{
err = xSemaphoreGive(semphore_handle);
if(err == pdPASS)
{
printf("\r\n释放信号量成功\r\n");
}else printf("\r\n信号量释放失败\r\n");
}
}
vTaskDelay(500);
}
}
//任务二获取二值信号量
void task2(void *pvParameters)
{
u8 i = 0;
while(1)
{
xSemaphoreTake(semphore_handle,portMAX_DELAY);
printf("\r\n获取信号量成功%d\r\n",i++);
}
}
key.c和led.c过于简单,就不放上来了。
三、计数型信号量
1.简介
计数型信号量相当于队列大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候就确定的。
计数型信号量适用的场合:
事件计数:当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其他任务会获取计数型信号量(计数值-1),这种场合一般创建时将初始计数值设置为0。
资源管理:信号量表示有效资源数目。任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。当计数值减为0时表示没有可控制的资源。当任务使用完资源时,必须释放信号量(信号量计数值+1)。信号量计数值被创建时应该等于最大资源数目。
2.相关API函数
使用计数型信号量的过程:创建计数型信号量——>释放信号量——>获取信号量
计数型的信号量的释放和获取与二值信号量相同!
计数型信号量函数简介:
创建计数型信号量函数:xSemaphoreCreateConuting(xMAXCount,uxlinitialCount)
获取计数型信号量当前计数值的大小:uxSemaphoreGetCount(xSemaphore)
3.计数型信号量实验
工程代码:
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include <stdio.h>
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_TASK_STACK_SIZE 128
//任务句柄
TaskHandle_t start_task_handler;
//开始任务
void start_task(void *pvParameters);
//任务优先级
#define TASK1_PRIO 2
//任务堆栈大小
#define TASK1_STACK_SIZE 128
//任务句柄
TaskHandle_t task1_handler;
//LED0任务
void task1(void *pvParameters);
//任务优先级
#define TASK2_PRIO 3
//任务堆栈大小
#define TASK2_STACK_SIZE 128
//任务句柄
TaskHandle_t task2_handler;
//LED1任务
void task2(void *pvParameters);
QueueHandle_t count_semphore_handle;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
GPIO_INIT();
KEY_INIT();
uart_init(9600);
//创建开始任务
xTaskCreate( (TaskFunction_t ) start_task, //任务函数指针
(char * ) "start_task", //任务函数名称
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, //任务堆栈大小
(void * ) NULL, //传递给任务函数的参数(一般为空)
(UBaseType_t ) START_TASK_PRIO, //任务优先级
(TaskHandle_t * ) &start_task_handler ); //任务句柄
//开启任务调度
vTaskStartScheduler();
}
开始任务函数
void start_task(void *pvParameters)
{
count_semphore_handle = xSemaphoreCreateCounting(100,100); //创建计数型信号量
if(count_semphore_handle != NULL)
{
printf("\r\n计数型量创建成功\r\n");
}
//创建LED0任务
xTaskCreate( (TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
//创建LED1任务
xTaskCreate( (TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
//删除自己
vTaskDelete(NULL);
}
//任务一释计数型信号量
void task1(void *pvParameters)
{
BaseType_t err;
u8 key = 1;
while(1)
{
if(key == KEY_SCAN(0))
{
if(count_semphore_handle != NULL)
{
err = xSemaphoreGive(count_semphore_handle); //释放计数型信号量
}
}
vTaskDelay(10);
}
}
//任务二获取计数型信号量
void task2(void *pvParameters)
{
BaseType_t err = 0;
while(1)
{
err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); //获取信号值且死等
if(err == pdTRUE)
{
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
}
vTaskDelay(1000);
}
}
四、优先级翻转
1.简介
高优先级的任务反而慢执行,低优先级的任务反而先执行。
优先级翻转在抢占式内核当中是十分常见的,但是在实时操作系统当中是不允许出现优先级翻转的,因为优先级翻转会破坏任务执行的预期顺序,可能会导致未知的严重后果。
在使用二值信号量的时候,经常会遇到优先级翻转的问题。
举个例子(注意前提条件):
(1)这是一个二值信号量;
(2)任务H是需要获取信号量才可以执行的;
(3)任务M是需要信号量就可以执行的;
(4)任务L先获取到信号量;
执行流程:
首先任务L获取到信号量,由于任务H的抢占优先级更高,任务H优先执行;但是由于没有获取信号量,任务H进入阻塞状态,此时任务M中等优先级准备就绪,优先级高于低优先级的任务L,任务M开始执行。
整个过程执行过程就是中等优先级的任务M一直在执行,而高优先级的任务H由于没有获取到信号量一直没有执行。从现象看,中等优先级的任务比高优先级的任务具有更高的优先级,这就是优先级翻转。
2.优先级翻转实验
工程代码:
main.c
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "sys.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/************************************************
ALIENTEK Mini STM32F103开发板 FreeRTOS实验2-1
FreeRTOS移植实验-库函数版本
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LOW_TASK_PRIO 2
//任务堆栈大小
#define LOW_STK_SIZE 50
//任务句柄
TaskHandle_t LOWTask_Handler;
//任务函数
void low_task(void *pvParameters);
//任务优先级
#define MEDIUM_TASK_PRIO 3
//任务堆栈大小
#define MEDIUM_STK_SIZE 50
//任务句柄
TaskHandle_t MEDIUMTask_Handler;
//任务函数
void medium_task(void *pvParameters);
//任务优先级
#define HIGH_TASK_PRIO 4
//任务堆栈大小
#define HIGH_STK_SIZE 50
//任务句柄
TaskHandle_t HIGHTask_Handler;
//任务函数
void high_task(void *pvParameters);
QueueHandle_t semphore_handle;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(9600); //初始化串口
//LED_Init(); //初始化LED
semphore_handle = xSemaphoreCreateBinary();
if(semphore_handle != NULL)
{
printf("二值信号量创建成功!!!\r\n");
}
xSemaphoreGive(semphore_handle); //释放二值信号量
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&LOWTask_Handler);
xTaskCreate((TaskFunction_t )medium_task,
(const char* )"medium_task",
(uint16_t )MEDIUM_STK_SIZE,
(void* )NULL,
(UBaseType_t )MEDIUM_TASK_PRIO,
(TaskHandle_t* )&MEDIUMTask_Handler);
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&HIGHTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//低优先级任务
void low_task(void *pvParameters)
{
while(1)
{
printf("low_task获取信号量!!!\r\n");
xSemaphoreTake(semphore_handle,portMAX_DELAY);
printf("low_task正在运行!!!\r\n");
delay_xms(3000);
printf("low_task释放信号量!!!\r\n");
xSemaphoreGive(semphore_handle);
vTaskDelay(1000);
}
}
//中优先级任务
void medium_task(void *pvParameters)
{
while(1)
{
printf("medium_task正在运行!!!\r\n");
vTaskDelay(1000);
}
}
//高优先级任务
void high_task(void *pvParameters)
{
while(1)
{
printf("high_task获取信号量!!!\r\n");
xSemaphoreTake(semphore_handle,portMAX_DELAY);
printf("high_task正在运行!!!\r\n");
delay_xms(1000);
printf("high_task释放信号量!!!\r\n");
xSemaphoreGive(semphore_handle);
vTaskDelay(1000);
}
}
delay.c
#include "delay.h"
//
//如果需要使用OS,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "FreeRTOS.h" //FreeRTOS使用
#include "task.h"
#endif
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//使用SysTick的普通计数模式对延迟进行管理(支持OS)
//包括delay_us,delay_ms
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/11/28
//版本:V1.8
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//修改说明
//
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
extern void xPortSysTickHandler(void);
//systick中断服务函数,使用ucos时用到
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
}
}
//初始化延迟函数
//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率!
//SYSCLK:系统时钟频率
void delay_init()
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟 HCLK
fac_us=SystemCoreClock/1000000; //不论是否使用OS,fac_us都需要使用
reload=SystemCoreClock/1000000; //每秒钟的计数次数 单位为M
reload*=1000000/configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右
fac_ms=1000/configTICK_RATE_HZ; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/configTICK_RATE_HZ秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
//延时nus
//nus:要延时的us数.
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt=0;
u32 reload=SysTick->LOAD; //LOAD的值
ticks=nus*fac_us; //需要的节拍数
told=SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
};
}
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u32 nms)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
vTaskDelay(nms/fac_ms); //FreeRTOS延时
}
nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
//延时nms,不会引起任务调度
//nms:要延时的ms数
void delay_xms(u32 nms)
{
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}
打印的信息一直跟逻辑对不上,找了半天发现一到delay函数就会任务跳转,我都蒙了,后来发现delay函数是用vTaskDelay函数在封装的。(delay函数是网上扒的,我都没怎么看,都是偷懒惹的祸)。
五、互斥信号量
1.简介
互斥信号量其实是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最合适。互斥信号量适用于那些需要互斥访问的应用中!
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时也有一个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被堵塞。不过这个高优先级的任务会将低优先级的任务优先级提升到与自己相同的优先级。
优先级继承并不能完全消除优先级翻转的问题,只能尽可能的降低优先级翻转带来的影响。
举个例子(条件同上):
执行流程:
首先任务L获取到信号量,由于任务H的抢占优先级更高,任务H优先执行;但是由于没有获取信号量,任务H进入阻塞状态,此时由于优先级继承,任务L的优先级被提升到与任务H同等的优先级,任务M由于优先级比此时的任务L要低,任务L执行完毕之后,释放信号量,任务H获取信号量开始执行;期间任务M由于优先级比两个都低,没有执行。
整个过程,任务H的阻塞时间只是任务L的执行的时间,会将优先级翻转的伤害降到最低。优先级继承将低优先级的任务的优先级提高,避免其他优先级高的任务干扰预期执行流程,保证尽快释放信号量给高优先级的任务。
2.相关API函数
使用互斥信号量:首先将宏configUSE_MUTEXES置1
使用流程:创建互斥信号量——>(task)获取信号量——>(give)释放信号量
互斥信号量的释放和获取函数与二值信号量相同!只不过互斥信号量不支持中断中调用
注意:创建互斥量时,会主动释放一次信号量
3.互斥信号量实验
工程代码:
main.c
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "sys.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/************************************************
ALIENTEK Mini STM32F103开发板 FreeRTOS实验2-1
FreeRTOS移植实验-库函数版本
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LOW_TASK_PRIO 2
//任务堆栈大小
#define LOW_STK_SIZE 50
//任务句柄
TaskHandle_t LOWTask_Handler;
//任务函数
void low_task(void *pvParameters);
//任务优先级
#define MEDIUM_TASK_PRIO 3
//任务堆栈大小
#define MEDIUM_STK_SIZE 50
//任务句柄
TaskHandle_t MEDIUMTask_Handler;
//任务函数
void medium_task(void *pvParameters);
//任务优先级
#define HIGH_TASK_PRIO 4
//任务堆栈大小
#define HIGH_STK_SIZE 50
//任务句柄
TaskHandle_t HIGHTask_Handler;
//任务函数
void high_task(void *pvParameters);
QueueHandle_t mutex_semphore_handle;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
//LED_Init(); //初始化LED
mutex_semphore_handle = xSemaphoreCreateMutex();
if(mutex_semphore_handle != NULL)
{
printf("互斥信号量创建成功!!!\r\n");
}
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&LOWTask_Handler);
xTaskCreate((TaskFunction_t )medium_task,
(const char* )"medium_task",
(uint16_t )MEDIUM_STK_SIZE,
(void* )NULL,
(UBaseType_t )MEDIUM_TASK_PRIO,
(TaskHandle_t* )&MEDIUMTask_Handler);
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&HIGHTask_Handler);
vTaskDelete(NULL); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//低优先级任务
void low_task(void *pvParameters)
{
while(1)
{
printf("low_task获取信号量!!!\r\n");
xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
printf("low_task正在运行!!!\r\n");
delay_xms(3000);
printf("low_task释放信号量!!!\r\n");
xSemaphoreGive(mutex_semphore_handle);
vTaskDelay(1000);
}
}
//中优先级任务
void medium_task(void *pvParameters)
{
while(1)
{
printf("medium_task正在运行!!!\r\n");
vTaskDelay(1000);
}
}
//高优先级任务
void high_task(void *pvParameters)
{
while(1)
{
printf("high_task获取信号量!!!\r\n");
xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
printf("high_task正在运行!!!\r\n");
delay_xms(1000);
printf("high_task释放信号量!!!\r\n");
xSemaphoreGive(mutex_semphore_handle);
vTaskDelay(1000);
}
}