FreeRTOS 任务与调度器(2)
在上一篇我们介绍了FreeRTOS任务的一些基本操作和功能,今天我们会介绍一个很好很强大的功能——任务通知
任务通知可以在不同任务之间传递信息,它可以取代二值信号量、计数信号量、事件标志组、深度为1的消息队列等功能,因为它更快,占用RAM更少,是FreeRTOS自8.2以来推出的重大改进功能。
一、任务通知相关变量
1.1、TCB中的通知相关成员
FreeRTOS每个任务都有一个通知指示值,和一个32位通知值:
任务数据结构(TCB)中与队列通知相关的成员
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile eNotifyValue eNotifyState;
#endif
- ulNotifiedValue就是任务中的通知值,任务通知实际上就是围绕这个变量作文章,下面会以“通知值”来代替这个成员变量,
- eNotifyState用来标志任务是否在等待通知,它有以下3种情况
eNotified | 任务已经被通知 | 带发送通知功能的函数都会首先把eNotifyState设置为eNotified,表示任务已经被通知 |
eWaitingNotification | 任务正在等待通知 | 接收通知功能的函数会首先把eNotifyState设置为eWaitingNotification,表示任务已经阻塞了正在等待通知 |
eNotWaitingNotification | 空状态 | 表示任务即没有收到新的通知,也没有正在等待通知,接收通知功能函数在接收到通知处理后,会把eNotifyState设置为eWaitingNotification |
根据上一节中的TCB我们的精简,我们现在为TCB补上接下来会用到新的成员:
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*任务堆栈栈顶*/ ListItem_t xGenericListItem; /*任务状态列表项项引用的列表,指示任务状态(准备态、阻塞态、挂起态)*/
ListItem_t xEventListItem; /*状态列表项*/
UBaseType_t uxPriority; /*任务优先级*/
StackType_t *pxStack; /*任务堆栈起始地址*/
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*任务名字*/ volatile uint32_t ulNotifiedValue; /*任务通知值*/
volatile eNotifyValue eNotifyState; /*通知状态标志*/ } tskTCB;
二、任务通知API函数
2.1、发送通知
xTaskNotifyGive() | 发送通知,通知值设定为自增方式(每次调用通知值加1) |
vTaskNotifyGiveFromISR() | 上面函数的中断版本 |
xTaskNotify() | 发送通知,可以自定义通知方式 |
xTaskNotifyFromISR() | 上面函数的中断版本 |
xTaskNotifyAndQuery() | 发送通知,自定义通知方式,并且读出上一次的通知值 |
xTaskNotifyAndQueryFromISR() | 上面函数的中断版本 |
其中有5个API是宏,它们关系如下:
非中断版本:
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) | xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL ) |
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) | xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) ) |
#define xTaskNotifyGive( xTaskToNotify ) | xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL ) |
中断版本:
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) | xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) ) |
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) | xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) ) |
可以发现,实际上他们底层只有两个函数,
- xTaskGenericNotify()
- xTaskGenericNotifyFromISR()
再加上一个直接指向顶层的函数
- vTaskNotifyGiveFromISR()
接下来我们直接看这3个函数,会更便于理解队列通知以及使用
2.1.1生成通知信号函数xTaskGenericNotify():
函数原型:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
输入参数:
xTaskToNotify | 被通知的任务的句柄 |
ulValue | 输入值 |
eAction | 进行通知方式 |
*pulPreviousNotificationValue | 这个任务上一次的通知值 |
其中参数eAction最为重要,它决定了我们以什么方式发送通知,我们查看它是一个eNotifyAction类型的结构体,查看这个结构体,可以留意到发送通知的方式分为以下5种:
typedef enum
{
eNoAction = ,
eSetBits,
eIncrement,
eSetValueWithOverwrite,
eSetValueWithoutOverwrite
} eNotifyAction;
现在我们来截取xTaskGenericNotifyFromISR()一段操作通知值的代码,看看这5种信号是怎么改变通知值的:
switch( eAction )
{
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break; case eIncrement :
( pxTCB->ulNotifiedValue )++;
break; case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break; case eSetValueWithoutOverwrite :
if( eOriginalNotifyState != eNotified )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
/* The value could not be written to the task. */
xReturn = pdFAIL;
}
break; case eNoAction :
/* The task is being notified without its notify value being
updated. */
break;
}
源码分析(展开折叠查看)
这个函数主要做了3件事:
- 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
- 根据需求更新TCB中的通知值ulNotifiedValue
- 解除目标任务的阻塞状态
#if( configUSE_TASK_NOTIFICATIONS == 1 ) BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
{
/*
xTaskToNotify:被通知的任务句柄
ulValue: 更新的通知值
eAction: 枚举类型,指明更新通知值的方法,
*pulPreviousNotificationValue:用于获取上次的通知值
*/
TCB_t * pxTCB;
eNotifyValue eOriginalNotifyState;
BaseType_t xReturn = pdPASS; configASSERT( xTaskToNotify );
pxTCB = ( TCB_t * ) xTaskToNotify; taskENTER_CRITICAL();
{
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue;
}
/*获取上一次的通知状态,保存在局部变量eOriginalNotifyState中,稍后覆盖方法会用到*/
eOriginalNotifyState = pxTCB->eNotifyState; /*更新TCB的通知状态为eNotified(已通知状态)*/ /*【1】*/
pxTCB->eNotifyState = eNotified; /*更新TCB的通知值(pxTCB->ulNotifiedValue),根据eAction的不同,达到传输不同种类通知的目的*/ /*【2】*/
switch( eAction )
{
case eSetBits :
pxTCB->ulNotifiedValue |= ulValue;
break; case eIncrement :
( pxTCB->ulNotifiedValue )++;
break; case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break; case eSetValueWithoutOverwrite :
if( eOriginalNotifyState != eNotified )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
/* The value could not be written to the task. */
/* 值无法被写入任务中 */
xReturn = pdFAIL;
}
break; case eNoAction:
/* The task is being notified without its notify value being
updated. */
/* 任务正在被通知,而它的通知值没有被更新 */
break;
} traceTASK_NOTIFY(); /* If the task is in the blocked state specifically to wait for a
notification then unblock it now. */
/* 如果目标任务是阻塞状态,特别是如果在等待通知,那么解除阻塞 */
if( eOriginalNotifyState == eWaitingNotification ) /*【3】*/
{
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
prvAddTaskToReadyList( pxTCB ); /* The task should not have been on an event list. */
/* 任务不应该已经添加进事件列表 */
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL ); #if( configUSE_TICKLESS_IDLE != 0 )
{
/* If a task is blocked waiting for a notification then
xNextTaskUnblockTime might be set to the blocked task's time
out time. If the task is unblocked for a reason other than
a timeout xNextTaskUnblockTime is normally left unchanged,
because it will automatically get reset to a new value when
the tick count equals xNextTaskUnblockTime. However if
tickless idling is used it might be more important to enter
sleep mode at the earliest possible time - so reset
xNextTaskUnblockTime here to ensure it is updated at the
earliest possible time. */
/* 如果一个任务阻塞等待通知,那么xNextTaskUnblockTime应该设置为阻塞任务时间外的时间。
如果任务因为一些原因(除了一个超时)被解除阻塞,xNextTaskUnblockTime通常保持不变。
因为当滴答计数器等于xNextTaskUnblockTime的时候,它会被重置为一个新的值。
无论如何,如果tickless idling 被使用,它可能是首先进入睡眠模式,
所以在这里重置xNextTaskUnblockTime来确保它被更新*/
prvResetNextTaskUnblockTime();
}
#endif if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* The notified task has a priority above the currently
executing task so a yield is required. */
/* 被通知的任务优先级超过了当前运行中的任务,所以需要进行切换(切换到被通知的任务) */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); return xReturn;
} #endif /* configUSE_TASK_NOTIFICATIONS */
2.1.2、生成通知信号函数中断版本 xTaskGenericNotifyFromISR()
函数原型:
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )
作为xTaskGenericNotify()的中断版本,xTaskGenericNotifyFromISR()中加了一些中段相关处理,我们可以看到,他们输入参数都相同(增加一个pxHigherPriorityTaskWoken参数用于指示执行期间是否有任务切换发生)。
所以这个函数实际上和xTaskGenericNotify()的操作相同。
- 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
- 根据需求更新TCB中的通知值ulNotifiedValue
- 解除目标任务的阻塞状态
2.1.3、一个自增通知的中断定制版 vTaskNotifyGiveFromISR():
函数原型:
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
这个函数是xTaskGenericNotifyFromISR()的压缩定制版,去掉了3个输入参数ulValue、eAction、pulPreviousNotificationValue和对应这3个参数相关的处理程序,因为它是特别为自增型通知定制的,与xTaskGenericNotifyFromISR()这个函数提高了效率,大概FreeRTOS作者认为自增通知是常用的通知类型,所以特意写了这个优化版本的函数。
所以这个函数实际上也和xTaskGenericNotify()的操作相同
源码分析:
- 将TCB中的通知状态标志eNotifyState设置为已经收到通知的状态
- 根据需求更新TCB中的通知值ulNotifiedValue(自增)
- 解除目标任务的阻塞状态
#if( configUSE_TASK_NOTIFICATIONS == 1 ) void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
eNotifyValue eOriginalNotifyState;
UBaseType_t uxSavedInterruptStatus; configASSERT( xTaskToNotify ); /* RTOS ports that support interrupt nesting have the concept of a
maximum system call (or maximum API call) interrupt priority.
Interrupts that are above the maximum system call priority are keep
permanently enabled, even when the RTOS kernel is in a critical section,
but cannot make any calls to FreeRTOS API functions. If configASSERT()
is defined in FreeRTOSConfig.h then
portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
failure if a FreeRTOS API function is called from an interrupt that has
been assigned a priority above the configured maximum system call
priority. Only FreeRTOS functions that end in FromISR can be called
from interrupts that have been assigned a priority at or (logically)
below the maximum system call interrupt priority. FreeRTOS maintains a
separate interrupt safe API to ensure interrupt entry is as fast and as
simple as possible. More information (albeit Cortex-M specific) is
provided on the following link:
http://www.freertos.org/RTOS-Cortex-M3-M4.html */ portASSERT_IF_INTERRUPT_PRIORITY_INVALID(); pxTCB = ( TCB_t * ) xTaskToNotify; uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
eOriginalNotifyState = pxTCB->eNotifyState;
pxTCB->eNotifyState = eNotified; /*【1】*/ /* 'Giving' is equivalent to incrementing a count in a counting
semaphore. */
/* 给予是等价于在计数信号量中增加一个计数 */
( pxTCB->ulNotifiedValue )++; /*【2】*/ traceTASK_NOTIFY_GIVE_FROM_ISR(); /* If the task is in the blocked state specifically to wait for a
notification then unblock it now. */
/* 如果任务在阻塞状态,明确地等待一个通知,然后马上解除阻塞*/
if( eOriginalNotifyState == eWaitingNotification ) /*【3】*/
{
/* The task should not have been on an event list. */
/* 任务不应该已经加入事件列表了 */
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL ); if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
( void ) uxListRemove( &( pxTCB->xGenericListItem ) );
prvAddTaskToReadyList( pxTCB );
}
else
{
/* The delayed and ready lists cannot be accessed, so hold
this task pending until the scheduler is resumed. */
/* 延时和准备列表无法被访问,所以保持这个任务挂起,知道调度器被恢复 */
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
} if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* The notified task has a priority above the currently
executing task so a yield is required. */
/* 通知任务已经比当前执行任务更高,所以需要进行切换 */
if( pxHigherPriorityTaskWoken != NULL )
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
} #endif /* configUSE_TASK_NOTIFICATIONS */
接下来我们回到API函数,看看这6个API的功能,以及它们调用的是哪个底层函数:
2.2接收通知
ulTaskNotifyTake() | 提取通知 |
适用于二值通知(eNoAction)和自增通知 |
xTaskNotifyWait() | 等待通知 | 适用所有通知,但不附带自增通知的自减功能 |
2.2.1、ulTaskNotifyTake()
函数原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
输入参数:
BaseType_t xClearCountOnExit | 退出时是否清除 |
TickType_t xTicksToWait | 最大等待时间 |
- xClearCountOnExit:这个参数可以传入pdTRUE或者pdFALSE
- xTicksToWait:在等待通知的时候,任务会进入阻塞状态,任务进入阻塞状态的最大时间(以Tick为单位,可以用pdMS_TO_TICKS()将毫秒换为tick)
源码分析:(展开折叠查看)
这个函数主要做了4件事:
- 改变TCB中的eNotifyState为正在等待通知状态
- 让任务进入阻塞或挂起状态等待通知
- 收到通知后,对通知值ulNotifiedValue进行操作(删除或自减)
- 改变TCB中的eNotifyState为空状态,因为读取通知的操作已经完成了
#if( configUSE_TASK_NOTIFICATIONS == 1 ) uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
TickType_t xTimeToWake;
uint32_t ulReturn; taskENTER_CRITICAL();
{
/* Only block if the notification count is not already non-zero. */
/* 仅仅当通知值为0的时候, 才进行阻塞操作 */
if( pxCurrentTCB->ulNotifiedValue == 0UL )
{
/* Mark this task as waiting for a notification. */
/* 屏蔽这个任务来等待通知 */
pxCurrentTCB->eNotifyState = eWaitingNotification; /*改变TCB中的eNotifyState 为 eWaitingNotification*/ /*【1】*/ if( xTicksToWait > ( TickType_t ) )
{
/* The task is going to block. First it must be removed
from the ready list. */
/* 任务将会阻塞。 但首先必须从准备列表移除 */
if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) )
{
/* The current task must be in a ready list, so there is
no need to check, and the port reset macro can be called
directly. */
/* 当前任务必须在准备列表,所以没有必要再检查,接口重置宏可以被直接调用 */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
} #if ( INCLUDE_vTaskSuspend == 1 ) /*【2】*/
{
/* 如果设置了 xTicksToWait 为 portMAX_DELAY。任务会直接挂起*/
if( xTicksToWait == portMAX_DELAY )
{
/* Add the task to the suspended task list instead
of a delayed task list to ensure the task is not
woken by a timing event. It will block
indefinitely. */
/* 把任务添加到挂起任务列表,而不是延时任务列表(阻塞状态会倒计时)。这是为了确认任务没有被时间事件唤醒。任务会被无限期的阻塞(直接挂起) */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* Calculate the time at which the task should be
woken if no notification events occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 如果没有通知事件发生,计算任务应该被唤醒的时间
这个可能溢出,但是没有关系,调度器会处理它的*/ xTimeToWake = xTickCount + xTicksToWait; /*计算唤醒时间*/
prvAddCurrentTaskToDelayedList( xTimeToWake ); /*把计算好的时间添加到延时列表。交给调度器处理*/
}
}
#else /* INCLUDE_vTaskSuspend */
{
/* Calculate the time at which the task should be
woken if the event does not occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 如果没有通知事件发生,计算任务应该被唤醒的时间
这个可能溢出,但是没有关系,调度器会处理它的*/
xTimeToWake = xTickCount + xTicksToWait;
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
#endif /* INCLUDE_vTaskSuspend */ traceTASK_NOTIFY_TAKE_BLOCK(); /* All ports are written to allow a yield in a critical
section (some will yield immediately, others wait until the
critical section exits) - but it is not something that
application code should ever do. */
/* 在临界区,所有接口被写入,来立刻允许一次切换(有一些会马上切换,其他会等待知道重要部分退出) , 但它不是一些应用代码应该做的重要的事*/
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE();
ulReturn = pxCurrentTCB->ulNotifiedValue;/*设置返回值为收到的通知值*/ if( ulReturn != 0UL ) /*【3】*/
{
if( xClearCountOnExit != pdFALSE )
{
pxCurrentTCB->ulNotifiedValue = 0UL;/*清零通知值*/
}
else
{
( pxCurrentTCB->ulNotifiedValue )--;/*减少通知值(对自增通知的特殊处理方法)*/
}
}
else
{
mtCOVERAGE_TEST_MARKER();
} pxCurrentTCB->eNotifyState = eNotWaitingNotification;/* 清除等待/接收通知状态 */ /*【4】*/
}
taskEXIT_CRITICAL(); return ulReturn;
} #endif /* configUSE_TASK_NOTIFICATIONS */
*在我们查看源码的时候,我们可以留意到,当我们设置输入参数为xTicksToWait为portMAX_DELAY的时候,而且INCLUDE_vTaskSuspend(激活挂起状态)宏定义为1的时候任务不是阻塞,而是直接挂起。
#if ( INCLUDE_vTaskSuspend == 1 ) /*【2】*/
{
/* 如果设置了 xTicksToWait 为 portMAX_DELAY。任务会直接挂起*/
if( xTicksToWait == portMAX_DELAY )
{
/* Add the task to the suspended task list instead
of a delayed task list to ensure the task is not
woken by a timing event. It will block
indefinitely. */
/* 把任务添加到挂起任务列表,而不是延时任务列表(阻塞状态会倒计时)。这是为了确认任务没有被时间事件唤醒。任务会被无限期的阻塞(直接挂起) */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* Calculate the time at which the task should be
woken if no notification events occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 如果没有通知事件发生,计算任务应该被唤醒的时间
这个可能溢出,但是没有关系,调度器会处理它的*/ xTimeToWake = xTickCount + xTicksToWait; /*计算唤醒时间*/
prvAddCurrentTaskToDelayedList( xTimeToWake ); /*把计算好的时间添加到延时列表。交给调度器处理*/
}
}
官方例子:
2.2.2、xTaskNotifyWait()
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
输入参数:
- ulBitsToClearOnEntry:在进入阻塞之前把指定的比特清除
- ulBitsToClearOnExit:在接收到通知并处理后把指定的比特位清除
- *pulNotificationValue:用于储存新到的通知值
- xTicksToWait:最大阻塞时间
返回值:
两种情况
源码分析:(展开折叠查看)
这个函数和ulTaskNotifyTake很像,主要做了5件事:
- 根据ulBitsToClearOnEntry先清除一下通知值相应的位
- 改变TCB中的eNotifyState为正在等待通知状态
- 让任务进入阻塞或挂起状态等待通知
- 收到通知后,对通知值ulNotifiedValue进行操作(根据ulBitsToClearOnExit清除相应位)
- 改变TCB中的eNotifyState为空状态,因为读取通知的操作已经完成了
#if( configUSE_TASK_NOTIFICATIONS == 1 ) BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
{
TickType_t xTimeToWake;
BaseType_t xReturn; /* */
taskENTER_CRITICAL();
{
/* Only block if a notification is not already pending. */
/* 只有没收到通知,才会阻塞任务(换句话说,如果现在已经收到通知了就不需要阻塞任务了,直接处理就好了) */
if( pxCurrentTCB->eNotifyState != eNotified )
{
/* Clear bits in the task's notification value as bits may get
set by the notifying task or interrupt. This can be used to
clear the value to zero. */
/* 清除任务通知值的比特位,因为这些比特肯能被通知任务或者中断置位了。
这个可以用于把值清0*/
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry; /*【1】*/ /* Mark this task as waiting for a notification. */
/* 记录这个任务为等待通知的状态*/
pxCurrentTCB->eNotifyState = eWaitingNotification; /*【2】*/ if( xTicksToWait > ( TickType_t ) )
{
/* The task is going to block. First it must be removed
from the ready list. */
/* 任务马上就要阻塞了,首先要把它移出准备列表 */
if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( UBaseType_t ) )
{
/* The current task must be in a ready list, so there is
no need to check, and the port reset macro can be called
directly. */
/* 当前任务肯定在准备列表中 ,所以没有必要检查了,接口重置宏可以被直接调用 */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
} #if ( INCLUDE_vTaskSuspend == 1 ) /*【3】*/
{
if( xTicksToWait == portMAX_DELAY )
{
/* Add the task to the suspended task list instead
of a delayed task list to ensure the task is not
woken by a timing event. It will block
indefinitely. */
/* 把任务加入挂起任务列表,而不是延时任务列表。这是为了确保任务没有被时间事件唤醒
这个任务会无限阻塞 */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* Calculate the time at which the task should be
woken if no notification events occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 计算在如果没有通知事件,发生任务应该被唤醒的时间 。
这个可能会导致溢出,但是没有关系。调度器会处理它的*/
xTimeToWake = xTickCount + xTicksToWait;
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
}
#else /* INCLUDE_vTaskSuspend */
{
/* Calculate the time at which the task should be
woken if the event does not occur. This may
overflow but this doesn't matter, the scheduler will
handle it. */
/* 计算在如果没有通知事件,发生任务应该被唤醒的时间 。
这个可能会导致溢出,但是没有关系。调度器会处理它的*/
xTimeToWake = xTickCount + xTicksToWait;
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
#endif /* INCLUDE_vTaskSuspend */ traceTASK_NOTIFY_WAIT_BLOCK(); /* All ports are written to allow a yield in a critical
section (some will yield immediately, others wait until the
critical section exits) - but it is not something that
application code should ever do. */
/* 写入所有接口,允许在临界区进行一次切换*/
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(); taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT(); if( pulNotificationValue != NULL )
{
/* Output the current notification value, which may or may not
have changed. */
/* 设置pulNotificationValue参数为当前通知值,不管它有没有改变*/
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue;
} /* If eNotifyValue is set then either the task never entered the
blocked state (because a notification was already pending) or the
task unblocked because of a notification. Otherwise the task
unblocked because of a timeout. */
/* 如果通知值*/
if( pxCurrentTCB->eNotifyState == eWaitingNotification )
{
/* A notification was not received. */
/*没收到通知(超时)*/
xReturn = pdFALSE;
}
else
{
/* A notification was already pending or a notification was
received while the task was waiting. */
/*在阻塞期间收到通知值,或者在调用这个函数之前就已经收到通知值 ,清除对应的通知值的位*/
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit; /*【4】*/
xReturn = pdTRUE;
} /*接收通知完成,修改TCB的eNotifyState为空状态*/
pxCurrentTCB->eNotifyState = eNotWaitingNotification; /*【5】*/
}
taskEXIT_CRITICAL(); return xReturn;
} #endif /* configUSE_TASK_NOTIFICATIONS */
官方使用例子:
这个例子是使用通知代替事件组,也就是置位通知
- 等待通知信号,任务会进入阻塞/挂起状态。
- 判断通知信号的第几位被置位了
3、最后笔者写了四个例子,提供大家参考
使用不带数据的二值任务通知(可以代替二进制信号量)
//Notify is used for binary semaphore
void vDemoTaskA(void *Parammenters)
{
for(;;)
{
//xTaskNotifyGive( HandleOfTaskB );
xTaskNotify( HandleOfTaskB, , eNoAction ); vTaskDelay( pdMS_TO_TICKS() );
}
} void vDemoTaskB(void *Parammenters)
{
for(;;)
{
ulTaskNotifyTake( pdTRUE , portMAX_DELAY );
//add your codes here
}
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }
使用自增的任务通知(可以代替计数信号量)
//Notify is used for counting semaphore
void vDemoTaskA(void *Parammenters)
{
for(;;)
{
xTaskNotifyGive( HandleOfTaskB );
xTaskNotifyGive( HandleOfTaskB );
xTaskNotifyGive( HandleOfTaskB ); vTaskDelay( pdMS_TO_TICKS() );
}
}
void vDemoTaskB(void *Parammenters)
{
uint32_t notifyCNT; for(;;)
{ if( notifyCNT = ulTaskNotifyTake( pdFALSE , portMAX_DELAY ) )
{
//should come in here for three times each turn
//add your codes here
} }
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }
使用置位任务通知(可以代替事件标志组)
//Notify is used for event group
void vDemoTaskA(void *Parammenters)
{
for(;;)
{
xTaskNotify( HandleOfTaskB, (uint32_t) , eSetBits );
xTaskNotify( HandleOfTaskB, (uint32_t)<< , eSetBits );
xTaskNotify( HandleOfTaskB, (uint32_t)<< , eSetBits ); vTaskDelay( pdMS_TO_TICKS() );
}
}
void vDemoTaskB(void *Parammenters)
{
#define ULONG_MAX 0xffffffff
uint32_t ulNotifiedValue=;
for(;;)
{
xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY ); if( ulNotifiedValue & () )
{
//add your codes here
}
if( ulNotifiedValue & (<<) )
{
//add your codes here
}
if( ulNotifiedValue & (<<) )
{
//add your codes here
}
if( ulNotifiedValue & (<<) )
{
//add your codes here
}
//you can add more events below if you need them
}
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }
使用传递消息任务通知(可以代替深度为一的消息队列)
//Notify is used for sending message
void vDemoTaskA(void *Parammenters)
{
uint32_t pTestBuff[] = { ,,,, };
int i; for(;;)
{
for(i=;i<;i++)
{
//xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithOverwrite ); //The notify can not be overwrite
xTaskNotify( HandleOfTaskB, pTestBuff[i] , eSetValueWithoutOverwrite );//The notify can be overwrite vTaskDelay( pdMS_TO_TICKS() );
}
}
}
void vDemoTaskB(void *Parammenters)
{
#define ULONG_MAX 0xffffffff
uint32_t ulNotifiedValue=;
uint32_t pRecBuff[]={};
int i; for(;;)
{
for(i=;i<;i++)
{
xTaskNotifyWait( 0x00 , ULONG_MAX , &ulNotifiedValue ,portMAX_DELAY );
pRecBuff[i] = ulNotifiedValue;
//add your codes here
}
}
} void vTaskCreate(void )
{
xTaskCreate( vDemoTaskA, "vDemoTaskA", , NULL, , NULL );
xTaskCreate( vDemoTaskB, "vDemoTaskB", , NULL, , &HandleOfTaskB );
} /*******************************************************************************
* Function Name : main
* Description :
* Input : None
* Output : None
* Return : None
*******************************************************************************/
int main( void )
{ vTaskCreate(); vTaskStartScheduler( ); while()
{
//should not be here
} }
FreeRTOS 任务与调度器(2)的更多相关文章
- FreeRTOS 任务与调度器(1)
前言: Task.c和Task.h文件内是FreeRTOS的核心内容,所有任务和调度器相关的API函数都在这个文件中,它包括下图这些内容FreeRTOS文件如下: Task.c和Task.h文件内是F ...
- FreeRTOS调度器
FreeRTOS----调度器 调度器的启动流程分析 当创建完任务之后,会调用vTaskStartScheduler()函数,启动任务调度器: void vTaskStartScheduler( vo ...
- 【freertos】005-启动调度器分析
前言 本节主要讲解启动调度器. 这些都是与硬件相关,所以会分两条线走:posix和cortex m3. 原文:李柱明博客:https://www.cnblogs.com/lizhuming/p/160 ...
- FreeRTOS --(9)任务管理之启动调度器
转载自 https://blog.csdn.net/zhoutaopower/article/details/107057528 在使用 FreeRTOS 的时候,一般的,先创建若干任务,但此刻任务并 ...
- FreeRTOS - 调度器
原文地址:http://www.cnblogs.com/god-of-death/p/6942641.html 绝大多数情况下,调度器的配置如下: 下面的说明基于上面的调度器配置: 如果有更高优先级的 ...
- 大数据之Yarn——Capacity调度器概念以及配置
试想一下,你现在所在的公司有一个hadoop的集群.但是A项目组经常做一些定时的BI报表,B项目组则经常使用一些软件做一些临时需求.那么他们肯定会遇到同时提交任务的场景,这个时候到底如何分配资源满足这 ...
- [Spring]支持注解的Spring调度器
概述 如果想在Spring中使用任务调度功能,除了集成调度框架Quartz这种方式,也可以使用Spring自己的调度任务框架. 使用Spring的调度框架,优点是:支持注解(@Scheduler),可 ...
- 编写简单的ramdisk(选择IO调度器)
前言 目前linux中包含anticipatory.cfq.deadline和noop这4个I/O调度器.2.6.18之前的linux默认使用anticipatory,而之后的默认使用cfq.我们在前 ...
- Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...
随机推荐
- 小技巧-mac修改finder菜单栏
效果: 方法: 添加:打开finder后,长按command,可以将其他app拖到菜单栏. 删除:同理,长按command,将不需要的图标拖出菜单栏即可. PS:强烈推荐gotoshell这个小工具, ...
- Spark性能优化(基于Spark 1.x)
Task优化: 1.慢任务的性能优化:可以考虑减少每个Partition处理的数据量,同时建议开启spark.speculation(慢任务推导,当检测的慢任务时,会同步开启相同的新任务,谁先完 ...
- DevOps自动化工具集合
版本控制&协作开发:GitHub.GitLab.BitBucket.SubVersion.Coding.Bazaar 自动化构建和测试:Apache Ant.Maven .Selenium.P ...
- node.js 基础学习
node.js 是一个 javaScript 运行环境,可以让 js 运行在服务端. 在 nodejs 环境下,可以运行 javascript 基本语法,可以在nodejs 中执行一些无法在浏览器端执 ...
- [转]extern "C"的作用
extern "C"的主要作用就是为了能够正确实现C++代码调用其它C语言代码. 加上extern “C”后,会指示编译器将这部分代码按C语言进行编译,而不是C++的.这是因为C+ ...
- VS2015 无法启动 IIS Express Web 服务器 解决方案
VS2015 IIS Express 无法启动Web 解决方案 [亲测已成功] 1.我的电脑—管理—事件查看器—Windows日志—应用程序: 详细信息会提示你:[模块 DLL C:\Program ...
- VMware安装CentOS6
1. 搭建虚拟化环境常见故障讲解 2. 安装CentOS Linux系统 ……………… PS:运维老鸟教你安装centos6.5如何选择安装包 3. 远程连接LInux ip配置 注意:不用做任何修改 ...
- zk理解(转载自邬兴亮---www.cnblogs.com/wuxl360/p/5817471.html)
一.分布式协调技术 在给大家介绍ZooKeeper之前先来给大家介绍一种技术——分布式协调技术.那么什么是分布式协调技术?那么我来告诉大家,其实分布式协调技术 主要用来解决分布式环境当中多个进程之间的 ...
- asp.net core 如何集成kindeditor并实现图片上传功能
准备工作 1.visual studio 2015 update3开发环境 2.net core 1.0.1 及以上版本 目录 新建asp.net core web项目 下载kindeditor ...
- BeanFactory中Bean的生命周期
Bean的生命周期图解 集体过程如下: 当调用者通过getBean(beanName)向容器请求某一个Bean时,如果容器注册了org.springframework.beans.factory.co ...