FreeRTOS中断管理与中断安全API

在ISR中使用FreeRTOS的API

任务的优先级和中断的优先级

区分任务的优先级和中断的优先级是非常重要的,原因如下:

        任务是与FreeRTOS运行所在的硬件无关的软件功能。任务的优先级由编程人员在软件中分配,软件算法(即调度器)决定哪个任务将被处于运行状态。

        尽管中断服务程序是由软件编写,但中断具备硬件特性,由硬件控制哪个中断服务程序将运行以及何时运行。只有在没有ISR运行时,任务才会运行,因此最低优先级的中断将中断最高优先级的任务,并且任务无法抢占ISR。

        运行FreeRTOS的所有架构都具备处理中断的能力,但与中断进入和中断优先级分配相关的细节,会因架构而有所不同。

中断安全API

        通常需要在中断服务程序(ISR)中使用FreeRTOS API函数提供的功能,但是许多FreeRTOS API函数执行的操作在ISR内部是无效的。其中最显著的一点,将调用API函数的任务置于阻塞状态,如果API函数是从ISR调用的,就没有可以置于阻塞状态的调用任务。FreeRTOS通过提供具有两种版本的API函数来解决这个问题:

一种版本在任务种使用,另一种版本在ISR中使用。用于ISR的函数会在其名称后附加“FromISR”。注意:不要在ISR中调用名称后面没有“FromISR”后缀的FreeRTOS API函数。

参数xHigherPriorityTaskWoken

        FreeRTOS 中,当 API 函数将阻塞任务唤醒为就绪态且被唤醒任务优先级更高时,任务切换时机取决于 API 调用上下文:任务上下文调用普通 API 且开启抢占式调度时,API 内部会在退出前自动完成高优先级任务切换;中断上下文需调用中断安全的 FromISR 版 API,需先将pxHigherPriorityTaskWoken标记初始化为pdFALSE,API 会在检测到高优先级任务需运行时将该标记设为pdTRUE,多个 FromISR API 可共用此标记,中断末尾需检查标记,若为pdTRUE则调用portYIELD_FROM_ISR()主动触发任务切换,使中断退出后直接运行高优先级任务,否则高优先级任务会延迟至调度器下次运行(如滴答中断)时才被执行。

有以下几个原因导致在API函数的中断安全版本函数内部不会自动发生上下文切换:

  • 避免不必要的上下文切换

        在任务执行处理之前,中断可能会多次执行。例如,考虑这样的场景,任务处理由中断驱动的UART接收的字符串;每次接收到一个字符串,UART的中断服务程序(ISR)都要切换到任务,这将造成浪费,因为只有在接收到完整的字符串后,任务才需要进行处理。

  • 对执行顺序的控制

        中断可能会在不可预测的时间零星地发生。FreeRTOS专家级用户可能希望暂时避免在应用程序的特定点上不可预测的切换到不同的任务,尽管这也可以通过FreeRTOS调度器锁定机制来实现。

  • 可移植性

        这是最简单的机制,可以在全部FreeRTOS移植中使用。

  • 效率

        针对较小处理器架构的移植只允许在ISR的末尾请求上下文切换,而取消这一限制将需要添加更多且更复杂的代码。还允许在同一个ISR中对FreeRTOS API函数进行多次调用,而不会在同一个ISR中产生多次上下文切换请求。

  • 在RTOS滴答中断中执行

        可以将应用程序代码添加到RTOS滴答中断中。在滴答中断内部进行上下文切换的结果取决于所使用的FreeRTOS移植。最好的情况是,在RTOS滴答中断中执行将导致不必要的调度器调用。 pxHigherPriorityTaskWoken参数的使用是可选的。如果不需要,可以将pxHigherPriorityTaskWoken设置为NULL。

示例:中断上下文调用 API

// 按键外部中断服务函数(ISR)
void EXTI0_IRQHandler(void) {
    // 1. 清除中断标志位(硬件必需,避免重复触发中断)
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
    
    // 2. 初始化标记:必须先设为pdFALSE(原文强制要求)
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    
    // 3. 调用中断安全API:释放信号量,唤醒Task_High
    // 第三个参数是标记的指针,API会修改它来告知是否需要切换任务
    xSemaphoreGiveFromISR(xKeySemaphore, &xHigherPriorityTaskWoken);
    
    // (可选)如果ISR调用多个FromISR API,传递同一个标记即可
    // 比如再调用:xQueueSendToBackFromISR(xQueue, &data, &xHigherPriorityTaskWoken);
    
    // 4. 检查标记:如果需要切换,主动请求上下文切换
    if(xHigherPriorityTaskWoken == pdTRUE) {
        // portYIELD_FROM_ISR:FreeRTOS端口层函数,触发中断上下文的任务切换
        // 不同芯片(STM32/ESP32)函数名略有差异,但作用完全一致
        portYIELD_FROM_ISR();
    }
    
    // 5. 退出中断
}

宏portYIELD_FROM_ISR() 和portEND_SWITCHING_ISR()

        taskYIELD()是可以在任务中调用的宏,用于请求上下文切换。portYIELD_FROM_ISR()和portEND_SWITCHING_ISR()是taskYIELD()的中断安全版本。宏portYIELD_FROM_ISR()和portEND_SWITCHING_ISR()的用法相同,功能也相同(历史上,portEND_SWITCHING_ISR()是一个用在FreeRTOS移植中的名字,要求中断处理程序使用汇编代码包装器,而portYIELD_FROM_ISR()也是一个用在FreeRTOS移植中的名字,允许完整的中断处理程序使用C语言编写)。有些FreeRTOS移植只提供这两个宏中的一个。较新的FreeRTOS移植提供了这两个宏。本书中的示例使用portYIELD_FROM_ISR()。这两个宏的代码清单如下所示。

portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

        可以将中断安全版本的API函数传递出来的xHigherPriorityTaskWoken 参数作为调用 portYIELD_FROM_ISR() 宏时直接使用的参数。

        如果 portYIELD_FROM_ISR() 的 xHigherPriorityTaskWoken 参数为 pdFALSE(0),则不会请求上下文切换,而且宏也不会起作用。如果 portYIELD_FROM_ISR() 宏的 xHigherPriorityTaskWoken 参数不是 pdFALSE,那么就会请求上下文切换,处于运行状态的任务可能会改变。即使在中断执行时,运行状态的任务发生了改变,中断也总是会返回到运行状态的任务。

        大多数 FreeRTOS 移植允许在 ISR(中断服务程序)代码的任意位置调用 portYIELD_FROM_ISR()宏。然而,少数 FreeRTOS 移植(主要是那些针对较小架构的移植),只允许在 ISR 代码的最后位置调用 portYIELD_FROM_ISR()宏。

推迟中断处理

通常认为,最好的做法是保持ISR(中断服务程序)尽量短小精悍。这样做的原因如下:

  • 即使给任务分配了很高的优先级,任务也只有在硬件没有服务中断的情况下才会运行。
  • ISR可能会破坏任务的启动时间和执行时间(增加“抖动”)。
  • 取决于FreeRTOS运行的架构,在ISR执行期间,可能无法接收任何新的中断,或者至少无法接收新中断的子集。
  • 编程人员需要考虑资源(如变量、外设和内存缓冲区)被任务和ISR同时访问的后果,并采取相应的保护措施。
  • 一些FreeRTOS移植允许中断嵌套,但中断嵌套会增加复杂性并降低可预测性。中断越短,嵌套的可能性就越小。

        中断服务程序必须记录中断的原因,并清除中断。中断所需的其他处理通常可以在任务中执行,从而允许中断服务程序以尽可能快的方式退出。这被称为“推迟中断处理”,因为中断所需的处理从ISR(中断服务程序)被“推迟”到任务中。将中断处理推迟给任务,也允许编程人员相对于应用程序中的其他任务对处理进行优先级排序,而且能够使用全部的FreeRTOS API函数。

        将中断处理推迟给任务,如果该任务的优先级高于任何其他任务的优先级,则处理将立即执行,就像在ISR内部执行一处理样。这种情况如下图所示,其中Task1是一个正常的应用程序任务,Task2是推迟中断处理任务。

        上图中,中断处理从时间t2开始,并在时间t4有效结束,但只有从时间t2到时间t3的这段时间在ISR(中断服务程序)中执行。如果没有使用延迟中断处理,那么从时间t2到时间t4的整个期间都将在ISR中执行。

        没有绝对的规则来确定,何时在ISR中执行由中断引起的所有处理最佳,或者何时将部分处理推迟到任务中最佳。出现以下情况时,将处理工作推迟到任务具有最佳效果:

  • 中断所需的处理并不简单。例如,如果中断只是存储模数转换的结果,那么几乎可以肯定,这个最好在ISR内部执行,但如果转换结果还必须通过一个软件滤波器,那么最好在任务中执行滤波处理。
  • 中断处理可以方便地执行ISR内部无法执行的操作,如写入控制台,或分配内存。
  • 中断处理不是确定性的—这意味着无法提前知道处理工作需要多长时间。

FreeRTOS二进制信号量

用于同步的二进制信号量

        可以使用二进制信号量API的中断安全版本,在每次发生特定中断时解除对任务的阻塞,从而有效地将任务与中断同步。这允许在同步任务中实现大部分的中断事件处理,而将非常快速和简短部分直接保留在ISR中。

        如果中断处理对时间特别关键,那么可以设置推迟处理任务的优先级,以确保该任务始终抢占系统中的其他任务。然后可以在ISR的实现中包括调用portYIELD_FROM_ISR()宏,确保ISR直接返回到正在推迟中断处理的任务。这确保了整个事件处理在时间上连续执行(不间断),就像它全部在ISR本身中实现一样。

        推迟处理任务使用对信号量的阻塞“获取”调用,来进入阻塞状态以等待事件发生。当事件发生时,ISR对相同的信号量执行“归还”操作,以解除对任务的阻塞,以便进行所需的事件处理。

        “获取信号量”和“归还信号量”的概念,根据使用场景的不同,具有不同的含义。在中断同步场景中,可以把二进制信号量在概念上认为是长度为1的队列。队列在任何时候最多只能包含一个数据项,因此它要么空或要么满(据此理解为二进制)。

        通过调用xSemaphoreTake() API函数,接收推迟中断处理的任务有效地尝试从队列中读取带有阻塞时间的数据项,如果队列为空,则使任务进入阻塞状态。当事件发生时,ISR使用xSemaphoreGiveFromISR()函数将令牌(即信号量)放入队列中,使队列变满。这将导致任务退出阻塞状态并移除令牌,再次使队列为空。当任务完成其处理后,再次尝试从队列中读取,发现队列为空后,重新进入阻塞状态以等待下一个事件。下图展示了这一序列。

函数xSemaphoreCreateBinary()

        FreeRTOS还包括 xSemaphoreCreateBinaryStatic() API函数,该函数在编译时静态分配创建二进制信号量所需的内存。将 FreeRTOS中各种类型信号量的句柄都存储在SemaphoreHandle_t 类型的变量中。

        在使用信号量之前,必须先创建它。要创建二进制信号量,可以使用 xSemaphoreCreateBinary() API 函数(注意有些信号量 API 函数实际上是宏,而不是函数。为简便起见,教程中统称它们为函数)。

xSemaphoreCreateBinary () API 函数的原型如下所示:

SemaphoreHandle_t xSemaphoreCreateBinary( void );

xSemaphoreCreateBinary() API函数的返回值说明如下所示:

参数名称 参数说明
返回值 如果返回 NULL,就不能创建信号量,因为没有足够的堆内存供FreeRTOS分配信号量数据结构。如果返回非 NULL 值,则表示信号量已成功创建。返回的值应存储为已创建信号量的句柄。

        该函数创建一个二进制信号量,并返回一个可以引用该信号量的句柄。如果需要在FreeRTOS中使用该函数,必须在 FreeRTOSConfig.h 文件中将configSUPPORT_DYNAMIC_ALLOCATION设置为 1,或保留未定义状态(此时默认为1)。

        二值信号量并不需要在得到后立即归还,因此,任务同步可以通过一个任务/中断持续归还信号量,而另外一个持续获得信号量来实现。

xSemaphoreCreateBinary () API 函数的使用示例如下所示:

SemaphoreHandle_t xSemaphore; // 信号量句柄

void vATask( void * pvParameters )
{
    /* 尝试创建一个信号量。 */
    xSemaphore = xSemaphoreCreateBinary();

    if( xSemaphore == NULL )
    {
        /* FreeRTOS 的可用堆内存不足以成功创建信号量。 */
    }
    else
    {
        /* 现在可以使用信号量了。它的句柄存储在 xSemaphore 变量中。在这个位置上,调用
        xSemaphoreTake() 函数来获取信号量会失败,直到信号量首先被给出(归还)。 */
    }
}

        在上述代码中,vATask函数试图创建一个二进制信号量,并将其句柄存储在xSemaphore 变量中。如果 xSemaphoreCreateBinary() 返回 NULL,则说明没有足够的 FreeRTOS 堆内存来创建信号量。如果成功创建了信号量(xSemaphore 不是 NULL),那么该信号量就可以被使用。但是,在信号量首次被归还(通过 xSemaphoreGive())之前,调用 xSemaphoreTake() 来获取信号量将会失败。

        每个二进制信号量需要少量RAM,用于保持信号量的状态。如果使用 xSemaphoreCreateBinary() 创建二进制信号量会从FreeRTOS堆中自动分配所需的RAM 。如果使用 xSemaphoreCreateBinaryStatic() 创建二进制信号量,那么RAM由编程人员提供,这需要用到一个附加参数,但允许在编译时静态分配 RAM。

xSemaphoreCreateBinaryStatic() API函数的原型如下:

SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
                          StaticSemaphore_t *pxSemaphoreBuffer );

        注意:参数pxSemaphoreBuffer必须指向 StaticSemaphore_t 类型的变量,该变量将用于保存信号量的状态。xSemaphoreCreateBinaryStatic () API 函数的使用示例如下所示:

SemaphoreHandle_t xSemaphore = NULL; // 信号量句柄,初始化为NULL
StaticSemaphore_t xSemaphoreBuffer;  // 用于静态存储信号量数据结构的缓冲区

void vATask( void * pvParameters )
{
    /* 创建一个二进制信号量,不使用任何动态内存分配。信号量的数据结构将被保存在
    xSemaphoreBuffer 变量中。 */
    xSemaphore = xSemaphoreCreateBinaryStatic( &xSemaphoreBuffer );
    /* 因为 pxSemaphoreBuffer 不是 NULL,所以期望句柄也不会是 NULL。 */
    configASSERT( xSemaphore );

    /* 任务的其余代码放在这里。 */
}

        函数vATask试图使用静态内存分配创建一个二进制信号量。xSemaphoreCreateBinaryStatic ()函数使用 xSemaphoreBuffer 变量来存储信号量的内部数据结构,而不需要动态分配内存。创建成功后,信号量的句柄会被存储在 xSemaphore 中。通过 configASSERT 宏,代码确保了 xSemaphore 不是 NULL,即信号量成功创建。如果创建失败,configASSERT 可能会触发一个断言,这通常用于调试阶段以发现可能的错误。

        信号量是在“空”状态下创建的,这意味着必须先用xSemaphoreGive() API 函数给出信号量, 然后才能使用 xSemaphoreTake() 函数来获取(获得)该信号量。

函数xSemaphoreTake()

        “获取”信号量意味着“获得”或“接收”信号量。信号量只有在可用的情况下,才能够被获取。

        除了递归互斥量,FreeRTOS中其他类型的信号量都可以使用 xSemaphoreTake() API函数来“获取”。需要注意,不能在中断服务程序中使用xSemaphoreTake() API函数。

xSemaphoreTake () API 函数的原型如下所示:

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, 
TickType_t xTicksToWait );

xSemaphoreTake() 函数的返回值说明如下所示:

参数名称 参数说明
xSemaphore 被“获取”的信号量。信号量由SemaphoreHandle_t 类型的变量引用。在使用信号量之前,必须先明确地创建信号量。
xTicksToWait 如果信号量当前不可用,任务应保持在阻塞状态以等待信号量的最长时间。如果 xTicksToWait 为0,那么SemaphoreTake()函数将在信号量不可用时立即返回。
返回值 如果函数成功获取信号量,则返回 pdTRUE。如果函数在等待时间内未能获取信号量,则返回 pdFALSE。

        需要注意,xSemaphoreTake ()函数在使用之前,必须通过调用xSemaphoreCreateBinary()、xSemaphoreCreateMutex() 或 xSemaphoreCreateCounting()等API函数来创建信号量。

xSemaphoreTake () API 函数的使用示例如下所示:

SemaphoreHandle_t xSemaphore = NULL;
/* 创建一个信号量的任务。 */
void vATask( void * pvParameters )
{
  /* 创建一个信号量以保护共享资源。因为我们使用信号量来实现互斥,所以我们创建
  一个互斥信号量而不是二进制信号量。 */
  xSemaphore = xSemaphoreCreateMutex();
}
/* 使用信号量的任务。 */
void vAnotherTask( void * pvParameters )
{
  /* ... 执行其他任务。 */

  if( xSemaphore != NULL )
  {
    /* 检查我们是否可以获取信号量。如果信号量不可用,则等待10个时钟周期以
    查看它是否变得可用。 */
    if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
    {
      /* 我们能够获取信号量,现在可以访问共享资源了。 */

      /* ... */

      /* 我们已经完成了对共享资源的访问。归还信号量。 */
      xSemaphoreGive( xSemaphore );
    }
    else
    {
      /* 我们无法获取信号量,因此无法安全地访问共享资源。 */
    }
  }
}    

函数xSemaphoreGiveFromISR()

        可以使用 xSemaphoreGiveFromISR() API函数来“归还”二进制信号量和计数信号量。xSemaphoreGiveFromISR() API函数是 xSemaphoreGive() API函数的中断安全版本,因此它具有在本章开头描述的 pxHigherPriorityTaskWoken 参数。

xSemaphoreGiveFromISR () API 函数的原型如下所示:

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
                                  BaseType_t *pxHigherPriorityTaskWoken );

xSemaphoreGiveFromISR() 函数的返回值说明如下所示:

参数名称 参数说明
xSemaphore 被“归还”的信号量。信号量由SemaphoreHandle_t 类型的变量引用。在使用信号量之前,必须先明确地创建信号量。
pxHigherPriorityTaskWoken 如果信号量的优先级高于当前运行的任务的优先级,则该参数的值为 pdTRUE。如果信号量的优先级低于或等于当前运行的任务的优先级,则该参数的值为 pdFALSE。
返回值 如果函数成功获取信号量,则返回 pdTRUE。如果函数在等待时间内未能获取信号量,则返回 pdFALSE。

xSemaphoreGiveFromISR() 函数的使用示例如下所示:

#define LONG_TIME 0xffff
#define TICKS_TO_WAIT    10

SemaphoreHandle_t xSemaphore = NULL;

/* 重复任务。 */
void vATask( void * pvParameters )
{
  /* 我们正在使用信号量进行同步,所以我们创建了一个二进制信号量而不是互斥锁。
  我们必须确保在信号量被创建之前,中断不会尝试使用它! */
  xSemaphore = xSemaphoreCreateBinary();

  for( ;; )
  {
    /* 我们希望这个任务每10个定时器滴答运行一次。在任务开始之前,信号量就
    已经被创建了。等待信号量变得可用(即阻塞等待)。 */
    if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE )
    {
      /* 执行时间到了。 */

      ...

      /* 我们已经完成了任务。返回到循环的顶部,我们将在那里阻塞等待信号量,
      直到再次到了执行的时间。请注意,当以这种方式使用信号量与中断服务程
      序进行同步时,无需“归还”信号量。 */
    }
  }
}

/* 定时器中断服务程序(ISR:Interrupt Service Routine) */
void vTimerISR( void * pvParameters )
{
  static unsigned char ucLocalTickCount = 0;
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;

  /* 一个定时器滴答(tick)已经发生。 */

  /*... 执行其他时间相关功能。 */

  /* 是时候运行 vATask() 了吗? */
  xHigherPriorityTaskWoken = pdFALSE;
  ucLocalTickCount++;
  if( ucLocalTickCount >= TICKS_TO_WAIT )
  {
    /* 通过归还信号量来解锁任务。 */
    xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );

    /* 重置计数器,以便我们在10个时钟周期后再次归还信号量。 */
    ucLocalTickCount = 0;
  }

  /* 如果 xHigherPriorityTaskWoken 为真,则放弃(即让出CPU使用权)。
  这里实际使用的宏是特定于端口的。 */
  portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

FreeRTOS计数型信号量的使用

        可以将二进制信号量视为长度为1的队列一样,也可以将计数信号量被视为长度大于1的队列。任务并不关心队列中存储的数据,只关心队列中数据项的数量。如果需要使用计数信号量,必须将FreeRTOSConfig.h文件中的configUSE_COUNTING_SEMAPHORES设置为1。

        每次对计数信号量执行“归还”操作时,都会使用其队列中的一个空间。队列中数据项的数量即为信号量的“计数”值。通常将计数信号量用于以下两种情况:

  • 计数事件

        在此场景中,每当发生一个事件时,事件处理程序将“归还”一个信号量,从而导致信号量的计数值在每次“归还”时递增。每当任务处理一个事件时,它将“获取”一个信号量,从而导致信号量的计数值在每次“获取”时递减。计数值是已发生事件数量与已处理事件数量之间的差值。此机制如下图使用计数信号量来“计数”事件的机制所示。

用于计数事件的计数信号量在创建时具有初始计数值0。

  • 资源管理

        在此场景中,计数值表示可用资源的数量。为了获得对资源的控制权,任务必须首先“获取”信号量,这将使信号量的计数值递减。当计数值达到0时,表示没有可用资源。当任务完成对资源的使用后,它会“归还”信号量,这将使信号量的计数值递增。

用于管理资源的计数信号量在创建时,其初始计数值等于可用资源的数量。

函数xSemaphoreCreateCounting()

        FreeRTOS还包括xSemaphoreCreateCountingStatic() API函数,该函数在编译时静态地分配创建计数信号量所需的内存。将FreeRTOS中各种类型信号量的句柄都存储在SemaphoreHandle_t类型的变量中。 在使用信号量之前,必须先创建它。要创建计数信号量,可以使用xSemaphoreCreateCounting() API函数。函数的原型如下所示:

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                         UBaseType_t uxInitialCount );

xSemaphoreCreateCounting () API函数的参数与返回值说明如下所示:

参数名称 参数说明
uxMaxCount 信号量的最大计数值。仍然用队列作类比,uxMaxCount 的值实际上就是队列的长度。当使用信号量用于计数或锁定事件时,uxMaxCount 是可以锁定的最大事件数量。当信号量用于管理对一系列资源的访问时,uxMaxCount 应设置为可用资源的总数。
uxInitialCount 信号量的初始计数值。当信号量用于计数或锁定事件时,uxInitialCount 应设置为0;当信号量用于管理对一系列资源的访问时,uxInitialCount 应设置为等于 uxMaxCount。
返回值 如果返回NULL,则无法创建信号量,因为FreeRTOS没有足够的堆内存分配信号量数据结构。如果返回非NULL值,则表示已成功创建信号量。应该将返回值作为已创建的信号量的句柄存储起来。

xSemaphoreCreateCounting () API 函数的使用示例如下所示:

void vATask( void * pvParameters )
{
  SemaphoreHandle_t xSemaphore;

  /* 创建一个计数信号量,其最大计数值为10,初始计数值为0。 */
  xSemaphore = xSemaphoreCreateCounting( 10, 0 );

  if( xSemaphore != NULL )
  {
    /* 信号量已成功创建。 */
  }
}

二进制信号量与计数信号量的异同点

        相同点:两者都是 FreeRTOS 中的同步机制,都用于任务间的同步和资源管理;任务获取和归还信号量的操作概念类似,都有对应的创建、获取和归还函数。

        不同点:二进制信号量可视为长度为 1 的队列,计数信号量视为长度大于 1 的队列。用途上,二进制信号量常用于任务与中断同步、简单的互斥等场景;计数信号量更适用于计数事件和管理多个相同资源的场景。创建时,二进制信号量无需设置最大计数值和初始计数值(默认初始值为空);计数信号量需要设置最大计数值和合适的初始计数值,根据不同场景(计数事件或资源管理)初始计数值设置不同。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐