信号量(二进制/计数)

二进制信号量定义:

c 复制代码
#include "semphr.h" // SemaphoreHandle_t
// 二进制信号量(Binary Semaphore)
SemaphoreHandle_t xBinarySemaphore;
void vTaskA(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); // 释放
        }
    }
}

使用示例:

c 复制代码
/**
 * @brief  主应用函数:二进制信号量
 * 
 * @note   None
 * @param  None
 * @return None
 * @warning 此函数不会返回,内部包含无限循环
 * @remark  通过函数指针从main()调用,支持灵活的启动架构
 */
void Current_Program4(void)
{	
	LED_Init();			//	初始化LED设备
	UART1_Config();		//	UART1串口初始化
	
	xBinarySemaphore = xSemaphoreCreateBinary();
	if (xBinarySemaphore == NULL) {
    // 创建失败(堆内存不足)
	}
	
	// 创建后信号量不可用(值为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()

三、代码运行流程分析

c 复制代码
SemaphoreHandle_t xBinarySemaphore;   // 全局句柄

void vTaskA(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 中的代码是在保护共享资源(如写一个全局变量),应使用互斥量而非二进制信号量。若只是用作任务间"干完活通知我"之类的一次性同步,二进制信号量就已足够。


六、注意事项

  1. 初始状态

    • xSemaphoreCreateBinary() 创建的信号量初始值为 0。如果希望一开始就可用,创建后需要先调用一次 xSemaphoreGive()
    • xSemaphoreCreateMutex() 创建的互斥量初始值为 1(可用)。
  2. 死锁风险

    • 如果获取信号量的任务在释放前被无限期挂起(如永循环),其他所有等待该信号量的任务都将永远阻塞。
  3. 中断中使用

    • 中断服务程序中只能使用 xSemaphoreGiveFromISR(),并且不能阻塞(不能调用 Take 带超时)。
    • 示例给出信号量后,应检查 pxHigherPriorityTaskWoken 并做任务切换。
  4. 所有权

    • 二进制信号量没有"所有者"概念,任何任务(包括中断)都可以释放它,这容易造成意外释放。互斥量则只有持有者才能释放,更安全。
  5. 递归获取

    • 二进制信号量不支持递归获取。同一个任务再次 Take 同一个二进制信号量会阻塞(除非先 Give)。而互斥量支持递归。

七、完善示例:正确的互斥使用(改用互斥量)

c 复制代码
SemaphoreHandle_t xMutex;

void vTaskA(void *pvParameters) {
    while(1) {
        if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
            // 临界区:访问共享资源
            printf("TaskA: Accessing shared resource...\r\n");
            vTaskDelay(500 / portTICK_PERIOD_MS);
            xSemaphoreGive(xMutex);
        }
    }
}

创建:

c 复制代码
xMutex = xSemaphoreCreateMutex();

八、总结

  • 二进制信号量是 FreeRTOS 中最简单的同步工具,状态只有 0/1。
  • Take 减少计数,Give 增加计数。
  • 适用于任务间同步 (如中断通知任务)和简单的资源控制(注意优先级反转风险)。
  • 对于保护共享资源的互斥场景,应优先使用 xSemaphoreCreateMutex() 创建的互斥量。

你给出的代码展示了典型的信号量获取与释放模式,只要理解其适用边界,就能灵活运用。

附录

相关推荐
炘爚1 小时前
Linux(整理合集)
linux
u0110225121 小时前
HTML5多媒体资源动态替换Source标签的刷新机制
jvm·数据库·python
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题】【Java基础篇】第18题:HashMap底层是如何扩容的
java·开发语言·面试·散列表·hash-index·hash
云祺vinchin1 小时前
“十五五”引领灾备升级,数字化安全建设如何合规落地?
网络·数据库·安全·kubernetes·数据安全·容灾备份
当战神遇到编程1 小时前
关系型数据库设计基础:约束、三大范式、表关系与表设计流程
数据库
想躺平的小羊1 小时前
IDEA 如何显示或关闭项目类的结构(类的方法)
java·ide·intellij-idea
JiaWen技术圈1 小时前
nftables 添加规则时支持的匹配条件与语句全解
linux·服务器
其实防守也摸鱼1 小时前
《SQL注入进阶实验:基于sqli-Labs的报错注入(Error-Based Injection)实战解析》
网络·数据库·sql·安全·网络安全·sql注入·报错注入
V我五十买鸡腿1 小时前
网安基础 Windows 和 Linux 那些常用命令
linux·运维·windows