精华内容
下载资源
问答
  • 信号量的作用 优先级翻转现象 uCOS中的特殊信号量——互斥信号量 本文作为一个学习uCOS的经验分享,希望能给初学小白们一个参考。以例程和运行效果来说明,对一些概念性的东西这里不做过多解释,网上...
    1. 信号量的作用

    2. 优先级翻转现象

    3. uCOS中的特殊信号量——互斥信号量


    本文作为一个学习uCOS的经验分享,希望能给初学小白们一个参考。以例程和运行效果来说明,对一些概念性的东西这里不做过多解释,网上相关文章多如牛毛。本文中的实验需要一些uCOS相关的前置知识,比如OS初始化,任务创建等。

    测试前准备:

    1、串口一个,当做可被抢占的资源。

    2、准备好三个用户任务,设置优先级为高、中、低(具体值自己设置,比如5、10、15),按照优先级命名函数。

    3、创建一个信号量。

    创建信号量的代码:

    OS_EVENT *testsem;
    
    testsem = OSSemCreate(1);

    任务代码分别为:

    void TaskHigh(void *p_arg)
    {
        u8 err;
        
        while(1)
        {
            OSSemPend(testsem, 0, &err); //申请(等待)信号量
    
            printf("%c",'T');delay_ms(10);printf("%c",'a');delay_ms(10);
            printf("%c",'s');delay_ms(10);printf("%c",'k');delay_ms(10);
            printf("%c",'H');delay_ms(10);printf("%c",'i');delay_ms(10);
            printf("%c",'g');delay_ms(10);printf("%c",'h');delay_ms(10);
            printf("%c",' ');delay_ms(10);printf("%c",'g');delay_ms(10);
            printf("%c",'e');delay_ms(10);printf("%c",'t');delay_ms(10);
            printf("%c",' ');delay_ms(10);printf("%c",'t');delay_ms(10);
            printf("%c",'h');delay_ms(10);printf("%c",'e');delay_ms(10);
            printf("%c",' ');delay_ms(10);printf("%c",'c');delay_ms(10);
            printf("%c",'o');delay_ms(10);
            printf("%c",'m');printf("%c",'\r');printf("%c",'\n');
            
            OSSemPost(testsem);
            OSTimeDly(1);
        }
    }
    
    
    void TaskMedium(void *pdata)
    {
        while(1)
        {
            printf("_\r\n");
    
            OSTimeDly(10);
        }
    }
    void TaskLow(void *p_arg)
    {
        u8 err;
        
        while(1)
        {
            OSSemPend(testsem, 0, &err); //申请(等待)信号量,不设置超时。
    
    
            printf("%c",'T');delay_ms(10);printf("%c",'a');delay_ms(10);
            printf("%c",'s');delay_ms(10);printf("%c",'k');delay_ms(10);
            printf("%c",'L');delay_ms(10);printf("%c",'o');delay_ms(10);
            printf("%c",'w');delay_ms(10);printf("%c",' ');delay_ms(10);
            printf("%c",'g');delay_ms(10);printf("%c",'e');delay_ms(10);
            printf("%c",'t');delay_ms(10);printf("%c",' ');delay_ms(10);
            printf("%c",'t');delay_ms(10);printf("%c",'h');delay_ms(10);
            printf("%c",'e');delay_ms(10);printf("%c",' ');delay_ms(10);
            printf("%c",'c');delay_ms(10);printf("%c",'o');delay_ms(10);
            printf("%c",'m');printf("%c",'\r');printf("%c",'\n');
                    
            OSSemPost(testsem); //释放信号量
            OSTimeDly(1);
        }
    }

    说明一下这么设计的意图,为了更好观察相关的现象,选择串口作为公共资源比设置一个变量数组更加直观,被抢占后能直接打印当前任务的字符串。代码中使用了阻塞延时是为了延迟任务的执行速度,另一个角度说就是能更明显的看到被抢占的现象。


    • 信号量的作用---------------------------------

    简单归纳为,对于一些资源,在使用时可能会被打扰或被抢占,而我又不想被抢占,这时就需要有个规则或约定来防止这种事情发生,这个规则或约定就是信号量。

    直接进入正题。

    • 先看看没信号量情况下的现象。

    测试条件:不创建TaskMedium,只保留TaskLow、TaskHigh,在TaskLow、TaskHigh 中屏蔽OSSemPend() 和OSSemPost()。

    运行效果如下:

    红圈多出来的一个字符就是TaskLow任务需要打印的字符串。由此可知TaskLow任务在使用串口时,被优先级高的TaskHigh给抢去了。如果不想被抢走就需要用到信号量。

    • 接下来看看使用信号量后的现象。

    测试条件:不创建TaskMedium,只保留TaskLow、TaskHigh。在TaskLow、TaskHigh 中使用OSSemPend() 和OSSemPost()。

    运行效果如下:

    使用信号量后, TaskLow任务能完整打印字符串,说明在使用串口时不会被抢走了。

    需要注意的是信号量可以分为二值信号量和计数信号量。计数信号量可根据资源数量设置信号量的数量,举个栗子比如银行业务窗口只能同时有5个人办理业务,再多的就要排队,这时就设置信号量数量为5,来一个人办理信号量就减一,减到0时说明人满了,其他人就要排队。当有人办理完时,信号量就加一,队伍里的人就可以出来一个去办理,这时信号量又减一,以此类推。计数信号量目前我还没使用过,不多讨论。

    而二值信号量,数量只有1(0和1两个值),其中一个功能就是互斥。什么叫互斥,就是你用了我就不能用,我用了你就不能用,上面例程中使用的就是二值信号量,体现了互斥功能。二值信号量还有个功能就是同步,简单说就是当做一个标志量来使用,即一个地方只释放信号量,另一个地方等到信号量后就能执行。

    • 优先级翻转现象---------------------------------

    在uCOSII中,每个任务都有一个唯一的优先级。一句话概括优先级翻转现象,爸爸都还没出声,就轮到儿子大声说话了。

    • 使用二值信号量出现的优先级翻转现象。

     测试条件:创建所有任务。在TaskLow、TaskHigh 中使用OSSemPend() 和OSSemPost()。

    运行结果如下:

    小横杠是TaskMedium任务打印的字符串,可以看到TaskLow任务不停被打断。

    这么看可能看不出什么名堂,低优先级被高优先级打断不是很正常吗?哪里能看到优先级翻转了呢?结合下图继续往下看。

    有时候,低优先级的任务在占着资源未释放信号量,高优先级的任务也要用资源的话就得等待(比如TaskLow任务正在占用串口打印字符串,TaskHigh任务在等待),这时候有个中优先级的任务需要需要运行(比如TaskMedium任务过来打印小横杠)。因为TaskHigh优先级高,按理说TaskHigh任务只是在等待CPU使用权(使用了OSTimeDly()之类叫主动释放,这时谁要用CPU都不关心了),并不允许其他任务(优先级比TaskHigh低的)比它先得到使用权。就像排队,下一个就到我了,不许有人插队进来。

    但实际就偏有人插队进来。如运行效果,TaskLow任务不断被打扰,但由于还未释放信号量,TaskHigh任务是没法获得CPU使用权来运行的,只能干瞪眼看着可恶的TaskMedium任务在捣乱,这看起来是TaskMedium优先级比TaskHigh高了,这就是优先级翻转现象。TaskMedium任务捣乱会让TaskLow任务不能尽快完成,TaskHigh任务就需要花更多时间等待。如果TaskMedium任务本身就需要很多时间来运行,TaskHigh任务将等到天荒地老,这对于实时操作系统是个灾难。

     这里需要说明一下,TaskMedium任务不通过信号量使用了同一个串口,原则上违背了例程“使用信号量达到资源独占”的目的,但为了能体现优先级反转现象才这么设计,TaskMedium可以不用串口,去做别的事,但依旧会因优先级高于TaskLow而打断TaskLow。

    •  uCOS中的特殊信号量——互斥信号量

     互斥信号量是一种二值信号量,在uCOS中能解决优先级翻转的问题。

    •  使用互斥信号量解决优先级翻转问题。

    测试条件:创建所有任务创建一个互斥信号量。将TaskLow、TaskHigh 中OSSemPend() 和OSSemPost()替换成OSMutexPend() OSMutexPost()

    创建互斥信号量的代码:

    OS_EVENT *testmutex;
    
    testmutex = OSMutexCreate(MUTEX_PRIO, &err);  //MUTEX_PRIO是占用互斥信号量的任务被提升时所需的优先级

     运行结果如下:

     可以看到TaskLow任务已经能完整打印字符串。

    互斥信号量能解决优先级翻转问题的关键在于,把占用信号量任务的优先级给提高了。TaskLow任务在占用信号量期间,优先级会被提升,这样就不会被打断以便尽快运行完释放信号量,释放信号量后TaskLow任务优先级会恢复到原来的。

    使用互斥信号量需要注意一些地方。

    官方明确规定,任务占用互斥信号量期间(还未释放信号量),不能挂起该任务,也就是不能使用延时之类的将任务挂起。否则这互斥信号量就失效了。

    对于提升优先级到底需要提升到多少,这个根据情况而定了,一般比所有等待互斥信号量的任务高一级就行,比如TaskHigh 优先级5,可以把TaskLow优先级提升至4。

    还有优先级天花板的问题,即等待互斥信号的任务是最高了(0级),占用信号量的任务还能不能提升?此问题网上也有很多讨论,这里不多赘述。

     

     

     

     

    展开全文
  • 互斥信号量作用:任务通过OSMutexPend()函数获得互斥信号量,如果互斥信号有效(不为0)则继续运行,否则进入等待。 那么他们之间区别在哪? 假设有三个任务A,B,C,他们优先级分别为10、20、30,而任务A和C共同...

    二值信号量作用:任务通过OSSemPend()函数获得一个信号量,如果信号量有效(不为0),则任务继续运行,否则进入等待状态。互斥信号量作用:任务通过OSMutexPend()函数获得互斥信号量,如果互斥信号有效(不为0)则继续运行,否则进入等待。


    那么他们之间区别在哪?
    假设有三个任务A,B,C,他们的优先级分别为10、20、30,而任务A和C共同使用一个二值信号量。现在任务C得到信号进入运行,这时候任务B也进入就绪,由于B优先级比C高,就会剥夺任务C的CPU控制权,如果这时候任务A需要运行,可是因为信号量被任务C获得还没释放被任务B挂起,所以A得不到运行,这样即使任务A优先级比B高也无法得到运行,这就是优先级反转。如果任务A是比较紧急的任务,那么就会影响实时性。
    为了解决二值信号带来的这个问题,于是出现了互斥信号,

    它的机制就是:

    当一个优先级较低的任务获得了一个互斥信号时,系统会把这个任务的优先级提高到最高,让它不被抢占,这样这个任务就会更快运行完而释放互斥信号提交给更高的任务使用,之后这个任务的优先级就会回到原来的等级。还是A、B、C举例,任务C首先得到一个互斥信号,这时系统会把C的优先级提高到最高,这样就不会被B抢占,从而尽快运行完释放信号,之后回到原来优先级。期间即使A任务需要运行,也能够尽快得到信号运行。不过也看出互斥信号只是缓解了优先级反转问题而已,如果任务A很紧急,也还是要等任务C运行完才可以运行,还是没能很好解决实时性的问题。

     

    互斥信号量主要是为了解决信号量出现的优先级反转的情况:任务的运行取决于优先级和获得信号量2个条件,并且获得信号量又优先于设定的优先级。剥夺性内核对信号量进行独占访问,就有可能出现先获得信号量的低优先级任务在独占信号量过程中被高优先级任务剥夺CPU控制权而挂起,不能及时释放信号量,而高优先级任务又需要该信号量从而出现优先级反转。
    解决的办法:引入互斥信号量,在任务获得共享信号量过程中提升置最高优先级不被打断(通过将信号量计数器分成高8位作为提升优先级,低8位作为占用标志0XFF表明未占用),从而使低优先级任务及时释放共享信号量。其它与信号量相同。
    一创建互斥信号量: OS_EVENT *OSMutexCreat(INT8U prio,INT8U &err)//从任务链表中取得一个任务控制块赋值类型为OS_Event_TYPE_MUXTEX,然后给任务计数器的高8位赋值优先级,第八位赋值0XFF表明未被占用。
    二申请互斥信号量:void OSMutexPend(OS_EVENT *P, INT16U timeout,INT8U &err)//访问任务计数器若为0xff则获得运行权,否则进入等待列表,timeout用于指定等待时间。

    OSMutexAccept(OS_EVENT *P,INT8U &err)//无等待的请求一个信号量。
    三发送(释放)互斥信号量:INT8U OSMutexPost(OS_EVENT *P)
    四获得互斥型信号量的当前状态:INT8U OSMutexQuery(OS_EVENT *P,OS_MUTEX_DATA *pdata)//需事先定义一个存储互斥型信号量状态的变量。
    五删除互斥型信号量:OS_EVENT *OSMutexDel(OS_EVENT *P, INT8U opt,INT8U &err)//opt为删除的选择项:立即删除、等待无任务等待时再删除。

    展开全文
  • 1 、互 斥 信 号 量1.1 互斥信号量的概念及其作用互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源...

    1 、互 斥 信 号 量

    1.1 互斥信号量的概念及其作用

    互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识,进而引出要讲解的互斥信号量。

    运行条件:  

    让两个任务 Task1 和 Task2 都运行串口打印函数 printf,这里我们就通过二值信号量实现对函数printf 的互斥访问。如果不对函数 printf 进行互斥访问,串口打印容易出现乱码。

    用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。

    代码实现:

    创建二值信号量

    static SemaphoreHandle_t xSemaphore = NULL;
    * 函 数 名: AppObjCreate
    * 功能说明: 创建任务通信机制
    * 形 参: 无
    * 返 回 值: 无
    static void AppObjCreate (void)
    {/* 创建二值信号量,首次创建信号量计数值是 0 */xSemaphore = xSemaphoreCreateBinary();if(xSemaphore == NULL)
    {/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */}/* 先释放一次,将初始值改为 1,利用二值信号量实现互斥功能 */xSemaphoreGive(xSemaphore);
    }

     通过二值信号量实现对 printf 函数互斥访问的两个任务

    static void vTaskLED(void *pvParameters)
    {
    TickType_t xLastWakeTime;const TickType_t xFrequency = 300;/* 获取当前的系统时间 */xLastWakeTime = xTaskGetTickCount();while(1)
    {/* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */xSemaphoreTake(xSemaphore, portMAX_DELAY);
    printf("任务 vTaskLED 在运行\r\n");
    bsp_LedToggle(1);
    bsp_LedToggle(4);
    xSemaphoreGive(xSemaphore);/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
    }
    * 函 数 名: vTaskMsgPro
    * 功能说明: 实现对串口的互斥访问
    * 形 参: pvParameters 是在创建该任务时传递的形参
    * 返 回 值: 无
    * 优 先 级: 3
    static void vTaskMsgPro(void *pvParameters)
    {
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = 300;
    /* 获取当前的系统时间 */  xLastWakeTime = xTaskGetTickCount();
    while(1)
    {
    /* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */  
    xSemaphoreTake(xSemaphore, portMAX_DELAY);
    printf("任务 vTaskMsgPro 在运行\r\n");
    bsp_LedToggle(2);
    bsp_LedToggle(3);
    xSemaphoreGive(xSemaphore);
    /* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/  
    vTaskDelayUntil(&xLastWakeTime, xFrequency);
      }
    }

     有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?互斥信号量可以防止优先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。

    1.2 优先级翻转问题

    下面我们通过如下的框图来说明一下优先级翻转的问题,让大家有一个形象的认识。bc43b55f43a9330ad7c0df65493d4a51.png运行条件: 

    创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。也就是 Task1 的优先级最高。
    任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。
    起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。

    运行过程描述如下:
    任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等待 Task3 释放函数 printf。
    在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。所以这种情况被称之为优先级翻转问题。
    任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源,从而可以继续执行。

    上面就是一个产生优先级翻转问题的现象。

    1.3 FreeRTOS 互斥信号量的实现

    FreeRTOS 互斥信号量是怎么实现的呢?其实相对于二值信号量,互斥信号量就是解决了一下优先级翻转的问题。下面我们通过如下的框图来说明一下 FreeRTOS 互斥信号量的实现,让大家有一个形象的认识。

    2d068ed0cf76dff357e2df310f603ee2.png

    运行条件:

    创建 2 个任务 Task1 和 Task2,优先级分别为 1 和 3,也就是任务 Task2 的优先级最高。
    任务 Task1 和 Task2 互斥访问串口打印 printf。
    使用 FreeRTOS 的互斥信号量实现串口打印 printf 的互斥访问。

    运行过程描述如下:

    低优先级任务 Task1 执行过程中先获得互斥资源 printf 的执行。此时任务 Task2 抢占了任务 Task1的执行,任务 Task1 被挂起。任务 Task2 得到执行。
    任务 Task2 执行过程中也需要调用互斥资源,但是发现任务 Task1 正在访问,此时任务 Task1 的优先级会被提升到与 Task2 同一个优先级,也就是优先级 3,这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务 Task2 被挂起,任务 Task1 有新的优先级继续执行。
    任务 Task1 执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2 获得互斥资源后开始执行。

    上面就是一个简单的 FreeRTOS 互斥信号量的实现过程。

    1.4 FreeRTOS 中断方式互斥信号量的实现

     互斥信号量仅支持用在 FreeRTOS 的任务中,中断函数中不可使用。

    2 互 斥 信 号 量 API 函 数

    使用如下 18 个函数可以实现 FreeRTOS 的信号量(含计数信号量,二值信号量和互斥信号):
     xSemaphoreCreateBinary()
     xSemaphoreCreateBinaryStatic()
     vSemaphoreCreateBinary()
     xSemaphoreCreateCounting()
     xSemaphoreCreateCountingStatic()
     xSemaphoreCreateMutex()
     xSemaphoreCreateMutexStatic()
     xSem'CreateRecursiveMutex()
     xSem'CreateRecursiveMutexStatic()
     vSemaphoreDelete()
     xSemaphoreGetMutexHolder()
     uxSemaphoreGetCount()
     xSemaphoreTake()
     xSemaphoreTakeFromISR()
     xSemaphoreTakeRecursive()
     xSemaphoreGive()
     xSemaphoreGiveRecursive()

      xSemaphoreGiveFromISR()

    关于这 18 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:

    d230dc80c08900c357bc974e4c342322.png

    2.1 函数 xSemaphoreCreateMutex

    函数原型:
    SemaphoreHandle_t xSemaphoreCreateMutex( void )
    函数描述:
    函数 xSemaphoreCreateMutex 用于创建互斥信号量。
    返回值,如果创建成功会返回互斥信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,无法为此互斥信号量提供所需的空间会返回 NULL。 

    使用这个函数要注意以下问题:
    1. 此函数是基于函数 xQueueCreateMutex 实现的:
    #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
    函数 xQueueCreateMutex 的实现是基于消息队列函数 xQueueGenericCreate 实现的。
    2. 使用此函数要在 FreeRTOSConfig.h 文件中使能宏定义:
    #define configUSE_MUTEXES 1

    使用举例:

    static SemaphoreHandle_t xMutex = NULL;
    * 函 数 名: AppObjCreate
    * 功能说明: 创建任务通信机制
    * 形 参: 无
    * 返 回 值: 无
    static void AppObjCreate (void)
    {/* 创建互斥信号量 */xMutex = xSemaphoreCreateMutex();
    if(xSemaphore == NULL)
    {/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */}
    }

    2.2 函数 xSemaphoreGive

    函数原型:
    xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */
    函数描述:
    函数 xSemaphoreGive 用于在任务代码中释放信号量。
    第 1 个参数是信号量句柄。
    返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。
    使用这个函数要注意以下问题:
    1. 此函数是基于消息队列函数 xQueueGenericSend 实现的:
    #define xSemaphoreGive( xSemaphore )  xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
    2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xSemaphoreGiveFromISR。

    3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。
    4. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。 

    2.3 函数 xSemaphoreTake

    函数原型:
    xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
    TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */
    函数描述:
    函数 xSemaphoreTake 用于在任务代码中获取信号量。
    第 1 个参数是信号量句柄。
    第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

    返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
    使用这个函数要注意以下问题:
    1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xSemaphoreTakeFromISR。
    2. 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
    3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。

    使用举例:

    static SemaphoreHandle_t xMutex = NULL;
    * 函 数 名: vTaskLED
    * 功能说明: 实现串口的互斥访问,防止多个任务同时访问造成串口打印乱码
    * 形 参: pvParameters 是在创建该任务时传递的形参
    * 返 回 值: 无
    * 优 先 级: 2
    static void vTaskLED(void *pvParameters)
    {
    TickType_t xLastWakeTime;const TickType_t xFrequency = 200;/* 获取当前的系统时间 */xLastWakeTime = xTaskGetTickCount();while(1)
    {/* 互斥信号量,xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用 */xSemaphoreTake(xMutex, portMAX_DELAY);
    printf("任务 vTaskLED 在运行\r\n");
    bsp_LedToggle(2);
    bsp_LedToggle(3);
    xSemaphoreGive(xMutex);/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
    }

     互斥信号量,xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用 

    24417df859475ffb21012a0fb17cf80a.png

    经过测试,互斥信号量是可以被其他任务释放的,但是我们最好不要这么做,因为官方推荐的就是在同一个任务中接收和释放。如果在其他任务释放,不仅仅会让代码整体逻辑变得复杂,还会给使用和维护这套API的人带来困难。遵守规范,总是好的。

    裸机编程的时候,我经常想一个问题,就是怎么做到当一个标志位触发的时候,立即执行某个操作,如同实现标志中断一样,在os编程之后,我们就可以让一个优先级最高任务一直等待某个信号量,如果获得信号量,就执行某个操作,实现类似标志位中断的作用(当然,要想正真做到中断效果,那就需要屏蔽所有可屏蔽中断,而临界区就可以做到)。

    再说一下递归互斥信号量:递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量

    使用举例:

    static void vTaskMsgPro(void *pvParameters)
    {
    TickType_t xLastWakeTime;    
    const TickType_t xFrequency = 1500;    /* 获取当前的系统时间 */
    xLastWakeTime = xTaskGetTickCount();    
    while(1)
    {/* 递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量 */
    xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
    {//假如这里是被保护的资源,第1层被保护的资源,用户可以在这里添加被保护资源
    printf("任务vTaskMsgPro在运行,第1层被保护的资源,用户可以在这里添加被保护资源\r\n");
    /* 第1层被保护的资源里面嵌套被保护的资源 */
    xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
    {  
    //假如这里是被保护的资源,第2层被保护的资源,用户可以在这里添加被保护资源
    printf("任务vTaskMsgPro在运行,第2层被保护的资源,用户可以在这里添加被保护资源\r\n");
    /* 第2层被保护的资源里面嵌套被保护的资源 */
    xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
    {
    printf("任务vTaskMsgPro在运行,第3层被保护的资源,用户可以在这里添加被保护资源\r\n");
    bsp_LedToggle(1);
    bsp_LedToggle(4);
    }
    xSemaphoreGiveRecursive(xRecursiveMutex);
               }
    xSemaphoreGiveRecursive(xRecursiveMutex);    
           }
    xSemaphoreGiveRecursive(xRecursiveMutex);        
    /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
    vTaskDelayUntil(&xLastWakeTime, xFrequency);
       }
    }

    可以前面的那个官方文档那样用if判断传递共享量:

    27860f20826dc14dfb2d96cbb38bc2bf.png

    也可以用我们递归互斥信号量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE时,会一直等待信号量,直到有信号量来才执行后面的语句。

    我们官方的QQ群1:281549832

    我们官方的QQ群2:386393398

    特别感谢网友的大力支持。

    我们的开源团队不断扩大,希望大家快来一起加入我们吧。

    在这里还是要谢谢大家的大力支持!

    39b3f503d4d40a900c465d1fd7c12d09.png

    大家快来关注吧!

    展开全文
  • 1 、互 斥 信 号 量1.1 互斥信号量的概念及其作用互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源...

    1 、互 斥 信 号 量

    1.1 互斥信号量的概念及其作用

    互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识,进而引出要讲解的互斥信号量。

    运行条件:

    让两个任务 Task1 和 Task2 都运行串口打印函数 printf,这里我们就通过二值信号量实现对函数printf 的互斥访问。如果不对函数 printf 进行互斥访问,串口打印容易出现乱码。

    用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。

    代码实现:

    创建二值信号量

    static SemaphoreHandle_t xSemaphore = NULL; * 函 数 名: AppObjCreate * 功能说明: 创建任务通信机制 * 形 参: 无 * 返 回 值: 无 static void AppObjCreate (void) {/* 创建二值信号量,首次创建信号量计数值是 0 */xSemaphore = xSemaphoreCreateBinary();if(xSemaphore == NULL) {/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */}/* 先释放一次,将初始值改为 1,利用二值信号量实现互斥功能 */xSemaphoreGive(xSemaphore); }

     通过二值信号量实现对 printf 函数互斥访问的两个任务

    static void vTaskLED(void *pvParameters) { TickType_t xLastWakeTime;const TickType_t xFrequency = 300;/* 获取当前的系统时间 */xLastWakeTime = xTaskGetTickCount();while(1) {/* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */xSemaphoreTake(xSemaphore, portMAX_DELAY); printf("任务 vTaskLED 在运行 "); bsp_LedToggle(1); bsp_LedToggle(4); xSemaphoreGive(xSemaphore);/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/vTaskDelayUntil(&xLastWakeTime, xFrequency); } } * 函 数 名: vTaskMsgPro * 功能说明: 实现对串口的互斥访问 * 形 参: pvParameters 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: 3 static void vTaskMsgPro(void *pvParameters) { TickType_t xLastWakeTime; const TickType_t xFrequency = 300; /* 获取当前的系统时间 */  xLastWakeTime = xTaskGetTickCount(); while(1) { /* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */   xSemaphoreTake(xSemaphore, portMAX_DELAY); printf("任务 vTaskMsgPro 在运行 "); bsp_LedToggle(2); bsp_LedToggle(3); xSemaphoreGive(xSemaphore); /* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/   vTaskDelayUntil(&xLastWakeTime, xFrequency);   } }

    有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?互斥信号量可以防止优先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。

    1.2 优先级翻转问题

    下面我们通过如下的框图来说明一下优先级翻转的问题,让大家有一个形象的认识。

    运行条件:

    创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。也就是 Task1 的优先级最高。

    任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。

    起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。

    运行过程描述如下:

    任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等待 Task3 释放函数 printf。

    在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。所以这种情况被称之为优先级翻转问题。

    任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源,从而可以继续执行。

    上面就是一个产生优先级翻转问题的现象。

    1.3 FreeRTOS 互斥信号量的实现

    FreeRTOS 互斥信号量是怎么实现的呢?其实相对于二值信号量,互斥信号量就是解决了一下优先级翻转的问题。下面我们通过如下的框图来说明一下 FreeRTOS 互斥信号量的实现,让大家有一个形象的认识。

    运行条件:

    创建 2 个任务 Task1 和 Task2,优先级分别为 1 和 3,也就是任务 Task2 的优先级最高。

    任务 Task1 和 Task2 互斥访问串口打印 printf。

    使用 FreeRTOS 的互斥信号量实现串口打印 printf 的互斥访问。

    运行过程描述如下:

    低优先级任务 Task1 执行过程中先获得互斥资源 printf 的执行。此时任务 Task2 抢占了任务 Task1的执行,任务 Task1 被挂起。任务 Task2 得到执行。

    任务 Task2 执行过程中也需要调用互斥资源,但是发现任务 Task1 正在访问,此时任务 Task1 的优先级会被提升到与 Task2 同一个优先级,也就是优先级 3,这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务 Task2 被挂起,任务 Task1 有新的优先级继续执行。

    任务 Task1 执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2 获得互斥资源后开始执行。

    上面就是一个简单的 FreeRTOS 互斥信号量的实现过程。

    1.4 FreeRTOS 中断方式互斥信号量的实现

    互斥信号量仅支持用在 FreeRTOS 的任务中,中断函数中不可使用。

    2 互 斥 信 号 量 API 函 数

    使用如下 18 个函数可以实现 FreeRTOS 的信号量(含计数信号量,二值信号量和互斥信号):

     xSemaphoreCreateBinary()

     xSemaphoreCreateBinaryStatic()

     vSemaphoreCreateBinary()

     xSemaphoreCreateCounting()

     xSemaphoreCreateCountingStatic()

     xSemaphoreCreateMutex()

     xSemaphoreCreateMutexStatic()

     xSem'CreateRecursiveMutex()

     xSem'CreateRecursiveMutexStatic()

     vSemaphoreDelete()

     xSemaphoreGetMutexHolder()

     uxSemaphoreGetCount()

     xSemaphoreTake()

     xSemaphoreTakeFromISR()

     xSemaphoreTakeRecursive()

     xSemaphoreGive()

     xSemaphoreGiveRecursive()

     xSemaphoreGiveFromISR()

    关于这 18 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:

    2.1 函数 xSemaphoreCreateMutex

    函数原型:

    SemaphoreHandle_t xSemaphoreCreateMutex( void )

    函数描述:

    函数 xSemaphoreCreateMutex 用于创建互斥信号量。

    返回值,如果创建成功会返回互斥信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,无法为此互斥信号量提供所需的空间会返回 NULL。

    使用这个函数要注意以下问题:

    1. 此函数是基于函数 xQueueCreateMutex 实现的:

    #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )

    函数 xQueueCreateMutex 的实现是基于消息队列函数 xQueueGenericCreate 实现的。

    2. 使用此函数要在 FreeRTOSConfig.h 文件中使能宏定义:

    #define configUSE_MUTEXES 1

    使用举例:

    static SemaphoreHandle_t xMutex = NULL; * 函 数 名: AppObjCreate * 功能说明: 创建任务通信机制 * 形 参: 无 * 返 回 值: 无 static void AppObjCreate (void) {/* 创建互斥信号量 */xMutex = xSemaphoreCreateMutex(); if(xSemaphore == NULL) {/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */} }

    2.2 函数 xSemaphoreGive

    函数原型:

    xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

    函数描述:

    函数 xSemaphoreGive 用于在任务代码中释放信号量。

    第 1 个参数是信号量句柄。

    返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。

    使用这个函数要注意以下问题:

    1. 此函数是基于消息队列函数 xQueueGenericSend 实现的:

    #define xSemaphoreGive( xSemaphore )  xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

    2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xSemaphoreGiveFromISR。

    3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。

    4. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。

    2.3 函数 xSemaphoreTake

    函数原型:

    xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */

    TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

    函数描述:

    函数 xSemaphoreTake 用于在任务代码中获取信号量。

    第 1 个参数是信号量句柄。

    第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

    返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。

    使用这个函数要注意以下问题:

    1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xSemaphoreTakeFromISR。

    2. 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。

    3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。

    使用举例:

    static SemaphoreHandle_t xMutex = NULL; * 函 数 名: vTaskLED * 功能说明: 实现串口的互斥访问,防止多个任务同时访问造成串口打印乱码 * 形 参: pvParameters 是在创建该任务时传递的形参 * 返 回 值: 无 * 优 先 级: 2 static void vTaskLED(void *pvParameters) { TickType_t xLastWakeTime;const TickType_t xFrequency = 200;/* 获取当前的系统时间 */xLastWakeTime = xTaskGetTickCount();while(1) {/* 互斥信号量,xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用 */xSemaphoreTake(xMutex, portMAX_DELAY); printf("任务 vTaskLED 在运行 "); bsp_LedToggle(2); bsp_LedToggle(3); xSemaphoreGive(xMutex);/* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/vTaskDelayUntil(&xLastWakeTime, xFrequency); } }

    互斥信号量,xSemaphoreTake 和 xSemaphoreGive 一定要成对的调用

    经过测试,互斥信号量是可以被其他任务释放的,但是我们最好不要这么做,因为官方推荐的就是在同一个任务中接收和释放。如果在其他任务释放,不仅仅会让代码整体逻辑变得复杂,还会给使用和维护这套API的人带来困难。遵守规范,总是好的。

    裸机编程的时候,我经常想一个问题,就是怎么做到当一个标志位触发的时候,立即执行某个操作,如同实现标志中断一样,在os编程之后,我们就可以让一个优先级最高任务一直等待某个信号量,如果获得信号量,就执行某个操作,实现类似标志位中断的作用(当然,要想正真做到中断效果,那就需要屏蔽所有可屏蔽中断,而临界区就可以做到)。

    再说一下递归互斥信号量:递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量

    使用举例:

    static void vTaskMsgPro(void *pvParameters) { TickType_t xLastWakeTime;     const TickType_t xFrequency = 1500;    /* 获取当前的系统时间 */ xLastWakeTime = xTaskGetTickCount();     while(1) {/* 递归互斥信号量,其实就是互斥信号量里面嵌套互斥信号量 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); {//假如这里是被保护的资源,第1层被保护的资源,用户可以在这里添加被保护资源 printf("任务vTaskMsgPro在运行,第1层被保护的资源,用户可以在这里添加被保护资源 "); /* 第1层被保护的资源里面嵌套被保护的资源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); {   //假如这里是被保护的资源,第2层被保护的资源,用户可以在这里添加被保护资源 printf("任务vTaskMsgPro在运行,第2层被保护的资源,用户可以在这里添加被保护资源 "); /* 第2层被保护的资源里面嵌套被保护的资源 */ xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); { printf("任务vTaskMsgPro在运行,第3层被保护的资源,用户可以在这里添加被保护资源 "); bsp_LedToggle(1); bsp_LedToggle(4); } xSemaphoreGiveRecursive(xRecursiveMutex);            } xSemaphoreGiveRecursive(xRecursiveMutex);            } xSemaphoreGiveRecursive(xRecursiveMutex);         /* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/ vTaskDelayUntil(&xLastWakeTime, xFrequency);    } }

    可以前面的那个官方文档那样用if判断传递共享量:

    也可以用我们递归互斥信号量里面的portMAX_DELAY指定永久等待,即xSemaphoreTakeRecursive返回的不是pdTRUE时,会一直等待信号量,直到有信号量来才执行后面的语句。

    责任编辑:PSY

    原文标题:嵌入式系统FreeRTOS — 互斥信号量

    文章出处:【微信公众号:开源嵌入式】欢迎添加关注!文章转载请注明出处。

    展开全文
  • 互斥信号量信号量的理解

    千次阅读 多人点赞 2018-09-17 10:10:24
    昨天一开始看事件标志组的时候确实不知道怎么回事,后来百度一下,明白了事件标志组的作用以后,再去看书上的讲解和原码就清晰多了,很容易就明白了他的基本运行机理。这也给了我一点启示,学一个东西,看一个东西...
  • 信号量用在多线程多任务同步,一个线程完成了某一个动作就通过信号量告诉别线程,别线程再进行某些动作(大家都在semtake时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个...
  • 1 、互 斥 信 号 量1.1 互斥信号量的概念及其作用互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源...
  • 信号量用在多线程多任务同步,一个线程完成了某一个动作就通过信号量告诉别线程,别线程再进行某些动作(大家都在semtake时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个...
  • 信号量用在多线程多任务同步,一个线程完成了某一个动作就通过信号量告诉别线程,别线程再进行某些动作(大家都在semtake时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个...
  • 信号量用在多线程多任务同步,一个线程完成了某一个动作就通过信号量告诉别线程,别线程再进行某些动作(大家都在semtake时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个...
  • UCOS2学习笔记:对于信号量,互斥信号量,事件标志组个人理解   ucos看了也有一周多了,索性源码都能开得懂,并且能去理解。昨天一开始看事件标志组时候确实不知道怎么回事,后来百度一下,明白...
  • 信号量用在多线程多任务同步,一个线程完成了某一个动作就通过信号量告诉别线程,别线程再进行某些动作(大家都在semtake时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个...
  • 互斥量和信号量的理解

    千次阅读 2018-07-29 15:59:56
    互斥量(Mutex)   互斥量表现互斥现象的...Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex主要的作用是用于互斥。Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。值为0, 表...
  • 昨天一开始看事件标志组的时候确实不知道怎么回事,后来百度一下,明白了事件标志组的作用以后,再去看书上的讲解和原码就清晰多了,很容易就明白了他的基本运行机理。这也给了我一点启示,学一个东西,看一个东西...
  • 4、关于信号量的作用 信号量(semaphore)用于实现任务间共享资源的管理、一个或多个事件的发生,比如现在有一个共享资源a,现在任务1获得访问权,那么对于其余任务来说就没有访问权限,在采用信号量时,可以这样...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 492
精华内容 196
关键字:

互斥信号量的作用