FreeRTOS Semaphore信号量-笔记

FreeRTOS Semaphore信号量-笔记

      • **一、信号量与互斥量的核心区别**
      • [**二、二值信号量(Binary Semaphore)**](#二、二值信号量(Binary Semaphore))
        • [**1. 功能与使用场景**](#1. 功能与使用场景)
        • [**2. 示例:ADC中断与任务同步**](#2. 示例:ADC中断与任务同步)
      • [**三、计数信号量(Counting Semaphore)**](#三、计数信号量(Counting Semaphore))
        • [**1. 功能与使用场景**](#1. 功能与使用场景)
        • [**2. 示例:ADC双缓冲区管理**](#2. 示例:ADC双缓冲区管理)
      • **四、互斥量(Mutex)**
        • [**1. 功能与使用场景**](#1. 功能与使用场景)
        • [**2. 示例:保护共享资源**](#2. 示例:保护共享资源)
      • **五、关键注意事项**
      • **六、总结**

队列的功能是将进程间需要传递的数据存在其中。所以在有的RTOS系统里,队列也被称为邮箱。

一、信号量与互斥量的核心区别

特性 信号量(Semaphore) 互斥量(Mutex)
用途 进程间同步 (如事件通知)或资源计数(如ADC双缓冲区) 互斥访问共享资源(如保护全局变量或外设)
所有权 无所有权(任何任务或ISR均可释放) 有所有权(仅持有者可释放)
优先级继承机制 (可能导致优先级翻转) (缓解优先级翻转)
是否可中断使用 (通过 xSemaphoreGiveFromISR 不可(ISR中无法使用)
初始值 二值信号量(0或1)或计数信号量(任意值) 固定为1(表示资源可用)

信号量和互斥量。信号量和互斥量的实现都是基于队列的,信号量更适用于进程间同步,而互斥量更适用于共享资源的互斥性访问。


二、二值信号量(Binary Semaphore)


如果不使用二值信号量,而是使用一个自定义标志变量来实现以上的同步过程,则任务需要不断的查询标志变量的值,而不是像使用二值信号那样可以使任务进入阻塞动态状态。所以使用二值信号量进行进程间同步的效率更高。

1. 功能与使用场景
  • 功能 :作为"标志"实现任务同步,例如:
    • ADC中断通知任务:当ADC中断写入缓冲区后,释放二值信号量通知任务处理数据。
    • 任务等待事件:任务阻塞等待信号量,避免忙等待。
  • 创建函数
c 复制代码
  // 动态分配
 
	xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );
	//这是一个宏函数。调用的函数类似于队列(xQueueGenericCreate)创建队列时,调用的也是这个函数。

参数解析

  • ( UBaseType_t ) 1

    • 队列长度:表示队列最多能容纳的"消息"数量。
    • 二进制信号量特性:二进制信号量只能处于"空"(0)或"满"(1)状态,因此队列长度设为 1。
  • semSEMAPHORE_QUEUE_ITEM_LENGTH

    • 队列项大小:定义队列中每个消息的字节长度。
    • 二进制信号量特殊性:该宏被定义为 0(#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )),表示信号量不存储实际数据,仅用队列的空/满状态表示信号量状态。
  • queueQUEUE_TYPE_BINARY_SEMAPHORE

    • 队列类型:指定此队列用于二进制信号量,而非普通队列或其他类型(如互斥锁或计数信号量)。

c 复制代码
  // 静态分配
  SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer);
  • 操作函数
    • 释放信号量(任务):
c 复制代码
    xSemaphoreGive( xSemaphore )    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

参数解析

  • (QueueHandle_t)(xSemaphore)

    • 信号量即队列:FreeRTOS 的信号量句柄 xSemaphore 实际上是队列句柄(QueueHandle_t)的别名。信号量通过队列实现,其行为由队列的配置参数控制(如长度、项大小等)。
  • NULL

    • 无实际数据:信号量本身不传递数据,仅用队列的空/满状态表示信号量状态。因此,发送的"消息"无需有效数据,使用 NULL 即可。
  • semGIVE_BLOCK_TIME

    • 阻塞时间:定义为 0(#define semGIVE_BLOCK_TIME (0)),表示调用 xSemaphoreGive 时不等待。如果队列已满(信号量已处于"可用"状态),则直接返回错误 errQUEUE_FULL。
  • queueSEND_TO_BACK

    • 入队位置:将"消息"添加到队列的尾部。信号量的顺序无关紧要,因此使用尾部入队是合理的。
  • 返回类型为BaseType_t,pdFALSE或者pdTRUE。


  • 释放信号量(ISR):
c 复制代码
xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )    xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

参数解析

  • xSemaphore
    • 信号量句柄:实际是一个队列句柄(QueueHandle_t)的别名。信号量通过队列实现,其行为由队列的配置参数控制(如长度、项大小等)。
  • pxHigherPriorityTaskWoken
    • 优先级唤醒标志:
    • 类型:BaseType_t *(指向布尔值的指针)。
    • 功能:如果释放信号量导致更高优先级的任务被唤醒,该指针会被设置为 pdTRUE,表示需要在退出中断前请求上下文切换。
    • 可选参数:从 FreeRTOS V7.3.0 开始,此参数可以设为 NULL。
    • 注意:如果释放信号量导致了一个任务解锁,而解锁的任务比当前任务的优先级高,这里就会返回pdTRUE。这就需要在退出ISR之前申请任务调度,以便及时的解锁高优先级的任务。

  • 获取信号量(任务):
c 复制代码
xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

参数解析

  • xSemaphore
  • 信号量句柄:实际是一个队列句柄(QueueHandle_t)的别名。信号量通过队列实现,其行为由队列的配置参数控制(如长度、项大小等)。
  • 类型:SemaphoreHandle_t(底层为 QueueHandle_t)。
    ● xBlockTime
  • 阻塞时间:以 Tick 为单位指定任务等待信号量的最长时间。
  • portMAX_DELAY:无限期等待,直到信号量可用。
  • 0:非阻塞模式,立即返回。
  • 转换:使用 pdMS_TO_TICKS(ms) 将毫秒转换为 Tick(例如 pdMS_TO_TICKS(100))。

2. 示例:ADC中断与任务同步
c 复制代码
// 创建二值信号量
SemaphoreHandle_t xADCSemaphore = xSemaphoreCreateBinary();

// ADC中断服务程序
void ADC_IRQHandler(void) {
    // 写入数据到缓冲区
    WriteDataToBuffer();
    // 释放信号量通知任务
    xSemaphoreGiveFromISR(xADCSemaphore, NULL);
}

// 数据处理任务
void vDataProcessingTask(void *pvParameters) {
    while (1) {
        // 等待信号量
        xSemaphoreTake(xADCSemaphore, portMAX_DELAY);
        // 处理缓冲区数据
        ProcessData();
    }
}

三、计数信号量(Counting Semaphore)


资源类比成一个餐馆中的四个餐桌。
管理多个共享资源。例如ADC连续数据采集时,一般使用双缓冲区,就可以使用计数信号量来进行管理。

1. 功能与使用场景
  • 功能 :管理多个同类型资源(如ADC双缓冲区),允许同时访问多个资源。

  • 创建函数

    c 复制代码
    // 动态分配
    SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
    // 静态分配
    SemaphoreHandle_t xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer);
  • 操作函数

    • 释放资源

      c 复制代码
      xSemaphoreGive(xSemaphore);  // 资源计数+1
    • 获取资源

      c 复制代码
      xSemaphoreTake(xSemaphore, xTicksToWait);  // 资源计数-1
2. 示例:ADC双缓冲区管理
c 复制代码
// 创建计数信号量(2个缓冲区)
SemaphoreHandle_t xADCBufferSemaphore = xSemaphoreCreateCounting(2, 2);

// ADC中断服务程序
void ADC_IRQHandler(void) {
    // 获取一个缓冲区
    if (xSemaphoreTakeFromISR(xADCBufferSemaphore, NULL) == pdTRUE) {
        // 写入数据到缓冲区
        WriteDataToBuffer();
        // 释放信号量(资源可用)
        xSemaphoreGiveFromISR(xADCBufferSemaphore, NULL);
    }
}

// 数据处理任务
void vDataProcessingTask(void *pvParameters) {
    while (1) {
        // 获取缓冲区资源
        xSemaphoreTake(xADCBufferSemaphore, portMAX_DELAY);
        // 处理缓冲区数据
        ProcessData();
        // 释放缓冲区资源
        xSemaphoreGive(xADCBufferSemaphore);
    }
}

四、互斥量(Mutex)


二值信号量更适用于进程间同步,而互斥量更适用于控制对互斥型资源的访问。二值信号量没有优先级继承机制,将二值信号量用于互斥型资源访问时,容易出现优先级翻转问题。而互斥量有优先级继承机制,可以减缓优先级翻转问题。

1. 功能与使用场景
  • 功能 :保护共享资源的独占访问(如串口),避免数据竞争。

  • 创建函数

    c 复制代码
    // 动态分配
    SemaphoreHandle_t xSemaphoreCreateMutex(void);
    // 静态分配
    SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxSemaphoreBuffer);
  • 操作函数

    • 释放互斥量(可以释放二值信号量、计数信号量或者是互斥量。)

      c 复制代码
      xSemaphoreGive(xSemaphore);
    • 获取互斥量

      c 复制代码
      xSemaphoreTake(xSemaphore, xTicksToWait);
    • 递归互斥量 (允许任务多次获取同一互斥量):

      c 复制代码
      // 创建
      SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
      // 释放
      xSemaphoreGiveRecursive(xSemaphore);
      // 获取
      xSemaphoreTakeRecursive(xSemaphore, xTicksToWait);
2. 示例:保护共享资源
c 复制代码
// 创建互斥量
SemaphoreHandle_t xSharedResourceMutex = xSemaphoreCreateMutex();

// 任务A
void vTaskA(void *pvParameters) {
    while (1) {
        xSemaphoreTake(xSharedResourceMutex, portMAX_DELAY);
        // 访问共享资源
        SharedResource++;
        xSemaphoreGive(xSharedResourceMutex);
    }
}

// 任务B
void vTaskB(void *pvParameters) {
    while (1) {
        xSemaphoreTake(xSharedResourceMutex, portMAX_DELAY);
        // 访问共享资源
        SharedResource--;
        xSemaphoreGive(xSharedResourceMutex);
    }
}

五、关键注意事项

  1. 优先级翻转问题

    • 二值信号量:无优先级继承机制,可能导致低优先级任务阻塞高优先级任务。
    • 互斥量:通过优先级继承机制缓解问题,但仍需谨慎设计任务优先级。
  2. ISR中的使用限制

    • 信号量 :可在ISR中使用(xSemaphoreGiveFromISR)。
    • 互斥量禁止在ISR中使用(因其依赖优先级继承)。
  3. 内存分配选择

    • 动态分配:适合资源充足的系统,但可能产生内存碎片。
    • 静态分配:适合资源受限的嵌入式系统,需手动管理内存。
  4. 调试工具

    • uxSemaphoreGetCount():获取信号量当前值(适用于计数信号量)。
    • xSemaphoreGetMutexHolder():检查互斥量当前持有者。

六、总结

其他相关函数

● xSemaphoreGiveRecursive(用于递归互斥锁的释放)。

●xSemaphoreTakeRecursive()获取二值信号量、计数信号量或者是互斥量。释放递归互斥量依然有一个专用的函数。

●uxSemaphoreGetCount()返回传进去的这个信号量的当前值。

●xSemaphoreGetMutexHolder()作用:返回当前持有指定互斥锁(mutex)的任务的句柄(TaskHandle)。如果互斥锁未被任何任务持有,则返回 NULL。

xSemaphoreTakeFromISR()

  • 选择信号量还是互斥量
    • 同步需求 (如事件通知)→ 信号量
    • 资源互斥访问 (如保护共享变量)→ 互斥量
  • 避免常见错误
    • 不要在ISR中释放互斥量。
    • 确保每次获取互斥量后最终释放。
    • 使用递归互斥量时,获取与释放需严格配对。

通过合理使用信号量和互斥量,可以高效实现FreeRTOS中的任务同步与资源共享,提升系统的实时性和稳定性。

相关推荐
汇能感知2 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun3 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao3 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾3 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT4 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
ST.J4 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin4 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全
点灯小铭5 小时前
基于STM32单片机的智能粮仓温湿度检测蓝牙手机APP设计
stm32·单片机·智能手机·毕业设计·课程设计
小憩-5 小时前
【机器学习】吴恩达机器学习笔记
人工智能·笔记·机器学习
生擒小朵拉5 小时前
STM32添加库函数
java·javascript·stm32