学习笔记--RTOS信号量

学习笔记–RTOS信号量

本文基于正点原子RTOS开发指南,笔记自用,获取详细信息请关注正点原子官方账号

简介:信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。和linux中的信号量差不多,描述资源数量,在使用资源时看剩余资源的数量,使用时将资源数量减一。

信号量与队列的区别

队列 信号量
可以容纳多个数据; 创建队列有两部分内存:队列结构体+队列项存储空间 仅存放计数值,无法存放其他数据; 创建信号量,只需分配信号量结构体
写入队列:当队列满时,可阻塞; 释放信号量:不可阻塞,计数值++, 当计数值为最大值时,返回失败
读取队列:当队列为空时,可阻塞; 获取信号量:计数值–, 当没有资源时,可阻塞

二值信号量

简介:信号量的计数值有限制,描述资源的数量,如果其最大值限定为1,也就是其资源数量为1,那么其就是二值信号量如果最大值不是1,就是计数型信号量

二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况,这就是二值。

用途:二值信号量通常用于互斥访问任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,所以二值信号量更适合用于同步
学习笔记--RTOS信号量_第1张图片

二值信号量API函数

使用二值信号量的过程:创建二值信号量 à 释放二值信号量 à 获取二值信号量

函数 描述
xSemaphoreCreateBinary() 使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic() 使用静态方式创建二值信号量
xSemaphoreGive() 释放信号量
xSemaphoreGiveFromISR() 在中断中释放信号量
xSemaphoreTake() 获取信号量
xSemaphoreTakeFromISR() 在中断中获取信号量
创建二值信号量函数
SemaphoreHandle_t   xSemaphoreCreateBinary( void ) 
    /*函数实现*/
 #define   xSemaphoreCreateBinary( )   						\
 xQueueGenericCreate( 1 ,   semSEMAPHORE_QUEUE_ITEM_LENGTH  , queueQUEUE_TYPE_BINARY_SEMAPHORE )  

实际上是创建一个队列,队列的类型时下面的5

#define queueQUEUE_TYPE_BASE                  	( ( uint8_t ) 0U )	/* 队列 */
#define queueQUEUE_TYPE_SET                  	( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX                 	( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE    	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE     	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX       	( ( uint8_t ) 4U )	/* 递归互斥信号量 */

返回值:成功返回信号量的句柄,失败时返回NULL

释放二值信号量
BaseType_t   xSemaphoreGive( xSemaphore ) 
#define   xSemaphoreGive (  xSemaphore  )    						\
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore )  ,   NULL  ,   semGIVE_BLOCK_TIME  , queueSEND_TO_BACK )  

实际上是队列发送函数,只不过类型是queueSEND_TO_BACK

参数:xSemaphore :需要释放的信号量句柄 semGIVE_BLOCK_TIME 阻塞时间,默认为0

返回值:成功返回pdPASS,失败返回 errQUEUE_FULL

获取二值信号量
BaseType_t   xSemaphoreTake( xSemaphore, xBlockTime ) 

参数介绍:xSemaphore:要操作的信号量句柄 xBlockTime:阻塞等待时间,超出该时间内为获取到信号量则直接返回错误。

返回值:pdTRUE:获取信号量成功 pdFALSE:超时,获取信号量失败

示例
/*初始化信号量*/
QueueHandle_t sem;//创建信号量句柄

sem = xSemaphoreCreateBinary();
    if(sem == NULL){
        printf("Create binary sem error!\r\n");
    }
    printf("Create binary sem success!\r\n");

获取信号量以及释放信号量

/*获取信号量*/ 
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET){
            while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET);
            printf("key0 press!\r\n");
            /*Get binary sem*/
           err = xSemaphoreTake(sem,portMAX_DELAY);
            if(err == pdFALSE){
                printf("Get Binary sem error!\r\n");
            }
            printf("Get Binary sem success!\r\n");
        }
/*释放信号量*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET){
        while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET);
        printf("key1 press!\r\n");
        ret = xSemaphoreGive(sem);
        if(ret == pdFALSE){
            printf("Relax Binary sem error!\r\n");
        }
        printf("Relax Binary sem success!\r\n");
            
        }

计数型信号量

简介:计数型信号量相当于队列长度大于1的队列,表示有多个资源。

应用场景:

  • 【事件计数】当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其他任务

    会获取计数型信号量(计数值-1) ,这种场合一般在创建时将初始计数值设置为 0

  • 【资源管理】信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1 )才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目

我的理解:对于事件计数型信号量,初始化为0,每有一次资源,那么信号量就加一,

对于资源管理型信号量,初始化为资源的数目,资源在使用时信号量减一,用完时信号量加一

计数型信号量相关API函数

函数 描述
xSemaphoreCreateCounting() 使用动态方法创建计数型信号量。
xSemaphoreCreateCountingStatic() 使用静态方法创建计数型信号量
uxSemaphoreGetCount() 获取信号量的计数值

计数型信号量的释放和获取和二值型信号量的获取和释放方法相同

动态创建计数型信号量
#define 	xSemaphoreCreateCounting(  uxMaxCount  ,  uxInitialCount  )   		\		xQueueCreateCountingSemaphore( (  uxMaxCount  ) , (  uxInitialCount  ) ) 

参数:uxMaxCount:计数型信号量的数量最大值限定,uxInitialCount:计数型信号量的初值

返回值:创建成功返回信号量的任务句柄,失败时返回NULL

获取信号量的计数值
#define 	uxSemaphoreGetCount( xSemaphore ) 						\		uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )

参数:要获取的信号量句柄

返回值:信号量的资源数目

示例
/*初始化信号量*/
QueueHandle_t sem;//信号量句柄
UBaseType_t SemMaxCount=10;//最大信号量数量
UBaseType_t SemInitialCount=1;//初始化信号量数量

/*Init Semaphore for the type of count*/
    sem = xSemaphoreCreateCounting(SemMaxCount,SemInitialCount);
    if(sem == NULL){
        printf("Create count sem error!\r\n");
    }else{
         printf("Create count sem success!\r\n");
    }

计数型信号量的申请和释放

/*申请信号量*/ 
if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET){
            while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET);
            printf("key0 press!\r\n");
            /*Get count sem*/
           err = xSemaphoreTake(sem,Waittime);
            if(err == pdFALSE){
                printf("Get count sem error!\r\n");
            }else{
                printf("Get count sem success!\r\n");
            }
            
        }
/*释放信号量*/
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET){
    while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET);
    printf("key1 press!\r\n");
    ret = xSemaphoreGive(sem);
    if(ret == pdFALSE){
        printf("Relax count sem error!\r\n");
    }
    else{
        printf("Relax count sem success!\r\n");
    }
        
            
}
/*查询信号量*/
UBaseType_t CurrentSemCount;
CurrentSemCount = uxSemaphoreGetCount(sem);
printf("CurrentSemCount:%d\r\n",CurrentSemCount);

效果

CurrentSemCount:1
CurrentSemCount:1
CurrentSemCount:1

key0 press!
Get count sem success!
CurrentSemCount:0
CurrentSemCount:0
CurrentSemCount:0

key1 press!
Relax count sem success!
CurrentSemCount:1

key1 press!
Relax count sem success!
CurrentSemCount:2

key1 press!
Relax count sem success!
CurrentSemCount:3

key1 press!
Relax count sem success!
CurrentSemCount:4
CurrentSemCount:4

key1 press!
Relax count sem success!
CurrentSemCount:5

key0 press!
Get count sem success!
CurrentSemCount:4
CurrentSemCount:4

key1 press!
Relax count sem success!
CurrentSemCount:5
CurrentSemCount:5

初始化的资源数为1,按下KEY0后资源数-1 按下KEY1后资源数加一

优先级翻转

简介:优先级翻转是指 高优先级的任务反而慢执行,低优先级的任务反而优先执行,

优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。

学习笔记--RTOS信号量_第2张图片

分析:任务L优先级低,首先获取到信号量,而此时H运行,准备抢占信号量,但是信号量在L手中未释放,于是H进入阻塞状态,而此时M已经处于就绪态,其优先级比L高,会抢占L 而此时L中的信号量并没有释放,所以一直等到M执行完毕,回到L执行完毕后释放信号量,然后H才能运行。

等于说,虽然是高优先级的H 但因为其把柄在L手中,而M又比L大,所以导致其最后运行,这就是优先级翻转。

互斥信号量

简介:互斥信号量其实就是一个拥有优先级继承二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!

优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。

示例:在低优先级任务获取到信号量,高优先级任务想要获取信号量时,会把低优先级的任务的优先级提高到高优先级,此时L继续运行,因为与H同优先级,而M进入就绪状态,一直等到L运行完毕,释放出信号量,好处是避免了优先级反转中M 抢占L导致信号量迟迟不会被释放,因为L的优先级比M高,所以不会被抢占。
学习笔记--RTOS信号量_第3张图片

注意:

  1. 优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
  2. 互斥信号量不能用于中断服务函数中,原因如下:
    1. ) 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
    2. 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
互斥信号量相关API

使用互斥信号量,首先将configUSE_MUTEXES设置为1

函数 描述
xSemaphoreCreateMutex() 使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic() 使用静态方法创建互斥信号量。

互斥信号量的释放和获取函数与二值信号量相同 !**只不过互斥信号量不支持中断中调用 **

注意:**创建互斥信号量时,会主动释放一次信号量 **

互斥信号量实现

#define   xSemaphoreCreateMutex()      xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

返回值:创建成功返回互斥信号量的句柄 失败返回NULL

示例
/*初始化信号量句柄*/
QueueHandle_t mutex_sem;

/*初始话互斥信号量*/
    mutex_sem = xSemaphoreCreateMutex();
    if(mutex_sem == NULL){
        printf("Create mutex sem error!\r\n");
    }else{
         printf("Create mutex sem success!\r\n");
    }
   

任务中获取、释放信号量

/*task1的优先级为3 属于低优先级,通过安按键KEY0首先获取到了信号量,并在10s中后释放信号量*/
void task1(void *argv)
{
    TickType_t Waittime = 10;
    uint16_t times;
    BaseType_t ret;

    uint8_t flag=pdFALSE;
    printf("task1 runing!\r\n");
	while(1){
        if(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET){
            while(HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin) == GPIO_PIN_SET);
            printf("task1 key0 press!\r\n");
            /*Get binary sem*/
           ret = xSemaphoreTake(mutex_sem,Waittime);
            if(ret == pdFALSE){
                printf("task1 Get mutex sem error!\r\n");
            }else{
                printf("task1 Get mutex sem success!\r\n");
                flag = pdTRUE;
            }
            
        }
        
        if(flag == pdTRUE){
            times++;
        }
        if(times==1000){
            printf("relax mutex sem\r\n");
            ret = xSemaphoreGive(mutex_sem);
            if(ret == pdFALSE){
                printf("task2 Relax mutex sem error!\r\n");
            }
            else{
                printf("task2 Relax mutex sem success!\r\n");
            }

        }
		
		vTaskDelay(10);
	}
	
}
/*Task2 属于高优先级任务优先级为5 通过KEY1按键获取信号量*/
void task2(void *argv)
{
    
    BaseType_t ret;
    printf("task2 runing!\r\n");
   while(1)
   {
        if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET){
            while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET);
            printf("task2 key0 press!\r\n");
            /*Get binary sem*/
           ret = xSemaphoreTake(mutex_sem,portMAX_DELAY);
            if(ret == pdFALSE){
                printf("task2 Get mutex sem error!\r\n");
            }else{
                printf("task2 Get mutex sem success!\r\n");
            }
            
        }

        vTaskDelay(10);
   }
	
}
/*Task3 属于中优先级的任务,优先级为4 负责打印优先级和信号量的数目*/
void task3(void *argv)
{
    UBaseType_t CurrentSemCount;
    UBaseType_t taskpriority;
   printf("task3 runing!\r\n");
   while(1)
   {
       HeartBeatLED0();
       vTaskDelay(1000);
       CurrentSemCount = uxSemaphoreGetCount(mutex_sem);
       printf("CurrentSemCount:%d\r\n",CurrentSemCount);
        printf("Task1 priority:%d\t Task2 Priority:%d\r\n",uxTaskPriorityGet(task1_handler),uxTaskPriorityGet(task2_handler));

   }

}

效果

/*初始化信号量*/
Create mutex sem success!
task2 runing!
task3 runing!
task1 runing!

/*初始化信号量情况,信号量数目为1 Task1优先级为3 Task2优先级为5*/
CurrentSemCount:1
Task1 priority:3	 Task2 Priority:5
CurrentSemCount:1
Task1 priority:3	 Task2 Priority:5
CurrentSemCount:1
Task1 priority:3	 Task2 Priority:5

/*KEY0 按下低优先级的任务获取到了信号量,此时资源变为0 优先级不变*/
task1 key0 press!
task1 Get mutex sem success!
CurrentSemCount:0
Task1 priority:3	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:3	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:3	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:3	 Task2 Priority:5

/*key1按下,此时属于优先级的任务获取信号量,此时Task2 阻塞(超时时间设置为死等),并且调高了低优先级任务Task1的优先级 和自己Task2一样为5*/
task2 key1 press!
CurrentSemCount:0
Task1 priority:5	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:5	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:5	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:5	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:5	 Task2 Priority:5

/*10s时间到,低优先级释放了信号量*/
relax mutex sem
/*高优先级得到了信号量*/
task2 Get mutex sem success!
task2 Relax mutex sem success!

/*被调高优先级的Task1优先级回到原来的3 因为资源被Task2 高优先级抢占,所以资源数目还是0*/
CurrentSemCount:0
Task1 priority:3	 Task2 Priority:5
CurrentSemCount:0
Task1 priority:3	 Task2 Priority:5

你可能感兴趣的:(RTOS学习笔记,学习,stm32,c语言,单片机,经验分享)