目录
2.taskDISABLE_INTERRUPTS() 关闭中断
3.taskENABLE_INTERRUPTS() 打开中断
4.taskENTER_CRITICAL()进入临界区 taskEXIT_CRITICAL()退出临界区
5.taskENTER_CRITICAL_FROM_ISR()进入临界区 和 taskEXIT_CRITICAL_FROM_ISR()退出临界区
6.vTaskStartScheduler(启动RTOS任务调度)
7.vTaskEndScheduler(停止RTOS内核滴答)(只在x86 Real Mode PC端口上实现)
英文原文:
内核控制(Kernel Control)
1.taskYIELD() 任务切换
taskYIELD();
taskYIELD()用于请求切换到另一个任务的上下文。但是,如果没有其他任务比调用taskYIELD()的任务具有更高或相同的优先级,那么RTOS调度器将简单地选择调用taskYIELD()的任务再次运行。
如果configUSE_PREEMPTION设置为1,那么RTOS调度器将总是运行能够运行的最高优先级的任务,所以调用taskYIELD()将永远不会导致切换到更高优先级的任务。
2.taskDISABLE_INTERRUPTS() 关闭中断
taskDISABLE_INTERRUPTS();
如果正在使用的端口支持configMAX_SYSCALL_INTERRUPT_PRIORITY(或configMAX_API_CALL_INTERRUPT_PRIORITY)常数,那么taskDISABLE_INTERRUPTS将禁用所有中断,或者掩码(禁用)中断直到configMAX_SYSCALL_INTERRUPT_PRIORITY设置为止。检查正在使用的端口的taskDISABLE_INTERRUPTS的实现。
如果正在使用的端口不支持configMAX_SYSCALL_INTERRUPT_PRIORITY常数,那么taskDISABLE_INTERRUPTS()将全局禁用所有可屏蔽中断。
通常这个宏不会被直接调用,taskENTER_CRITICAL()和taskEXIT_CRITICAL()应该在它的位置使用。
3.taskENABLE_INTERRUPTS() 打开中断
taskENABLE_INTERRUPTS();
宏启用微控制器中断。通常这个宏不会被直接调用,taskENTER_CRITICAL()和taskEXIT_CRITICAL()应该在它的位置使用。
4.taskENTER_CRITICAL()进入临界区 taskEXIT_CRITICAL()退出临界区
void taskENTER_CRITICAL( void );
void taskEXIT_CRITICAL( void );
通过调用taskENTER_CRITICAL()进入临界区,然后通过调用taskEXIT_CRITICAL()退出临界区。
宏 taskENTER_CRITICAL() 和宏 taskEXIT_CRITICAL() 提供了一个基本的关键段实现,它通过简单地禁用中断来工作,可以是全局的,也可以是特定的中断优先级。参见vTaskSuspendAll() RTOS API函数获取在不禁用中断的情况下创建临界区的信息。
如果正在使用的FreeRTOS端口没有使用configMAX_SYSCALL_INTERRUPT_PRIORITY内核配置常量(也称为configMAX_API_CALL_INTERRUPT_PRIORITY),那么调用taskENTER_CRITICAL()将使中断全局禁用。
如果正在使用的FreeRTOS端口使用了configMAX_SYSCALL_INTERRUPT_PRIORITY内核配置常量,那么调用taskENTER_CRITICAL()将使中断处于或低于中断优先级,中断优先级由禁用configMAX_SYSCALL_INTERRUPT_PRIORITY设置,所有高优先级中断启用。
抢占式上下文切换只发生在中断内部,所以当中断被禁用时不会发生。因此,调用taskENTER_CRITICAL()的任务保证在临界区退出之前一直保持在Running状态,除非任务显式地试图阻塞或放弃(它不应该在临界区内部这样做)。
对taskENTER_CRITICAL()和taskEXIT_CRITICAL()的调用被设计成嵌套。因此,只有在对taskENTER_CRITICAL()的每次调用都执行一次对taskEXIT_CRITICAL()的调用时,一个临界区才会被退出。
临界段必须保持非常短,否则它们将对中断响应时间产生不利影响。每个对taskENTER_CRITICAL()的调用必须与对taskEXIT_CRITICAL()的调用紧密配对。
FreeRTOS API函数不能从临界区调用。
taskENTER_CRITICAL()和taskEXIT_CRITICAL()不能从中断服务例程(ISR)中调用——参见taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中的中断安全等量。
使用示例:
/* 利用临界段的函数 */
void vDemoFunction( void )
{
/* 进入临界区。在本例中,这个函数本身是在临界区中调用的,因此进入这个临界区将导致嵌套深度为2 */
taskENTER_CRITICAL();
/* 执行此处临界区所保护的操作 */
/* 退出临界区。在这个例子中,这个函数本身是从一个临界区调用的,所以这个对taskEXIT_CRITICAL()的调用会将嵌套计数减少1,但不会导致中断被启用 */
taskEXIT_CRITICAL();
}
/* 在临界区中调用vDemoFunction()的任务 */
void vTask1( void * pvParameters )
{
for( ;; )
{
/* 在这里执行一些功能 */
/* 调用taskENTER_CRITICAL()来创建一个临界区 */
taskENTER_CRITICAL();
/* 执行这里需要临界部分的代码。 */
/* 对taskENTER_CRITICAL()的调用可以嵌套,因此调用包含自己对taskENTER_CRITICAL()和taskEXIT_CRITICAL()的调用的函数是安全的。 */
vDemoFunction();
/* 需要临界段的操作完成,因此退出临界段。在调用taskEXIT_CRITICAL()之后,嵌套深度将为零,因此中断将被重新启用。 */
taskEXIT_CRITICAL();
}
}
5.taskENTER_CRITICAL_FROM_ISR()进入临界区 和 taskEXIT_CRITICAL_FROM_ISR()退出临界区
UBaseType_t taskENTER_CRITICAL_FROM_ISR( void );
void taskEXIT_CRITICAL_FROM_ISR( UBaseType_t uxSavedInterruptStatus );
可用于中断服务例程(ISR)的taskENTER_CRITICAL()和taskEXIT_CRITICAL()的版本。
在ISR中,通过调用taskENTER_CRITICAL_FROM_ISR()进入临界区,然后通过调用taskEXIT_CRITICAL_FROM_ISR()退出临界区。
宏taskENTER_CRITICAL_FROM_ISR()和宏taskEXIT_CRITICAL_FROM_ISR提供了一个基本的关键段实现,它通过简单地禁用中断来工作,无论是全局的,还是特定的中断优先级级别。
如果正在使用的FreeRTOS端口支持中断嵌套,那么调用taskENTER_CRITICAL_FROM_ISR()将在由configMAX_SYSCALL_INTERRUPT_PRIORITY(或configMAX_API_CALL_INTERRUPT_PRIORITY)内核配置常量设置的中断优先级及以下禁用中断,并保持所有其他中断优先级启用。如果正在使用的FreeRTOS端口不支持中断嵌套,那么taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()将不起作用。
对taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()的调用被设计为嵌套,但是宏如何使用的语义与taskENTER_CRITICAL()和taskEXIT_CRITICAL()的等价函数不同。
临界段必须保持非常短,否则会对嵌套的高优先级中断的响应时间产生不利影响。每个对taskENTER_CRITICAL_FROM_ISR()的调用必须与对taskEXIT_CRITICAL_FROM_ISR()的调用紧密配对。
FreeRTOS API函数不能从临界区调用。
参数:
uxSavedInterruptStatus | taskEXIT_CRITICAL_FROM_ISR()使用uxSavedInterruptStatus作为唯一参数。用作uxSavedInterruptStatus参数的值必须是匹配调用taskENTER_CRITICAL_FROM_ISR()返回的值。 taskENTER_CRITICAL_FROM_ISR()不带任何参数。 |
返回值:
taskENTER_CRITICAL_FROM_ISR()返回宏被调用前的中断掩码状态。taskENTER_CRITICAL_FROM_ISR()返回的值必须作为匹配调用taskEXIT_CRITICAL_FROM_ISR()的uxSavedInterruptStatus参数。
taskEXIT_CRITICAL_FROM_ISR()不返回值。
使用示例:
/* 从ISR调用的函数。 */
void vDemoFunction( void )
{
UBaseType_t uxSavedInterruptStatus;
/* 进入临界区。
在本例中,这个函数本身是在临界区中调用的,因此进入这个临界区将导致嵌套深度为2。
将taskENTER_CRITICAL_FROM_ISR()返回的值保存到本地堆栈变量中,
这样它就可以传递给taskEXIT_CRITICAL_FROM_ISR()。 */
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 执行此处临界区所保护的操作。 */
/* 退出临界区。
在这个例子中,这个函数本身是在临界区被调用的,所以在uxSavedInterruptStatus中
存储一个值之前中断就已经被禁用了,因此将uxSavedInterruptStatus传递给
taskEXIT_CRITICAL_FROM_ISR()不会导致中断被重新启用。 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}
/* 在中断服务例程中调用vDemoFunction()的任务。 */
void vDemoISR( void )
{
UBaseType_t uxSavedInterruptStatus;
/* 调用taskENTER_CRITICAL_FROM_ISR()创建一个临界区,将返回值保存到本地堆栈变量中。 */
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 执行这里需要临界部分的代码。 */
/* 对taskENTER_CRITICAL_FROM_ISR()的调用可以嵌套,因此调用包含对
taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()
的自己的调用的函数是安全的。 */
vDemoFunction();
/* 需要临界段的操作完成,因此退出临界段。假设在进入该ISR时启用了中断,
保存在uxSavedInterruptStatus中的值将导致中断被重新启用。*/
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}
6.vTaskStartScheduler(启动RTOS任务调度)
void vTaskStartScheduler( void );
启动RTOS调度程序。调用RTOS之后,内核可以控制执行哪些任务以及何时执行。
空闲任务和计时器守护任务(可选)在RTOS调度器启动时自动创建。
vTaskStartScheduler()只会在没有足够的RTOS堆可用来创建空闲或计时器守护进程任务时返回。
所有的RTOS演示应用程序项目都包含使用vTaskStartScheduler()的例子,通常在main.c中的main()函数中。
使用示例:
void vAFunction( void )
{
// 任务可以在启动RTOS调度器之前或之后创建
xTaskCreate( vTaskCode,
"NAME",
STACK_SIZE,
NULL,
tskIDLE_PRIORITY,
NULL );
// 启动实时调度程序。
vTaskStartScheduler();
// 除非内存不足,否则不会到达这里。
}
7.vTaskEndScheduler(停止RTOS内核滴答)(只在x86 Real Mode PC端口上实现)
void vTaskEndScheduler( void );
注意:这只在x86 Real Mode PC端口上实现。
停止RTOS内核滴答。所有创建的任务将被自动删除和多任务(抢占或合作)将停止。然后从调用vTaskStartScheduler()的地方继续执行,就像vTaskStartScheduler()刚刚返回一样。
请参阅演示应用程序文件主。例如,使用vTaskEndScheduler()。
vTaskEndScheduler()要求在可移植层中定义一个出口函数(参见port中的vPortEndScheduler())。c表示PC端口)。这将执行特定于硬件的操作,例如停止RTOS内核滴答。
vTaskEndScheduler()将导致RTOS内核分配的所有资源被释放——但不会释放应用程序任务分配的资源。
使用示例:
void vTaskCode( void * pvParameters )
{
for( ;; )
{
// 任务代码
// 在某些时候,我们希望结束实时内核处理
// 所以调用 ...
vTaskEndScheduler ();
}
}
void vAFunction( void )
{
// 在启动RTOS内核之前创建至少一个任务。
xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
// 使用抢占启动实时内核。
vTaskStartScheduler();
// 只有当vTaskCode()任务调用vTaskEndScheduler()时才会到达这里。当我们回到这里时,我们又回到了单任务执行。
}
8.vTaskSuspendAll(挂起任务调度器)
void vTaskSuspendAll( void );
挂起任务调度器。挂起调度器可以防止发生上下文切换,但会启用中断。如果一个中断请求在调度器挂起时进行上下文切换,那么该请求将保持挂起,只有在调度器恢复(未挂起)时才执行。
在之前调用vTaskSuspendAll()之后,调用xTaskResumeAll()将调度器从暂停状态转换出来。
对vTaskSuspendAll()的调用可以嵌套。在调度器离开suspend状态并重新进入Active状态之前,对xTaskResumeAll()的调用必须与之前对vTaskSuspendAll()的调用相同的次数。
xTaskResumeAll()只能从正在执行的任务中调用,因此在调度器处于初始化状态(在调度器启动之前)时不能调用。
当调度程序挂起时,其他FreeRTOS API函数不能被调用。
有可能导致上下文切换的API函数(例如vTaskDelayUntil(), xQueueSend()等)一定不能在调度程序挂起时调用。
使用示例:
/* 一个暂停然后恢复调度程序的函数。 */
void vDemoFunction( void )
{
/* 这个函数暂停调度程序。当从vtas1调用它时,调度程序已经挂起,
因此这个调用创建了一个嵌套深度为2的嵌套深度。*/
vTaskSuspendAll();
/* 在这里执行一个操作。 */
/* 由于对vTaskSuspendAll()的调用是嵌套的,
因此在这里恢复调度器不会导致调度器重新进入活动状态。 */
xTaskResumeAll();
}
void vTask1( void * pvParameters )
{
for( ;; )
{
/* 在这里执行一些操作。 */
/* 在某些情况下,任务希望执行一个操作,但在此期间它不希望被交换出去,
或者它希望访问从另一个任务(但不是从中断)访问的数据。它不能使用
taskENTER_CRITICAL()/taskEXIT_CRITICAL(),因为操作的长度可能
会导致中断被错过。 */
/* 防止调度器执行上下文切换。 */
vTaskSuspendAll();
/* 请在此处执行操作。不需要使用临界段,因为任务拥有除中断服务例程使用的所有处理时间。*/
/* 对vTaskSuspendAll()的调用可以嵌套,因此调用一个(非API)函数是安全的,
该函数也包含对vTaskSuspendAll()的调用。当调度器挂起时,不应该调用API函数。 */
vDemoFunction();
/* 操作完成。将调度程序设置回Active状态。 */
if( xTaskResumeAll() == pdTRUE )
{
/* 在xTaskResumeAll()中发生了上下文切换。 */
}
else
{
/* 在xTaskResumeAll()中没有发生上下文切换。 */
}
}
}
9.xTaskResumeAll(恢复暂停后的任务调度器)
BaseType_t xTaskResumeAll( void );
通过调用vTaskSuspendAll()恢复调度器暂停后的调度器。
xTaskResumeAll()只恢复调度器。它不会取消之前通过调用vTaskSuspend()挂起的任务。
返回值:
如果恢复调度程序导致上下文切换,则返回pdTRUE,否则返回pdFALSE。
使用示例:
void vTask1( void * pvParameters )
{
for( ;; )
{
/* Task code goes here. */
/* ... */
/* 在某些情况下,任务希望执行一个长时间的操作,而在此期间它不希望被换出。
它不能使用taskENTER_CRITICAL ()/taskEXIT_CRITICAL(),
因为操作的长度可能会导致中断被错过——包括节拍。
防止RTOS内核换出任务。 */
vTaskSuspendAll();
/* 请在此处执行操作。不需要使用临界区,因为我们拥有所有的微控制器处理时间。
在此期间,中断仍将运行,实时RTOS内核计时计数将保持不变。 */
/* ... */
/* 操作完成。重启RTOS内核。我们希望强制进行上下文切换——但是如果恢复调度器
已经导致了上下文切换,那就没有意义了。 */
if( !xTaskResumeAll () )
{
taskYIELD ();
}
}
}
10.vTaskStepTick(设置系统节拍值)
void vTaskStepTick( TickType_t xTicksToJump );
如果RTOS配置为使用无tick的空闲功能,那么tick中断将被停止,并且微控制器处于低功耗状态,每当空闲任务是唯一能够执行的任务时。在退出低功耗状态时,必须纠正滴答计数值,以反映停止时所经过的时间。
如果一个FreeRTOS端口包含一个默认的portSUPPRESS_TICKS_AND_SLEEP()实现,那么vTaskStepTick()将在内部使用,以确保维护正确的tick计数值。vTaskStepTick()是一个公共API函数,它允许重写默认的portSUPPRESS_TICKS_AND_SLEEP()实现,并在所使用的端口不提供默认值时提供portSUPPRESS_TICKS_AND_SLEEP()。
configUSE_TICKLESS_IDLE配置常量必须设置为1,以便vTaskStepTick()可用。
参数:
xTicksToJump | 自滴答中断停止以来已经过的RTOS滴答数。对于正确的操作,参数必须小于或等于portSUPPRESS_TICKS_AND_SLEEP()参数。 |
使用示例:
该示例显示了对多个函数的调用。只有vTaskStepTick()是FreeRTOS API的一部分。其他功能特定于所用硬件上可用的时钟和节能模式,因此必须由应用程序编写人员提供。
/* 首先定义portSUPPRESS_TICKS_AND_SLEEP()。该参数是到下一次内核需要执行之前的时间,单位为滴答。 */
#define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
/* 定义portSUPPRESS_TICKS_AND_SLEEP()调用的函数。 */
void vApplicationSleep( TickType_t xExpectedIdleTime )
{
unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;
/* 从时间源读取当前时间,当微控制器处于低功耗状态时,该时间源将保持工作状态。 */
ulLowPowerTimeBeforeSleep = ulGetExternalTime();
/* 停止正在生成滴答中断的计时器。 */
prvStopTickInterruptTimer();
/* 配置一个中断,以便在内核下一次需要执行时将微控制器从低功耗状态中取出。
当微控制器处于低功耗状态时,中断必须从一个保持运行的源产生。 */
vSetWakeTimeInterrupt( xExpectedIdleTime );
/* 进入低功耗状态 */
prvSleep();
/* 确定微控制器实际处于低功耗状态的时间,如果微控制器是由一个非vSetWakeTimeInterrupt()
调用配置的中断而退出低功耗模式,那么这个时间将小于xExpectedIdleTime。注意,调度程序
在调用portSUPPRESS_TICKS_AND_SLEEP()之前被挂起,并在portSUPPRESS_TICKS_AND_SLEEP()
返回时恢复。因此,在此函数完成之前,不会执行其他任务。 */
ulLowPowerTimeAfterSleep = ulGetExternalTime();
/* 修正内核滴答计数,以反映微控制器在低功耗状态下所花费的时间。 */
vTaskStepTick( ulLowPowerTimeAfterSleep - ulLowPowerTimeBeforeSleep );
/* 重新启动正在生成滴答中断的计时器。 */
prvStartTickInterruptTimer();
}