信号量(二进制/计数)
二进制信号量定义#includesemphr.h// SemaphoreHandle_t// 二进制信号量Binary SemaphoreSemaphoreHandle_t xBinarySemaphore;voidvTaskA(void*pvParameters){while(1){// 获取信号量if(xSemaphoreTake(xBinarySemaphore,portMAX_DELAY)pdTRUE){printf(TaskA: Got semaphore, doing work...\r\n);vTaskDelay(500/portTICK_PERIOD_MS);xSemaphoreGive(xBinarySemaphore);// 释放}}}使用示例/** * brief 主应用函数二进制信号量 * * note None * param None * return None * warning 此函数不会返回内部包含无限循环 * remark 通过函数指针从main()调用支持灵活的启动架构 */voidCurrent_Program4(void){LED_Init();// 初始化LED设备UART1_Config();// UART1串口初始化xBinarySemaphorexSemaphoreCreateBinary();if(xBinarySemaphoreNULL){// 创建失败堆内存不足}// 创建后信号量不可用值为0通常需要先 Give 一次xSemaphoreGive(xBinarySemaphore);// 使其变为可用//xSemaphoreGive(xBinarySemaphore); // 释放信号量xTaskCreate(vTaskA,vTaskA,128,NULL,1,NULL);// 启动调度器永远不会返回vTaskStartScheduler();// 理论上程序不会执行到这里但为了安全可以加一个死循环while(1){}}下面详细解释二进制信号量Binary Semaphore代码所涉及的核心知识点。二进制信号量是 FreeRTOS 中用于任务同步和资源共享控制的轻量级机制可以把它看作一个只能取 0 或 1 的计数器0 表示不可用1 表示可用。一、二进制信号量的本质计数范围0 或 1。操作Take获取如果信号量值为 1则将其减为 0 并立即返回成功如果为 0则任务可阻塞等待。Give释放将信号量值从 0 变为 1若已有任务等待该信号量则唤醒其中一个。与队列的区别队列可以携带数据而信号量只传递“事件发生”或“资源可用”这一布尔信息没有数据负载。二、代码中涉及的 API 函数1.xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)作用获取消耗一个信号量。参数xSemaphore信号量句柄。xTicksToWait若信号量不可用最多等待多少个 tick。portMAX_DELAY表示无限等待。返回值pdTRUE成功获取信号量。pdFALSE超时仍未获取到。注意在中断服务函数中应使用xSemaphoreTakeFromISR()。2.xSemaphoreGive(SemaphoreHandle_t xSemaphore)作用释放给出一个信号量使其值从 0 变为 1。参数信号量句柄。返回值pdTRUE释放成功。pdFALSE释放失败例如信号量已经是 1但一般不会失败。注意在中断中应使用xSemaphoreGiveFromISR()。三、代码运行流程分析SemaphoreHandle_t xBinarySemaphore;// 全局句柄voidvTaskA(void*pvParameters){while(1){// 1. 尝试获取信号量若为 0 则阻塞if(xSemaphoreTake(xBinarySemaphore,portMAX_DELAY)pdTRUE){printf(TaskA: Got semaphore, doing work...\r\n);// 2. 模拟占用资源的工作持有信号量期间vTaskDelay(500/portTICK_PERIOD_MS);// 3. 工作完成释放信号量xSemaphoreGive(xBinarySemaphore);}}}执行逻辑假设初始信号量值为 1可用。任务 A 调用xSemaphoreTake成功将信号量减为 0然后打印并延时 500ms。在延时期间任务 A 仍然持有信号量信号量值为 0其他任何想获取该信号量的任务都会进入阻塞状态。延时结束后任务 A 调用xSemaphoreGive信号量恢复为 1。此时如果有其他任务正在等待该信号量则会被唤醒并获得信号量。四、典型应用场景1. 任务同步Task Synchronization一个任务给出信号量另一个任务获取它表示“事件已发生”。例如中断服务程序给出信号量后台任务获取后处理数据。2. 资源互斥Resource Mutex——有缺陷上述代码将二进制信号量用于互斥访问共享资源如打印机、缓冲区。但二进制信号量不提供优先级继承可能导致优先级反转问题。对于互斥FreeRTOS 专门提供了xMutex互斥量支持优先级继承。3. 单次通知如果信号量初始为 0一个任务Give另一个任务Take实现一次性通知。五、二进制信号量 vs 互斥量特性二进制信号量互斥量Mutex用途同步、简单标志互斥访问共享资源优先级继承❌ 不支持✅ 支持防止优先级反转谁可以释放任何任务/中断只能由获取它的任务释放递归获取❌ 不能重复获取✅ 支持同一个任务可多次获取需释放相同次数创建函数xSemaphoreCreateBinary()xSemaphoreCreateMutex()因此如果vTaskA中的代码是在保护共享资源如写一个全局变量应使用互斥量而非二进制信号量。若只是用作任务间“干完活通知我”之类的一次性同步二进制信号量就已足够。六、注意事项初始状态xSemaphoreCreateBinary()创建的信号量初始值为 0。如果希望一开始就可用创建后需要先调用一次xSemaphoreGive()。而xSemaphoreCreateMutex()创建的互斥量初始值为 1可用。死锁风险如果获取信号量的任务在释放前被无限期挂起如永循环其他所有等待该信号量的任务都将永远阻塞。中断中使用中断服务程序中只能使用xSemaphoreGiveFromISR()并且不能阻塞不能调用Take带超时。示例给出信号量后应检查pxHigherPriorityTaskWoken并做任务切换。所有权二进制信号量没有“所有者”概念任何任务包括中断都可以释放它这容易造成意外释放。互斥量则只有持有者才能释放更安全。递归获取二进制信号量不支持递归获取。同一个任务再次Take同一个二进制信号量会阻塞除非先Give。而互斥量支持递归。七、完善示例正确的互斥使用改用互斥量SemaphoreHandle_t xMutex;voidvTaskA(void*pvParameters){while(1){if(xSemaphoreTake(xMutex,portMAX_DELAY)pdTRUE){// 临界区访问共享资源printf(TaskA: Accessing shared resource...\r\n);vTaskDelay(500/portTICK_PERIOD_MS);xSemaphoreGive(xMutex);}}}创建xMutexxSemaphoreCreateMutex();八、总结二进制信号量是 FreeRTOS 中最简单的同步工具状态只有 0/1。Take减少计数Give增加计数。适用于任务间同步如中断通知任务和简单的资源控制注意优先级反转风险。对于保护共享资源的互斥场景应优先使用xSemaphoreCreateMutex()创建的互斥量。你给出的代码展示了典型的信号量获取与释放模式只要理解其适用边界就能灵活运用。附录