2.释放信号量 无论二进制信号量、计数信号量还是互斥量,它们都使用相同的获取和释放API函数。释放信号量用于使信号量有效,分为不带中断保护和带中断保护两个版本。 2.1 xSemaphoreGive() 用于释放一个信号量,不带中断保护。被释放的信号量可以是二进制信号量、计数信号量和互斥量。注意递归互斥量并不能使用这个API函数释放。其实信号量释放是一个宏,真正调用的函数是xQueueGenericSend(),宏定义如下:
#definexSemaphoreGive( xSemaphore ) \
xQueueGenericSend( \
( QueueHandle_t ) ( xSemaphore ), \
NULL, \
semGIVE_BLOCK_TIME, \
queueSEND_TO_BACK )
可以看出释放信号量实际上是一次入队操作,并且阻塞时间为0(由宏semGIVE_BLOCK_TIME定义)。 对于二进制信号量和计数信号量,根据上一章的内容可以总结出,释放一个信号量的过程实际上可以简化为两种情况:第一,如果队列未满,队列结构体成员uxMessageWaiting加1,判断是否有阻塞的任务,有的话解除阻塞,然后返回成功信息(pdPASS);第二,如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。 对于互斥量要复杂些,因为互斥量具有优先级继承机制。 优先级继承是个什么过程呢?我们举个例子。某个资源X同时只能有一个任务访问,现在有任务A和任务C都要访问这个资源,任务A的优先级为1,任务C的优先级为10,所以任务C的优先级大于任务A的优先级。我们用互斥量保护资源X,并且当前任务A正在访问资源X。在任务A访问资源X的过程中,来了一个中断,中断事件使得任务C执行。任务C执行的过程中,也想访问资源X,但是因为资源X还被任务A独占着,所以任务C无法获取互斥量,会进入阻塞状态。此时,低优先级任务A会继承高优先级任务C的优先级,任务A的优先级临时的被提升,优先级变成10。这个机制能够将已经发生的优先级反转影响降低到最小。 那么什么是优先级反转呢?还是看上面的例子,任务C的优先级高于任务A,但是任务C因为没有获得互斥量而进入阻塞,只能等待低优先级的任务A释放互斥量后才能运行,这种情况就是优先级反转。 那为什么优先级继承可以降低优先级反转的影响呢?还是看上面的例子,不过我们再增加一个优先级为5的任务B,这三个任务都处于就绪状态。如果没有优先级继承机制,三个任务的优先级顺序为任务C>任务B>任务A。当任务C因为得不到互斥量而阻塞后,任务B会获取CPU权限,等到任务B主动或被动让出CPU后,任务A才会执行,任务A释放互斥量后,任务C才能得到运行。再看一下有优先级继承的情况,当任务C因为得不到互斥量而阻塞后,任务A继承任务C的优先级,现在三个任务的优先级顺序为任务C=任务A>任务B。当任务C因为得不到互斥量而阻塞后,任务A会获得CPU权限,等到任务A释放互斥量后,任务C就会得到运行。看,任务C等待的时间变短了。 有了上面的基础理论,我们就很好理解为什么释放互斥量会比较复杂了。还是可以简化为两种情况:第一,如果队列未满,除了队列结构体成员uxMessageWaiting加1外,还要判断获取互斥量的任务是否有优先级继承,如果有的话,还要将任务的优先级恢复到原始值。当然,恢复到原来值也是有条件的,就是该任务必须在没有使用其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是否有阻塞的任务,有的话解除阻塞,最后返回成功信息(pdPASS);第二,如果如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。
|