zoukankan      html  css  js  c++  java
  • Freertos学习:08-信号量

    --- title: rtos-freertos-08-ipc-semaphore date: 2020-06-23 11:01:12 categories: tags: - freertos - ipc ---

    章节概述:

    介绍 FreeRTOS中的任务间通讯机制:信号量的使用(尽管FreeRTOS中没有进程的概念,但为了统一,我们还是以进程间通讯(IPC)的说法)

    介绍

    todo : 信号量实际上的使用(因为找不到对应的函数)

    FreeRTOS的信号量包括:二值信号量、计数信号量、互斥量(互斥信号量) 与 递归互斥量。

    我们可以把互斥量和递归互斥量看成特殊的信号量。

    互斥量和信号量在用法上不同:

    • 信号量用于同步,任务间或者任务和中断间同步;互斥量用于互锁,用于保护同时只能有一个任务访问的资源,为资源上一把锁。
    • 信号量用于同步时,一般是一个任务(或中断)给出信号,另一个任务获取信号;互斥量必须在同一个任务中获取信号、同一个任务给出信号。
    • 互斥量具有优先级继承,信号量没有。
    • 互斥量不能用在中断服务程序中,信号量可以。

    信号量这里大多数应用都是应用在任务同步中,特别对于中断同步来说。对于有芯片编程的经验的朋友应该都知道,中断函数中我们尽可能是少做事情,这样CPU的工作效率以及整体的性能都会得到提升。信号量在系统应用中就多用在中断程序与任务之间的同步中。

    学过Linux驱动的学者,肯定对于中断上下文(Interrupt context)非常熟悉:为了在中断执行时间尽可能短和中断处理需完成大量工作之间找到一个平衡点,Linux 将中断处理程序分解为两个半部:顶半部(top half)和底半部(bottom half)。

    • 中断顶半部: 触发中断开始到进入中断处理程序的那部分中断响应程序。

    • 中断底半部: 执行中断处理程序的那部分。

    为了尽快地处理中断响应,顶半部完成尽可能少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态并清除中断标志后就进行“登记中断”的工作,“登记中断”意味着将底半部处理程序挂到该设备的下半部执行队列中去。这样,顶半部执行的速度就会很快,可以服务更多的中断请求。,以免影响系统的性能。

    当然了,如果中断要处理的工作本身很少,则完全可以直接在顶半部全部完成。

    注意:

    • 信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。
    • 二值信号量、计数信号量和互斥量信号量的创建API函数是独立的,但是获取和释放API函数都是相同的;递归互斥信号量的创建、获取和释放API函数都是独立的。
    • 互斥量和二值信号量都是SemaphoreHandle_t类型,并且可以用于任何具有这类参数的API函数中。

    二值信号量

    既可以用于互斥功能也可以用于同步功能。

    当任务企图获取一个无效信号量时,任务进入阻塞状态,阻塞时间用来确定任务进入阻塞的最大时间。如果有多个任务阻塞在同一个信号量上,那么当信号量有效时,具有最高优先级别的任务最先解除阻塞。

    注:在大部分应用场合,任务通知都可以代替二值信号量,并且速度更快、生成的代码更少。

    假定现在需要有一个任务用来维护外设,如果使用轮询的方法会浪费CPU资源并且妨碍其它任务执行。

    可以使用二值信号量实现这种应用:当任务调用xSemaphoreTake()取信号量时,因为此时尚未发生特定事件,信号量为空,任务会进入阻塞状态;当外设需要维护时,触发一个中断服务程序,该中断服务仅仅使用API函数xSemaphoreGiveFromISR()给出信号量(生产者)。任务被唤醒,取走信号量并执行任务(消费者)。

    因此,在二值信号量的支持下,任务的大部分时间处于阻塞状态(允许其它任务执行),直到某些事件发生该任务才执行。这样也使得中断执行尽可能的短,其它处理过程可以在任务中实现。

    计数信号量

    通常计数信号量用于下面两种事件:

    • 计数事件:每当事件发生,程序将给出一个信号(信号量计数值增1),当处理事件时,处理程序会取走信号量(信号量计数值减1)。因此,计数值是事件发生的数量和事件处理的数量差值。在这种情况下,计数信号量在创建时其值为0。
    • 资源管理:计数值表示有效的资源数目。任务必须先获取信号量才能获取资源控制权。当计数值减为零时表示没有的资源。当任务完成后,它会返还信号量(信号量计数值增加)。在这种情况下,信号量创建时,计数值等于最大资源数目。

    注:在大部分应用场合,任务通知都可以代替计数信号量,并且速度更快、生成的代码更少。

    互斥量

    互斥量和信号量使用相同的TakeGiveAPI函数,因此互斥量也允许指定一个阻塞时间。

    互斥量是一个包含优先级继承机制的二值信号量;用于实现同步(任务之间或者任务与中断之间)的话,二值信号量是更好的选择,互斥量用于简单的互锁。

    用于互锁的互斥量可以充当保护资源的令牌。当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问同一资源。

    互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制。也就是说,如果一个互斥量(令牌)正在被一个低优先级任务使用,此时一个高优先级企图获取这个互斥量,高优先级任务会因为得不到互斥量而进入阻塞状态,正在使用互斥量的低优先级任务会临时将自己的优先级提升,提升后的优先级与与进入阻塞状态的高优先级任务相同。这个优先级提升的过程叫做优先级继承。

    这个机制用于确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”影响降低到最小。

    “优先级翻转”:在很多场合中,某个硬件资源只有一个,当低优先级任务占用该资源的时候,即便高优先级任务也只能乖乖等待低优先级任务释放资源。此时,高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。

    优先级继承不能解决优先级反转,只能将这种情况的影响降低到最小。硬实时系统在一开始设计时就要避免优先级反转发生。

    为什么优先级继承能够降低优先级翻转的影响呢?

    举个例子,现在有任务A、任务B和任务C;三个任务的优先级顺序为任务C > 任务B > 任务A。

    任务A和任务C都要使用某一个硬件资源,并且当前低优先级的任务A占有该资源。

    先看没有优先级继承的情况:任务C也要使用该资源,但是此时任务A正在使用这个资源,因此任务C进入阻塞,此时三个任务的优先级顺序没有发生变化。在任务C进入阻塞之后,某硬件产生了一次中断,唤醒了一个事件,该事件可以解除任务B的阻塞状态。在中断结束后,因为任务B的优先级是大于任务A的,所以任务B抢占任务A的CPU权限。那么任务C的阻塞时间就至少为:中断处理时间+任务B的运行时间+任务A的运行时间。

    再看有优先级继承的情况:任务C也要使用该资源,但是此时任务A正在使用这个资源,因此任务C进入阻塞,此时由于优先级A会继承任务C的优先级,三个任务的优先级顺序发生了变化,新的优先级顺序为:任务C=任务A>任务B。在任务C进入阻塞之后,某硬件产生了一次中断,唤醒了一个事件,该事件可以解除任务B的阻塞状态。在中断结束后,因为任务A的优先级临时被提高,大于任务B的优先级,所以任务A继续获得CPU权限。任务A完成后,处于高优先级的任务C会接管CPU。所以任务C的阻塞时间为:中断处理时间+任务A的运行时间。看,任务C的阻塞时间变小了,这就是优先级继承的优势。

    简单来讲,优先级继承减少了介于中间优先级的任务运行时间(例如这里的任务B)

    递归互斥量

    已经获取递归互斥量的任务可以重复获取该递归互斥量。使用xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态。

    比如,某个任务成功获取5次递归互斥量,那么在它没有返还5次该递归互斥量之前,这个互斥量对别的任务无效。

    递归互斥量可以看成带有优先级继承机制的信号量,获取递归互斥量的任务在用完后必须返还。

    互斥量不能用在中断服务程序中,这是因为:

    • 互斥量具有优先级继承机制,只有在任务中获取或给出互斥才有意义。
    • 中断不能因为等待互斥量而阻塞。

    API

    FreeRTOS的信号量包括二值信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。我们可以把互斥量和递归互斥量看成特殊的信号量。

    信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。

    二值信号量、计数信号量和互斥量信号量的创建API函数是独立的,但是获取和释放API函数都是相同的;递归互斥信号量的创建、获取和释放API函数都是独立的。

    创建二值信号量

    SemaphoreHandle_t  xSemaphoreCreateBinary( void );
    

    描述:用于创建一个二值信号量。

    返回值:

    • NULL:创建信号量失败,因为FreeRTOS堆栈不足。
    • 其它值:信号量创建成功。这个返回值存储着信号量句柄。

    例子:

    SemaphoreHandle_t xSemaphore;
     
    void vATask( void * pvParameters )
    {
        /* 创建信号量 */
       xSemaphore = xSemaphoreCreateBinary();
     
       if( xSemaphore == NULL )
       {
           /* 因堆栈不足,信号量创建失败,这里进行失败处理*/
       }
       else
       {
           /* 信号量可以使用。信号量句柄存储在变量xSemahore中。
              如果在这里调用API函数xSemahoreTake()来获取信号量,
              则必然是失败的,因为创建的信号量初始是无效(空)的。*/
       }
    }
    

    创建计数信号量

    SemaphoreHandle_t xSemaphoreCreateCounting (UBaseType_t uxMaxCount, 
                                                UBaseType_t uxInitialCount );
    

    描述:创建计数信号量

    参数解析:

    • uxMaxCount:最大计数值,当信号到达这个值后,就不再增长了。
    • uxInitialCount:创建信号量时的初始值。

    返回值:NULL表示信号量创建失败,否则返回信号量句柄。

    例子:

    void vATask( void * pvParameters )
     {
         xSemaphoreHandle xSemaphore;
     
         // 必须先创建信号量,才能使用它
         // 信号量可以计数的最大值为10,计数初始值为0.
         xSemaphore = xSemaphoreCreateCounting( 10, 0 );
     
         if( xSemaphore != NULL )
         {
             // 信号量创建成功
             // 现在可以使用信号量了。
         }
     }
    

    创建互斥量

    SemaphoreHandle_t xSemaphoreCreateMutex( void );
    

    描述:创建互斥量。

    返回值:NULL表示信号量创建失败,否则返回信号量句柄。

    例子:

    xSemaphoreHandle xSemaphore;
     
    voidvATask( void * pvParameters )
    {
        // 互斥量在未创建之前是不可用的
        xSemaphore = xSemaphoreCreateMutex();
        if( xSemaphore != NULL )
        {
            // 创建成功
            // 在这里可以使用这个互斥量了 
        }
    }
    

    创建递归互斥量

    SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
    

    描述:创建递归互斥量。

    返回值: NULL表示互斥量创建失败,否则返回互斥量句柄。

    例子:

    xSemaphoreHandle xMutex;
     
    void vATask( void * pvParameters )
    {
        // 互斥量未创建前是不能被使用的
        xMutex = xSemaphoreCreateRecursiveMutex();
     
        if( xMutex != NULL )
        {
            // 创建成功
            // 在这里创建互斥量
        }
    }
    

    删除信号量

    void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
    

    描述:删除信号量。如果有任务阻塞在这个信号量上,则这个信号量不要删除。

    参数解析:信号量句柄

    获取信号量

    BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
    
    // 中断保护
    BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,  
                                     signedBaseType_t *pxHigherPriorityTaskWoken);
    

    描述:获取信号量。

    参数解析:

    • xSemaphore:信号量句柄。信号量必须是通过API函数xSemaphoreCreateBinary()xSemaphoreCreateCounting()xSemaphoreCreateMutex()预先创建过的。
    • xTickToWait:信号量无效时,任务最多等待的时间,单位是系统节拍周期个数。使用宏portTICK_PERIOD_MS可以辅助将系统节拍个数转化为实际时间(以毫秒为单位)。如果设置为0,表示不是设置等待时间。如果INCLUDE_vTaskSuspend设置为1,并且参数xTickToWait为portMAX_DELAY则可以无限等待。
    • pxHigherPriorityTaskWoken:如果*pxHigherPriorityTaskWoken为pdTRUE,则需要在中断退出前手动进行一次上下文切换。从FreeRTOS V7.3.0开始,该参数为可选参数,并可以设置为NULL。

    返回值:成功获取到信号量返回pdTRUE,否则返回pdFALSE。

    例子:

    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
            {
                /* 没有获取到信号量,这里处理异常情况。*/
            }
        }
    }
    

    获取递归互斥量

    BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, 
                                       TickType_t xTicksToWait );
    

    描述:获取递归互斥信号量。

    文件FreeRTOSConfig.h中的宏configUSE_RECURSIVE_MUTEXES必须设置成1,此函数才有效。

    参数解析:

    • xMutex:互斥量句柄,必须是使用API函数xSemaphoreCreateRecursiveMutex()返回的。
    • xTickToWait:互斥量无效时,任务最多等待的时间,单位是系统节拍周期个数。使用宏portTICK_PERIOD_MS可以辅助将系统节拍个数转化为实际时间(以毫秒为单位)。如果设置为0,表示不是设置等待时间。如果任务已经拥有信号量则xSemaphoreTakeRecursive()立即返回,不管xTickToWait是什么值。

    返回值:成功获取递归互斥量返回pdTURE,否则返回pdFALSE。

    例子:

    SemaphoreHandle_t xMutex = NULL;
     
    // 这个任务创建互斥量
    void vATask( void *pvParameters )
    {
        // 这个互斥量用于保护共享资源
        xMutex =xSemaphoreCreateRecursiveMutex();
    }
     
    //这个任务使用互斥量
    void vAnotherTask( void *pvParameters )
    {
        // ... 做其它事情.
     
        if( xMutex != NULL )
        {
            // 如果互斥量无效,则最多等待10系统时钟节拍周期.   
            if(xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
            {
                // 到这里我们成功获取互斥量并可以访问共享资源了
     
                // ...
                // 由于某种原因,某些代码需要在一个任务中多次调用API函数
                // xSemaphoreTakeRecursive()。当然不会像本例中这样连续式
                //调用,实际代码会有更加复杂的结构
                xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
                xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
     
                // 我们获取一个互斥量三次,所以我们要将这个互斥量释放三次
                //它才会变得有效。再一次说明,实际代码可能会更加复杂。
                xSemaphoreGiveRecursive( xMutex );
                xSemaphoreGiveRecursive( xMutex );
                xSemaphoreGiveRecursive( xMutex );
     
                // 到这里,这个共享资源可以被其它任务使用了.
            }
            else
            {
                // 处理异常情况
            }
        }
    }
    

    释放信号量

    BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore );
    
    // 中断保护
    BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,
                          signed BaseType_t *pxHigherPriorityTaskWoken );
    

    描述:释放一个信号量。

    必须使用API函数xSemaphoreTake()获取这个信号量。

    参数解析:

    • xSemaphore:信号量句柄。信号量必须是API函数xSemaphoreCreateBinary()、xSemaphoreCreateCounting()或xSemaphoreCreateMutex() 创建的。
    • pxHigherPriorityTaskWoken:如果pxHigherPriorityTaskWoken为pdTRUE,则需要在中断退出前人为的进行一次上下文切换。从FreeRTOS V7.3.0开始,该参数为可选参数,并可以设置为NULL。

    返回值:信号量释放成功返回pdTRUE,否则返回pdFALSE。

    例子:

    SemaphoreHandle_t xSemaphore = NULL;
     
    voidvATask( void * pvParameters )
    {
       // 创建一个互斥量,用来保护共享资源
       xSemaphore = xSemaphoreCreateMutex();
     
        if( xSemaphore != NULL )
        {
             if( xSemaphoreGive( xSemaphore ) != pdTRUE )
             {
                  //我们希望这个函数调用失败,因为首先要获取互斥量
             }
             // 获取信号量,不等待
             if( xSemaphoreTake( xSemaphore, ( TickType_t ) 0 ) )
             {
                  // 现在我们拥有互斥量,可以安全的访问共享资源
                  if( xSemaphoreGive( xSemaphore ) != pdTRUE )
                  {
                       //我们不希望这个函数调用失败,因为我们必须
                       //要释放已获取的互斥量
                  }
             }
         }
    }
    
    // xSemaphoreGiveFromISR 例子
    #define LONG_TIME 0xffff
    #define TICKS_TO_WAIT    10
     
    SemaphoreHandle_t xSemaphore = NULL;
     
    /* Repetitive task. */
    void vATask( void * pvParameters )
    {
        /* 我们使用信号量同步,所以先创建一个二进制信号量.必须确保
           在创建这个二进制信号量之前,中断不会访问它。*/
        xSemaphore = xSemaphoreCreateBinary();
     
        for( ;; )
        {
            /* 我们希望每产生10次定时器中断,任务运行一次。*/
            if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE )
            {
                /* 到这里成功获取到信号量*/
     
                ...
     
               /* 我们成功执行完一次,由于这是个死循环,所以任务仍会
                   阻塞在等待信号量上。信号量由ISR释放。*/
            }
        }
    }
     
    /* 定时器 ISR */
    void vTimerISR( void * pvParameters )
    {
        static unsigned char ucLocalTickCount = 0;
        static signed BaseType_txHigherPriorityTaskWoken;
     
        /*定时器中断发生 */
     
          ...执行其它代码
     
        /*需要vATask() 运行吗? */
        xHigherPriorityTaskWoken = pdFALSE;
        ucLocalTickCount++;
        if( ucLocalTickCount >= TICKS_TO_WAIT )
        {
            /* 释放信号量,解除vATask任务阻塞状态 */
            xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
     
            /* 复位计数器 */
            ucLocalTickCount = 0;
        }
     
        /* 如果 xHigherPriorityTaskWoken 表达式为真,需要执行一次上下文切换*/
        portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    }
    

    释放递归互斥量

    BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex );
    

    描述:释放一个递归互斥量。

    文件FreeRTOSConfig.h中宏configUSE_RECURSIVE_MUTEXES必须设置成1本函数才有效。

    参数解析:

    xMutex:互斥量句柄。必须是函数xSemaphoreCreateRecursiveMutex()返回的值。

    返回值:如果递归互斥量释放成功,返回pdTRUE。

    获取互斥量持有任务的句柄

    TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );
    

    描述:返回互斥量持有任务的句柄(如果有的话)

    文件FreeRTOSConfig.h中宏configUSE_MUTEXES必须设置成1本函数才有效。

    参数解析:

    xMutex:互斥量句柄

    返回值:返回互斥量持有任务的句柄(如果有的话)。

    以下情况返回NULL:

    • 参数xMutex不是互斥类型信号量
    • 虽然互斥量有效但这个互斥量不被任何任务持有

    注意:如果调用此函数的任务持有互斥量,那么能够可靠地返回任务句柄,但是如果是别的任务持有互斥量,则不总是可靠。

  • 相关阅读:
    【记录】20060430 11:30:00 本Blog访问量突破100000 !
    ESFramework介绍之(24)―― 日志记录IEsbLogger
    ESFramework介绍之(20)―― 插件自动升级
    ESFramework介绍之(21)-- Tcp组件接口ITcp介绍
    ESFramework介绍之(17)―― 支持漫游用户和跨区域功能请求
    python标准库学习9
    python标准库学习5 bisect — Array bisection algorithm
    C++中四种显示类型转换总结
    BeautifulSoup学习笔记
    jdbc连接数据库(水文)
  • 原文地址:https://www.cnblogs.com/schips/p/rtos-freertos-08.html
Copyright © 2011-2022 走看看