二值信号量可以在某个特殊的中断发生时,让任务解除阻塞,相当于让任务与中断同步。这样就可以让中断事件处理量大的工作在同步任务中完成,中断服务例程(ISR)中只是快速处理少部份工作。如此,中断处理可以说是被”推迟(deferred)”到一个”处理(handler)”任务。
如果某个中断处理要求特别紧急,可以将其延迟处理任务的优先级可以设为最高,以保证延迟处理任务随时都抢占系统中的其它任务。这样,延迟处理任务就成为其对应的 ISR退出后第一个执行的任务,在时间上紧接着 ISR 执行,相当于所有的处理都在 ISR 中完成一样。
延迟处理任务对一个信号量进行带阻塞性质的”take”调用,意思是进入阻塞态以等待事件发生。当事件发生后,ISR 对同一个信号量进行”give”操作,使得延迟处理任务解除阻塞,从而事件在延迟处理任务中得到相应的处理。
P 源自荷兰语 Parsseren,即英语的 Pass;V 源自荷兰语 Verhoog,即英语的 Increment。P(S)/V(S)操作是信号量的两个原子操作,S 为信号量 Semaphore,相当于一个标志,可以代表一个资源,一个事件等等,初始值视应用场合而定。
P(S) / V(S)原子操作有如下行为:
P(S) : IF (S <= 0) THEN 将本线程加入 S 的等待队列
S = S – 1
V(S) : S = S + 1
IF (S > 0) THEN 唤醒某个等待线程
在这种中断同步的情形下,信号量可以看作是一个深度为 1 的队列。这个队列由于最多只能保存一个数据单元,所以其不为空则为满(所谓”二值”)。延迟处理任务调用xSemaphoreTake()时,等效于带阻塞时间地读取队列,如果队列为空的话任务则进入阻塞态。当事件发生后,ISR 简单地通过调用 xSemaphoreGiveFromISR()放置一个令牌(信号量)到队列中,使得队列成为满状态。这也使得延迟处理任务切出阻塞态,并移除令牌,使得队列再次成为空。当任务完成处理后,再次读取队列,发现队列为空,又进入阻塞态,等待下一次事件发生。
中断给出信号量,甚至是在信号量第一次被获取之前就给出;而任务在获取信号量之后再也不给回来。在其它场合下,任务获得(Take)了信号量之后,必须得给(Give)回来。
FreeRTOS 中各种信号量的句柄都存储在 xSemaphoreHandle 类型的变量中。在使用信号量之前,必须先创建它
typedef void * QueueHandle_t;
typedef QueueHandle_t SemaphoreHandle_t;
#define xSemaphoreHandle SemaphoreHandle_t
SemaphoreHandle_t xSemaphore = NULL;
#if (1 == configSUPPORT_DYNAMIC_ALLOCATION)
#define vSemaphoreCreateBinary(xSemaphore) \
{ \
(xSemaphore) = xQueueGenericCreate((UBaseType_t)1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE); \
if ((xSemaphore) != NULL) \
{ \
(void)xSemaphoreGive((xSemaphore)); \
} \
}
#endif
需要说明的是 vSemaphoreCreateBinary()在实现上是一个宏,所以信号量变量应当直接传入,而不是传址。信号量 API 实际上是由一组宏实现的,而不是函数。
#define xSemaphoreTake(xSemaphore, xBlockTime) xQueueGenericReceive((QueueHandle_t)(xSemaphore), NULL, (xBlockTime), pdFALSE)
“带走(Taking)”一个信号量意为”获取(Obtain)”或”接收(Receive)”信号量。只有当信号量有效的时候才可以被获取。在经典信号量术中,xSemaphoreTake()等同于一次 P()操作。
除互斥信号量外,所有类型的信号量都可以调用函数 xSemaphoreTake() 来获取。
xSemaphoreTake()不能在中断服务函数中调用。
除互斥信号量外, FreeRTOS 支持的其它类型的信号量都可以通过调用 xSemaphoreGiveFromISR() 给出
xSemaphoreGiveFromISR() 是 xSemaphoreGive() 的特殊形式,专门用于中断服务例程中。
static void vPeriodicTask(void *pvParameters)
{
for(;;)
{
/* 此任务通过每500毫秒产生一个软件中断来”模拟”中断事件 */
vTaskDelay( 500 / portTICK_RATE_MS );
/* 产生中断,并在产生之前和之后输出信息,以便在执行结果中直观直出执行流程 */
printf("Periodic task - About to generate an interrupt.\r\n");
NVIC_SetPendingIRQ(IRQn); // creat a interrupt by stm32fxx mcu
printf("Periodic task - Interrupt generated.\r\n\r\n\r\n");
}
}
static void vHandlerTask( void *pvParameters )
{
for( ;; )
{
/* P operation, 使用信号量等待一个事件。任务被无超时阻塞, 该任务运行到这里会被挂起 vHandlerTask, 后面的不会执行了, 直到等到了信号量才会再次运行。此处也没有必要检测返回值 */
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
/* 程序运行到这里时,事件必然已经发生。本例的事件处理只是简单地打印输出一个信息 */
printf("Handler task - Processing event.\r\n");
}
}
static void IRQHandler(void)
{
static portBASE_TYPE xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* 'Give' the semaphore to unblock the task. V operation*/
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
if (pdTRUE == xHigherPriorityTaskWoken)
{
portYIELD();
}
}
int main(void)
{
vSemaphoreCreateBinary(xBinarySemaphore); // create a semaphore
NVIC_SetPriority(IRQn, priority); // set a soft interrupt for stm32fxx mcu
// check wether the semaphore has been created success
if(xBinarySemaphore != NULL)
{
/* 创建延迟处理任务。此任务将与中断同步。延迟处理任务在创建时使用了一个较高的优先级,以保证中断退出后会被立即执行。在本例中,为延迟处理任务赋予优先级3 */
xTaskCreate(vHandlerTask, "Handler", 1000, NULL, 3, NULL);
/* 创建一个任务用于周期性产生软件中断。此任务的优先级低于延迟处理任务。每当延迟处理任务切出阻塞态,就会抢占周期任务*/
xTaskCreate(vPeriodicTask, "Periodic", 1000, NULL, 1, NULL);
vTaskStartScheduler();
}
for(;;)
{
}
}