信号量(二进制/计数)

二进制信号量定义:

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() 创建的互斥量。

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

附录

相关推荐
杨云龙UP8 小时前
TDengine 3.4.2.8 Community 三节点三副本生产集群部署实战(DNode/MNode/taosAdapter/Explorer)
大数据·linux·运维·数据库·tdengine·时序库
梦帮科技9 小时前
领域认知知识库图谱注入:从双式记账图网络到高质量问答对自动化合成流水线
运维·网络·数据库·人工智能·矩阵·架构·自动化
Long long ago.9 小时前
vastbase数据库运行sql宕机重启解决
数据库·sql
乌暮9 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习
向成科技9 小时前
XC3576H工控主板|深度适配Ubuntu 26.04 LTS,释放边缘AI与工业开发新潜能
linux·人工智能·ubuntu·机器人·硬件·主板·边缘ai
大猫和小黄9 小时前
深入理解 Java Optional:从设计哲学到工程实战
java
SelectDB技术团队9 小时前
一条日志两套引擎的账:把 Elasticsearch 检索与分析合并到同一份数据的落地写法
大数据·数据库·elasticsearch·搜索引擎·全文检索·日志·apache doris
数据库百宝箱9 小时前
rum&gin索引对比
java·数据库·gin
Fcy64810 小时前
Linux下 进程间关系与守护进程
linux·运维·服务器·守护进程
yychen_java10 小时前
第六篇:Spring AI 实战:将 Java 业务接口封装成企业级 MCP Server
java·人工智能·spring