freeRTOS学习笔记-信号量的原理及实现

目录

1、信号量简介

2、信号量定义

3、信号量创建

1)创建二值信号量 xSemaphoreCreateBinary()​编辑

2)创建计数信号量 xSemaphoreCreateCounting()

4、信号量删除函数 vSemaphoreDelete()

5、信号量释放函数

1).xSemaphoreGive()(在任务中使用)

 2)2. xSemaphoreGiveFromISR()(中断保护版本)

6、信号量获取函数

1)xSemaphoreTake( xSemaphore, xBlockTime )    

 信号量使用示例:

互斥量

1)创建互斥量xSemaphoreCreateMutex()

2)互斥量获取xSemaphoreTake(xSemaphore, xBlockTime)

 3)互斥量释放函数 xSemaphoreGive()

4)递归互斥量获取函数 xSemaphoreTakeRecursive()

5)递归互斥量释放函数 xSemaphoreGiveRecursive()

1、信号量简介

、信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资
源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任
务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它
当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。

freeRTOS信号量的实现是借助消息队列实现的,即复用了freeRTOS消息队列的一系列函数。

2、信号量定义

 信号量控制块与消息队列的控制块完全相同。常用的信号量有计数信号量和二值信号量。

3、信号量创建

1)创建二值信号量 xSemaphoreCreateBinary()

 其实就是创建了一个长度为1的消息队列,并把消息大小设为0,即存储消息的空间为0,只有控制块。即第二个参数宏为0。

案例:

2)创建计数信号量 xSemaphoreCreateCounting()

实现如下:最终调用的是创建消息队列的函数xQueueGenericCreate(),只不过将队列长度作为信号量最大值,并把初始信号量值初始的消息个数(uxMessagesWaiting),作为信号量使用的消息队列的消息元素大小的参数依然指定为0(宏queueSEMAPHORE_QUEUE_ITEM_LENGTH)

 创建完成的信号量结构如下图:

使用示例:

3)互斥量的创建

4、信号量删除函数 vSemaphoreDelete()

 实质上就是删除一个消息队列。

5、信号量释放函数

1).xSemaphoreGive()(在任务中使用)

实现如下:实质是调用的消息发送到消息队列的函数xQueueGenericSend(),其中因为只是使用了消息队列的uxMessageWaiting成员,并没有真的发送消息,所以消息的指针设为NULL,并且由于作为信号量的消息队列消息元素大小为0,所以函数不会真的将消息拷贝到消息队列中,仅仅只是将uxMessageWaiting的值加1。这样可以利用消息队列的结构实现信号量为0和到达最大值时的阻塞机制,因为与消息队列的阻塞机制相同。

 2)2. xSemaphoreGiveFromISR()(中断保护版本)

调用xQueueGiveFromISR,内部实现与xQueueGenericSendFromISR一致,只是没有了延迟阻塞机制。

调用函数 xSemaphoreGiveFromISR()
可能会唤醒阻塞在该信号量上的任务,如果被唤醒的任务的优先级大于当前任务的优先级,
那么形参 pxHigherPriorityTaskWoken 就会被设置为 pdTRUE, 然后在中断退出前执行一次
上下文切换。 从 FreeRTOS V7.3.0 版本开始, pxHigherPriorityTaskWoken 是一个可选的参
数,可以设置为 NULL。

6、信号量获取函数

1)xSemaphoreTake( xSemaphore, xBlockTime )    

本质是调用读取消息队列的函数xQueueGenericReceive(),只是第二个参数接收消息的内存为NULL。可以设定阻塞时间xBlockTime(与释放信号量不同,调用 xSemaphoreGive()释放信号量阻塞时间只能为0)。

 信号量使用示例:

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */


static void AppTaskCreate(void);/* 用于创建任务 */

static void Take_Task(void* pvParameters);/* Take_Task任务实现 */
static void Give_Task(void* pvParameters);/* Give_Task任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
  
  printf("这是一个[野火]-STM32全系列开发板-FreeRTOS计数信号量实验!\n");
  printf("车位默认值为5个,按下KEY1申请车位,按下KEY2释放车位!\n\n");
  
  /* 创建AppTaskCreate任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;  
  
  while(1);   /* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建Test_Queue */
  CountSem_Handle = xSemaphoreCreateCounting(5,5);	 
  if(NULL != CountSem_Handle)
    printf("CountSem_Handle计数信号量创建成功!\r\n");

  /* 创建Take_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Take_Task, /* 任务入口函数 */
                        (const char*    )"Take_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&Take_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建Take_Task任务成功!\r\n");
  
  /* 创建Give_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )Give_Task,  /* 任务入口函数 */
                        (const char*    )"Give_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&Give_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建Give_Task任务成功!\n\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : Take_Task
  * @ 功能说明: Take_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Take_Task(void* parameter)
{	
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY1被单击
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreTake(CountSem_Handle,	/* 计数信号量句柄 */
                             0); 	/* 等待时间:0 */
			if ( pdTRUE == xReturn ) 
				printf( "KEY1被按下,成功申请到停车位。\n" );
			else
				printf( "KEY1被按下,不好意思,现在停车场已满!\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次		
  }
}

/**********************************************************************
  * @ 函数名  : Give_Task
  * @ 功能说明: Give_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void Give_Task(void* parameter)
{	 
  BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
  /* 任务都是一个无限循环,不能返回 */
  while (1)
  {
    //如果KEY2被单击
		if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )       
		{
			/* 获取一个计数信号量 */
      xReturn = xSemaphoreGive(CountSem_Handle);//给出计数信号量                  
			if ( pdTRUE == xReturn ) 
				printf( "KEY2被按下,释放1个停车位。\n" );
			else
				printf( "KEY2被按下,但已无车位可以释放!\n" );							
		}
		vTaskDelay(20);     //每20ms扫描一次	
  }
}

互斥量

互斥量本质就是一个二值信号量,也就是一个长度为1的消息列表,但是消息列表作为互斥量的时候其中两个成员head和tail会被用作pxMutexHolder和uxQueueType。pxMutexHolder指向的是占有这个互斥量的任务,用于实现优先级继承,这也是互斥量和二值信号量的区别,uxQueueType的值为queueQUEUE_IS_MUTEX。

1)创建互斥量xSemaphoreCreateMutex()

最终还是调用xQueuegenericcreate()创建了一个长度为1,元素大小为0的消息队列,并将pxMutexHolder(即原head)初始化为NULL,由于互斥量无任务占有时为1,内部调用了一次xQueueGenericSend()

 

2)互斥量获取xSemaphoreTake(xSemaphore, xBlockTime)

本质就是调用了xQueueGenericReceive()函数和读取消息列表一样,只不过不会真的拷贝消息,只是操作了消息列表的xMessageWaiting成员变量和借用了消息列表的阻塞机制。作为互斥量的时候,如果获取成功,则将互斥量的pxMutexHolder指向获取该互斥量的任务,同时任务的互斥量持有数加一。源码实现如下图:


重点:若互斥量获取失败,则除了和消息列表一样的阻塞机制,还多了一步优先级继承,即将互斥量持有任务的优先级暂时提升至互斥量申请者的优先级,当然若本来就比申请者高则不用提升。源码实现如下:

 

 在代码(2)处,判断了一下EventlistItem的值最高位是否为1,不为一说明任务没有在等事件,那么就要根据提升后的优先级来调整EventlistItem的值。如果在等事件则不用调,因为EventlistItem的值被暂时用于事件等待列表作为判断标志了。

3)互斥量释放函数 xSemaphoreGive()

实质是调用xQueueGenericSend(),不同的是作为互斥量释放时,需要恢复原持有者的优先级(互斥量持有者的优先级可能在持有期间被提升过)源码实现如下: 

 

4)递归互斥量获取函数 xSemaphoreTakeRecursive()

    本质最终调用的是xQueueGerenicReceive(),只是在调用前进行了判断,若获取者为占有者本身,则只对成员uxRecursiveCallCount加1,不实际调用获取函数。

5)递归互斥量释放函数 xSemaphoreGiveRecursive()

        本质最终调用的是xQueueGerenicReceive(),只是在调用前进行了判断,若释放者不为占有者本身,则返回pdFAiL,不实际调用xQueueGerenicReceive(),否则对成员uxRecursiveCallCount减1,直到减至0才实际调用xQueueGerenicReceive()进行释放。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值