创建互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建互斥量API接口一样是个宏,定义以下:
其中,宏queueQUEUE_TYPE_MUTEX用于通用队列创建函数,表示创建队列的类型是互斥量,在文章《FreeRTOS高级篇5---FreeRTOS队列分析》关于通用队列创建函数参数说明中提到了这个宏。
我们来看1下函数xQueueCreateMutex()是如何实现的:
这个函数是带条件编译的,只有将宏configUSE_MUTEXES定义为1才会编译这个函数。
函数首先调用通用队列创建函数xQueueGenericCreate()来创建1个队列,队列项数目为1,队列项大小为0,说明创建的队列只有队列数据结构存储空间而没有队列项存储空间。
如果队列创建成功,通用队列创建函数还会依照通用队列的方式 初始化所有队列结构体成员。但是这里要创建的是互斥量,所以有1些结构体成员必须重新赋值。在这段代码中,可能你会疑惑,队列结构体成员中,并没有pxMutexHolder和uxQueueType!其实这两个标识符只是宏定义,是专门为互斥量而定义的,以下所示:
当队列结构体用于互斥量时,成员pcHead和pcTail指针就不再需要,并且将pcHead指针设置为NULL,表示pcTail指针实际指向互斥量持有者任务TCB(如果有的话)。
最后调用函数xQueueGenericSend()释放1个互斥量,相当于互斥量创建后是有效的,可以直接使用获得信号量API函数来获得这个互斥量。如果某资源同时只准1个任务访问,可以用互斥量保护这个资源。这个资源1定是存在的,所以创建互斥量时会先释放1个互斥量,表示这个资源可使用。任务想访问资源时,先获得互斥量,等使用完资源后,再释放它。也就是说互斥量1旦创建好后,要先获得,后释放,要在同1个任务中获得和释放。这也是互斥量和2进制信号量的1个重要区分,2进制信号量可以在随意1个任务中获得或释放,然后也能够在任意1个任务中释放或获得。互斥量不同于2进制信号量的还有:互斥量具有优先级继承机制,2进制信号量没有,互斥量不可以用于中断服务程序,2进制信号量可以。
初始化后的互斥量内存如图1⑶所示。
2.释放信号量
不管2进制信号量、计数信号量还是互斥量,它们都使用相同的获得和释放API函数。释放信号量用于使信号量有效,分为不带中断保护和带中断保护两个版本。
2.1 xSemaphoreGive()
用于释放1个信号量,不带中断保护。被释放的信号量可以是2进制信号量、计数信号量和互斥量。注意递归互斥量其实不能使用这个API函数释放。其实信号量释放是1个宏,真正调用的函数是xQueueGenericSend(),宏定义以下:
可以看出释放信号量实际上是1次入队操作,并且阻塞时间为0(由宏semGIVE_BLOCK_TIME定义)。
对2进制信号量和计数信号量,根据上1章的内容可以总结出,释放1个信号量的进程实际上可以简化为两种情况:第1,如果队列未满,队列结构体成员uxMessageWaiting加1,判断是不是有阻塞的任务,有的话消除阻塞,然后返回成功信息(pdPASS);第2,如果队列满,返回毛病代码(err_QUEUE_FULL),表示队列满。
对互斥量要复杂些,由于互斥量具有优先级继承机制。
优先级继承是个甚么进程呢?我们举个例子。某个资源X同时只能有1个任务访问,现在有任务A和任务C都要访问这个资源,任务A的优先级为1,任务C的优先级为10,所以任务C的优先级大于任务A的优先级。我们用互斥量保护资源X,并且当前任务A正在访问资源X。在任务A访问资源X的进程中,来了1个中断,中断事件使得任务C履行。任务C履行的进程中,也想访问资源X,但是由于资源X还被任务A独占着,所以任务C没法获得互斥量,会进入阻塞状态。此时,低优先级任务A会继承高优先级任务C的优先级,任务A的优先级临时的被提升,优先级变成10。这个机制能够将已产生的优先级反转影响下降到最小。
那末甚么是优先级反转呢?还是看上面的例子,任务C的优先级高于任务A,但是任务C由于没有取得互斥量而进入阻塞,只能等待低优先级的任务A释放互斥量后才能运行,这类情况就是优先级反转。
那为何优先级继承可以下降优先级反转的影响呢?还是看上面的例子,不过我们再增加1个优先级为5的任务B,这3个任务都处于就绪状态。如果没有优先级继承机制,3个任务的优先级顺序为任务C>任务B>任务A。当任务C由于得不到互斥量而阻塞后,任务B会获得CPU权限,等到任务B主动或被动让出CPU后,任务A才会履行,任务A释放互斥量后,任务C才能得到运行。再看1下有优先级继承的情况,当任务C由于得不到互斥量而阻塞后,任务A继承任务C的优先级,现在3个任务的优先级顺序为任务C=任务A>任务B。当任务C由于得不到互斥量而阻塞后,任务A会取得CPU权限,等到任务A释放互斥量后,任务C就会得到运行。看,任务C等待的时间变短了。
有了上面的基础理论,我们就很好理解为何释放互斥量会比较复杂了。还是可以简化为两种情况:第1,如果队列未满,除队列结构体成员uxMessageWaiting加1外,还要判断获得互斥量的任务是不是有优先级继承,如果有的话,还要将任务的优先级恢复到原始值。固然,恢复到原来值也是有条件的,就是该任务必须在没有使用其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是不是有阻塞的任务,有的话消除阻塞,最后返回成功信息(pdPASS);第2,如果如果队列满,返回毛病代码(err_QUEUE_FULL),表示队列满。
3.获得信号量
不管2进制信号量、计数信号量还是互斥量,它们都使用相同的获得和释放API函数。释获得信号量会消耗信号量,如果获得信号量失败,任务可能会阻塞,阻塞时间由函数参数xBlockTime指定,如果为0,则直接返回,不阻塞。获得信号量分为不带中断保护和带中断保护两个版本。
3.1 xSemaphoreTake
用于获得信号量,不带中断保护。获得的信号量可以是2进制信号量、计数信号量和互斥量。注意递归互斥量其实不能使用这个API函数获得。其实获得信号量是1个宏,真正调用的函数是xQueueGenericReceive (),宏定义以下:
通过上面的宏定义可以看出,获得信号量实际上是履行出队操作。
对2进制信号量和计数信号量,可以简化为3种情况:第1,如果队列不为空,队列结构体成员uxMessageWaiting减1,判断是不是有因入队而阻塞的任务,有的话消除阻塞,然后返回成功信息(pdPASS);第2,如果队列为空并且阻塞时间为0,则直接返回毛病码(errQUEUE_EMPTY),表示队列为空;第3,如果队列为空并且阻塞时间不为0,则任务会由于等待信号量而进入阻塞状态,任务会被挂接到延时列表中。
对互斥量,也能够简化为3种情况,但是进程要复杂1些:第1,如果队列不为空,队列结构体成员uxMessageWaiting减1、将当前任务TCB结构体成员uxMutexesHeld加1,表示任务获得互斥量的个数、将队列结构体成员指针pxMutexHolder指向任务TCB、判断是不是有因入队而阻塞的任务,有的话消除阻塞,然后返回成功信息(pdPASS);第2,如果队列为空并且阻塞时间为0,则直接返回毛病码(errQUEUE_EMPTY),表示队列为空;第3,如果队列为空并且阻塞时间不为0,则任务会由于等待信号量而进入阻塞状态,在将任务挂接到延时列表之前,会判断当前任务和具有互斥量的任务优先级哪一个高,如果当前任务优先级高,则具有互斥量的任务继承担前任务优先级。
来源:oschina
链接:https://my.oschina.net/u/3874841/blog/4314838