FreeRTOS 教程指南 学习笔记 第三章 任务管理(二)

八、空闲任务和空闲任务回调

示例4中创建的任务的大部分时间都处于阻塞状态。在此状态下,它们无法运行,因此无法由调度程序选择。
必须始终有一个任务可以进入运行状态。为了确保这种情况,当调用vTaskStartScheduler()时,调度程序会自动创建一个空闲任务。空闲的任务只需要呆在一个循环中——因此,就像最初的第一个示例中的任务一样,它总是能够运行。
空闲任务具有最低的优先级(优先级为零),以确保它永远不会阻止更高优先级的应用程序任务进入运行状态——尽管没有什么可以阻止应用程序设计人员在空闲任务优先级上创建任务,从而共享空闲任务优先级。FreeRTOSConfig.h中的configIDLE_SHOULD_YIELD编译时配置常数可用于防止空闲任务消耗处理时间,而这些处理时间将更有效地分配给应用程序任务。configIDLE_SHOULD_YIELD在第3.12节,调度算法中被描述。
以最低优先级运行可确保一旦较高优先级任务进入已就绪状态,空闲任务就会转换出“正在运行”状态。这可以在图17中的时间tn中看到,在那里空闲任务被立刻退出,让任务2离开阻塞状态并立刻执行。也就是说,任务2已经抢占了空闲的任务。抢占会自动发生,并且不通知被抢占的任务。
注意:如果一个应用程序使用vTaskDelete()API函数,那么空闲任务必须不能缺乏处理时间。这是因为空闲任务负责在删除任务后清理内核资源。

空闲任务回调函数

通过使用空闲任务回调函数,可以将应用程序特定的功能直接添加到空闲任务中———该函数是空闲任务循环的每次迭代自动调用一次的函数。
空闲任务回调的常用用途包括:

  • 执行低优先级、后台处理或连续处理功能。
  • 测量备用处理能力的量。(只有在所有高优先级应用程序任务没有工作可执行时,空闲任务才会运行;因此,测量分配给空闲任务的处理时间可以清楚显示空闲的处理时间。)
  • 将处理器置于低功率模式,在没有要执行的应用处理时提供简单和自动的省电方法(尽管使用此方法可以实现的省电小于使用第10章低功率支持中描述的无滴答空闲模式可以实现的)。

空闲任务回调功能实现的限制
空闲任务回调功能必须遵守以下规则:

  • 一个空闲任务回调函数永远不能尝试进入阻塞或挂起。注意:以任何方式阻止空闲任务都可能导致没有可用的任务进入运行状态的场景。
  • 如果应用程序使用了vTaskDelete()API函数,那么空闲任务回调必须始终在合理的时间段内返回给其调用者。这是因为空闲任务负责在删除任务后清理内核资源。如果空闲任务永久保持在空闲回调功能中,则无法进行此清理。
//Listing 28. The idle task hook function name and prototype
void vApplicationIdleHook( void );
Example 7. Defining an idle task hook function

示例4中使用阻塞vTaskDelay()API调用在执行空闲任务时产生了大量的空闲时间,因为这两个应用程序任务都处于“阻塞”状态。示例7通过添加一个空闲回调函数来利用这个空闲时间,它的源代码如清单29所示。

//Listing 29. A very simple Idle hook function
/* Declare a variable that will be incremented by the hook function. */
volatile uint32_t ulIdleCycleCount = 0UL;
/* Idle hook functions MUST be called vApplicationIdleHook(), take no parameters, and return void. */
void vApplicationIdleHook( void )
{/* This hook function does nothing but increment a counter. */ulIdleCycleCount++;
}

configUSE_IDLE_HOOK必须在FreeRTOSConfig.h中设置为1,才能调用空闲回调函数。实现创建任务的函数将轻微修改,以打印出ulIdleCycleCount值,如清单30所示。

//Listing 30. The source code for the example task now prints out the ulIdleCycleCount value
void vTaskFunction( void *pvParameters )
{char *pcTaskName;const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );/* The string to print out is passed in via the parameter. Cast this to a character pointer. */pcTaskName = ( char * ) pvParameters;/* As per most tasks, this task is implemented in an infinite loop. */for( ;; ){/* Print out the name of this task AND the number of times ulIdleCycleCount has been incremented. */vPrintStringAndNumber( pcTaskName, ulIdleCycleCount );/* Delay for a period of 250 milliseconds. */vTaskDelay( xDelay250ms );}}

示例7所产生的输出如图21所示。它显示了空闲任务回调函数在应用程序任务的每次迭代之间被调用了大约400万次(迭代的次数取决于执行演示的硬件的速度)。

九、更改任务的优先级

The vTaskPrioritySet() API Function

vTaskPrioritySet()API函数可用于在调度程序启动后更改任何任务的优先级。注意,只有在FreeRTOSConfig.h中将INCLUDE_vTaskPrioritySet设置为1时,vTaskPrioritySet()API函数才可用。

//Listing 31. The vTaskPrioritySet() API function prototype
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
/*参数pxTask:正在修改其优先级的任务的句柄(主题任务)—请参见xTaskCreate()API函数的 pxCreatedTask参数,以了解有关获取任务句柄的信息。一个任务可以通过传递NULL来代替一个有效的任务句柄来更改它自己的优先级。*/
/*参数uxNewPriority:要设置的主题任务的优先级。这将被自动限制为(configMAX_PRIORITIES-1)的最大可用优先级,其中configMAX_PRIORITIES是在FreeRTOSConfig.h头文件中设置的常数。*/
The uxTaskPriorityGet() API Function

uxTaskPriorityGet()API函数可用于查询任务的优先级。注意,只有在FreeRTOSConfig.h中INCLUDE_uxTaskPriorityGet设置为1时,uxTaskPriorityGet()API函数才可用。

//Listing 32. The uxTaskPriorityGet() API function prototype
UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );
/*参数pxTask:正在查询其优先级的任务的句柄(主题任务)—请参见xTaskCreate()API函数的pxCreatedTask参数,以了解有关获取任务句柄的信息。任务可以通过传递NULL代替有效的任务句柄来查询自己的优先级。*/
/*返回值UBaseType_t:当前分配给正在被查询的任务的优先级。*/
Example 8. Changing task priorities

调度程序将始终选择就绪状态中优先级最高的任务作为进入正在运行状态的任务。示例8通过使用vTaskPrioritySet()API函数来更改两个任务的优先级来演示这一点。
示例8以两个不同的优先级创建了两个任务。这两个任务都没有做出任何可能导致其进入阻塞状态的API函数调用,因此两者都始终处于“已就绪”状态或“正在运行”状态。因此,具有最高相对优先级的任务将始终是调度程序选择的处于运行状态的任务。
示例8的行为如下:

  1. 任务1(清单33)是以最高优先级创建的,因此保证要先运行。在将Task 2的优先级(清单34)提高到其自身的优先级以上之前,任务1将打印出几个字符串。
  2. 任务2具有最高相对优先级,即开始运行(进入已运行状态)。任意一次只能有一个任务处于“运行”状态,因此当任务2处于“运行”状态时,任务1处于“就绪”状态。
  3. 任务2在将自己的优先级设置为任务1的优先级以下之前打印出一条消息。
  4. 任务2将其优先级降低意味着任务1再次成为最高优先级的任务,因此任务1重新进入“运行”状态,迫使任务2回到“就绪”状态。
void vTask1( void *pvParameters )
{UBaseType_t uxPriority;/* This task will always run before Task 2 as it is created with the higher priority. Neither Task 1 nor Task 2 ever block so both will always be in either the Running or the Ready state. Query the priority at which this task is running - passing in NULL means "return the calling task’s priority". */uxPriority = uxTaskPriorityGet( NULL );for( ;; ){/* Print out the name of this task. */vPrintString( "Task 1 is running\r\n" );/* Setting the Task 2 priority above the Task 1 priority will cause Task 2 to immediately start running (as then Task 2 will have the higher priority of the two created tasks). Note the use of the handle to task 2 (xTask2Handle) in the call to vTaskPrioritySet(). Listing 35 shows how the handle was obtained. */vPrintString( "About to raise the Task 2 priority\r\n" );vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );/* Task 1 will only run when it has a priority higher than Task 2. Therefore, for this task to reach this point, Task 2 must already have executed and set its priority back down to below the priority of this task. */}}void vTask2( void *pvParameters )
{UBaseType_t uxPriority;/* Task 1 will always run before this task as Task 1 is created with the higher priority. Neither Task 1 nor Task 2 ever block so will always be in either the Running or the Ready state. Query the priority at which this task is running - passing in NULL means "return the calling task’s priority". */uxPriority = uxTaskPriorityGet( NULL );for( ;; ){/* For this task to reach this point Task 1 must have already run and set the priority of this task higher than its own. Print out the name of this task. */vPrintString( "Task 2 is running\r\n" );/* Set the priority of this task back down to its original value.  Passing in NULL as the task handle means "change the priority of the  calling task". Setting the priority below that of Task 1 will cause  Task 1 to immediately start running again – pre-empting this task. */vPrintString( "About to lower the Task 2 priority\r\n" );vTaskPrioritySet( NULL, ( uxPriority - 2 ) );}
}/* Declare a variable that is used to hold the handle of Task 2. */
TaskHandle_t xTask2Handle = NULL;
int main( void )
{/* Create the first task at priority 2. The task parameter is not used and set to NULL. The task handle is also not used so is also set to NULL. */xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );/* The task is created at priority 2 ______^. *//* Create the second task at priority 1 - which is lower than the priority given to Task 1. Again the task parameter is not used so is set to NULL - BUT this time the task handle is required so the address of xTask2Handle is passed in the last parameter. */xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );/* The task handle is the last parameter _____^^^^^^^^^^^^^ *//* Start the scheduler so the tasks start executing. */vTaskStartScheduler(); /* If all is well then main() will never reach here as the scheduler will now be running the tasks. If main() does reach here then it is likely there was insufficient heap memory available for the idle task to be created.  Chapter 2 provides more information on heap memory management. */for( ;; );
}


十、删除任务

The vTaskDelete() API Function

任务可以使用vTaskDelete()API函数来删除自身或任何其他任务。注意,只有当vTeeRTOSConfig.h中INCLUDE_vTaskDelete设置为1时,vTaskDelete()API函数才可用。
已删除的任务不再存在,并且无法再次进入正在运行的状态。
空闲任务有责任释放分配给已被删除的任务的内存。因此,使用vTaskDelete()API函数的应用程序不会完全耗尽所有处理时间的空闲任务是很重要的。
注意:当删除任务时,只有内核本身分配给任务的内存才会自动释放。任务自行分配内存或其他资源必须被手动显式地释放。

//Listing 36. The vTaskDelete() API function prototype
void vTaskDelete( TaskHandle_t pxTaskToDelete );
/*参数pxTaskToDelete :要删除的任务的句柄(主题任务)—请参见xTaskCreate()API功能的pxCreateTask参数,以了解有关获取任务句柄的信息。任务可以通过传递NULL来代替有效的任务句柄来删除自身。*/
Example 9. Deleting tasks

这是一个非常简单的例子,其行为如下:

  1. 任务1由main()创建,优先级为1。当它运行时,它将在优先级为2处创建任务2。任务2现在是优先级最高的任务,因此它将立即开始执行。main()的源代码如清单37所示,任务1的源代码如清单38所示。
  2. 任务2只做一些可以删除自己的事情。它可以通过将NULL传递给vTaskDelete()来删除自己,但是为了演示目的,它使用自己的任务句柄。任务2的源代码如清单39所示。
  3. 当任务2被删除时,任务1再次是最高优先级的任务,因此继续执行-此时它调用vTaskDelay()进行短时间的阻塞。
  4. 空闲任务在任务1处于阻塞状态时执行,并释放分配给现在已删除的任务2的内存。
  5. 当任务1离开阻塞状态时,它再次成为最高优先级的准备状态任务,因此优先于空闲任务。当它进入“运行”状态时,它将再次创建任务2,因此它将继续进行。
int main( void )
{/* Create the first task at priority 1. The task parameter is not used so is set to NULL. The task handle is also not used so likewise is set to NULL. */xTaskCreate( vTask1, "Task 1", 1000, NULL, 1, NULL );/* The task is created at priority 1 ______^. *//* Start the scheduler so the task starts executing. */vTaskStartScheduler(); /* main() should never reach here as the scheduler has been started. */for( ;; );
}TaskHandle_t xTask2Handle = NULL;
void vTask1( void *pvParameters )
{const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );for( ;; ){/* Print out the name of this task. */vPrintString( "Task 1 is running\r\n" );/* Create task 2 at a higher priority. Again the task parameter is not used so is set to NULL - BUT this time the task handle is required so the address of xTask2Handle is passed as the last parameter. */xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );/* The task handle is the last parameter _____^^^^^^^^^^^^^ *//* Task 2 has/had the higher priority, so for Task 1 to reach here Task 2 must have already executed and deleted itself. Delay for 100  milliseconds. */vTaskDelay( xDelay100ms );}}void vTask2( void *pvParameters )
{/* Task 2 does nothing but delete itself. To do this it could call vTaskDelete() using NULL as the parameter, but instead, and purely for demonstration purposes, it calls vTaskDelete() passing its own task handle. */vPrintString( "Task 2 is running and about to delete itself\r\n" );vTaskDelete( xTask2Handle );
}


十一、线程本地存储

TBD,原文中还未编写

十二、调度算法

任务状态和事件的概述

实际正在运行的任务(使用处理时间)处于“正在运行”状态。在单核处理器上,在任何给定时间只能有一个处于运行状态的任务。
未实际运行但未处于阻止状态或挂起状态的任务处于“就绪”状态。调度程序可以选择处于就绪状态的任务作为进入运行状态的任务。调度程序将始终选择优先级最高的就绪状态任务以输入正在运行的状态。
任务可以在“阻止”状态下等待事件,并在事件发生时自动移动回“就绪”状态。时间事件发生在特定的时间,例如,当阻塞时间超时溢出时,通常用于实现周期性或超时行为。当任务或中断服务例程使用任务通知、队列、事件组或许多信号量类型之一发送信息时,就会发生同步事件。它们通常用于表示异步活动,例如有数据到达外围设备时。

配置调度算法

调度算法是决定将哪个准备状态任务转换到运行状态的软件例程。
到目前为止,所有的例子都使用了相同的调度算法,但是算法可以使用configUSE_PREEMPTION和configUSE_TIME_SLICING配置常数进行更改。这两个常量都在FreeRTOSConfig.h中定义过。
第三个配置常数configUSE_TICKLESS_IDLE也会影响调度算法,因为它的使用会导致tick interrupt在较长时间内被完全关闭。configUSE_TICKLESS_IDLE是一种高级选项,专门用于必须最小化其功耗的应用程序。configUSE_TICKLESS_IDLE在第10章,低功率支持中被描述。本节中提供的描述假设configUSE_TICKLESS_IDLE设置为0,如果常量未定义,则为默认设置。
在所有可能的配置中,FreeRTOS调度程序将确保选择共享优先级的任务依次进入运行状态。这种轮流选择的原则,可以参考 ‘Round Robin Scheduling’(循环调度)。循环调度算法不能保证相同优先级的任务之间的时间被平均共享,只有相同优先级的就绪状态任务将依次进入运行状态。

有时间片的优先级抢占调度

表14所示的配置设置了FreeRTOS调度程序,使用一种名为“带时间切片的固定优先级抢占调度”的调度算法,这是大多数小型RTOS应用程序使用的调度算法,以及本书中迄今为止提供的所有例子所使用的算法。表15提供了对算法名称中使用的术语的描述。

Constant Value
configUSE_PREEMPTION 1
configUSE_TIME_SLICING 1

名词解释:

  • 固定优先级:被描述为“固定优先级”的调度算法不会改变正在调度的任务的优先级,但也不会阻止任务本身改变它们自己的优先级,或其他任务的优先级。
  • 抢占:如果优先级高于“运行状态”任务的任务进入“就绪”状态,优先调度算法将立即“抢占”“运行状态”任务。被抢占意味着非自愿(不显式地屈服或阻塞)退出“正在运行”状态并进入“就绪”状态,以允许不同的任务进入“正在运行”状态。
  • 时间片:时间切片用于在同等优先级的任务之间共享处理时间,即使这些任务没有显式地产生或进入阻塞状态。如果有其他与“正在运行”任务优先级相同的已就绪状态任务,则使用“时间切片”的调度算法将在每个时间切片的末尾进入“正在运行”状态。一个时间片等于两个RTOS tick interrupt之间的时间。

图26和图27演示了当使用带时间片的固定优先级抢占调度时,任务是如何调度的。图26显示了当应用程序中的所有任务都具有唯一的优先级时,选择任务进入“正在运行”状态的顺序。图27显示了当应用程序中的两个任务共享一个优先级时,选择任务进入“正在运行”状态的顺序。

参见图26:

  1. 空闲任务空闲任务以最低优先级运行,因此每次较高优先级任务进入就绪状态时都会被优先执行——例如,在时刻t3、t5和t9。
  2. Task 3
    Task 3是一个事件驱动的任务,它执行的优先级相对较低,但高于空闲优先级。它大部分时间都在阻塞状态等待感兴趣的事件,每次事件发生时都从阻塞状态转换到就绪状态。所有FreeRTOS任务间通信机制(任务通知、队列、信号量、事件组等)可以用于以这种方式通知事件和解除阻止任务。
    事件发生在时间t3和t5之间,也发生在时间t9和t12之间。在时间t3和t5发生的事件立即被处理,因为在这些时间内,任务3是能够运行的最高优先级的任务。发生在时间t9和t12之间的事件直到时间t12才被处理,因为在此之前,更高优先级的任务任务1和Task 2仍在执行。只有在t12时,任务1和任务2都处于阻塞状态,这使任务3成为最高优先级的就绪状态任务。
  3. 任务2
    任务2是一个定期的任务,它执行的优先级高于任务3的优先级,但低于任务1的优先级。任务的周期间隔意味着任务2希望在时间t1、t6和t9执行。
    在时间t6,任务3处于运行状态,但任务2具有较高的相对优先级,因此优先于任务3并立即开始执行。任务2完成其处理,并在时间t7重新进入阻塞状态,此时任务3可以重新进入运行状态以完成其处理。任务3本身在时间t8时阻塞。
  4. 任务1
    任务1也是一个事件驱动的任务。它以所有优先级最高的方式执行,因此可以抢占系统中的任何其他任务。唯一显示的任务1事件发生在时间t10,在这个时间段内,任务1优先于任务2。任务2只有在任务1在时间t11重新进入阻塞状态后才能完成其处理。

参见图27:

  1. 空闲任务和任务2
    空闲任务和任务2都是连续处理任务,它们的优先级都为0(可能的最低优先级)。只有当没有更高的优先级的任务能够运行时,调度程序才会为优先级0任务分配处理时间,并通过时间切片共享分配给优先级0任务的时间。每个新的时间切片开始,在图27中是时间t1、t2、t3、t4、t5、t8、t9、t10和t11。
    空闲任务和任务2依次进入运行状态,这可能导致两个任务在同一时间片中部分处于运行状态,就像在时间t5和时间t8之间发生的情况一样。
  2. 任务1
    任务1的优先级高于空闲优先级。任务1是一个事件驱动的任务,它将大部分时间花在“阻止”状态中等待其感兴趣的事件,每次事件发生时都从“阻止”状态转换到“就绪”状态。感兴趣的事件发生在时间t6,所以在时间t6,任务1成为能够运行的最高优先级任务,因此任务1通过一个时间片优先执行空闲任务。事件的处理在时间t7完成,此时Task 1重新进入阻塞状态。

图27显示了由应用程序作者创建的任务与空闲任务共享处理时间。
如果应用程序作者创建的空闲优先级任务有工作要做,但空闲任务没有,那么将那么多的处理时间分配给空闲任务可能是不可取的。configIDLE_SHOULD_YIELD编译时配置常量可用于更改空闲任务的调度方式:

  • 如果configIDLE_SHOULD_YIELD被设置为0,那么空闲任务将在其整个时间片中始终处于运行状态,除非它被一个更高优先级的任务抢占。
  • 如果configIDLE_SHOULD_YIELD被设置为1,那么如果有其他空闲优先级任务,空闲任务将在其循环的每次迭代中产生(自愿放弃其分配的时间片的剩余部分)。

图27中所示的执行模式是当configIDLE_SHOULD_YIELD被设置为0时将观察到的执行模式。图28所示的执行模式是在configIDLE_SHOULD_YIELD设置为1时相同场景中观察到的。

图28还显示,当configIDLE_SHOULD_YIELD设置为1时,选择在空闲任务后进入运行状态的任务不会执行整个时间片,而是执行空闲任务产生的时间片的任何剩余部分。

优先级抢占调度(无时间切片)

没有时间切片的优先级抢占调度保持了前一节中描述的相同的任务选择和抢占算法,但不使用时间切片在同等优先级的任务之间共享处理时间。配置FreeRTOS调度器的FreeRTOSConfig.h的优先优先调度设置如表16所示。

Constant Value
configUSE_PREEMPTION 1
configUSE_TIME_SLICING 0

如图27所示,如果使用时间切片,有不止一个准备状态的在最高优先级的任务能够运行,然后调度程序将选择一个新任务每个RTOS tick interrupt(滴答中断表示着时间片的结束)进入运行状态在。如果没有使用时间切片,那么调度程序将只会在下列情况下,才选择一个新的任务来进入运行状态:

  • 一个更高优先级的任务进入就绪状态。
  • 处于“运行”状态的任务进入“阻止或挂起”状态。

当不使用时间切片时,任务上下文切换比使用时间切片时更少。因此,关闭时间切片会减少调度器的处理开销。然而,关闭时间切片也会导致相同优先级的任务被分配到到非常不同的处理时间,如图29所示。因此,运行不需要时间切片的调度程序被认为是一种高级技术,只有有经验的用户才能使用。

参考图29,它假定configIDLE_SHOULD_YIELD被设置为0:

  1. tick interrupt
    tick中断发生在时间t1、t2、t3、t4、t5、t8、t11、t12和t13。
  2. 任务1
    任务1是一个高优先级的事件驱动的任务,它将大部分时间花在阻塞状态下等待其感兴趣的事件。在每次事件发生时,任务1将从阻塞状态转换到“就绪”状态(随后,由于它是最高优先级的就绪状态任务,将转换到“正在运行”状态)。图29显示了任务1在t6和t7之间处理事件,然后在t9和t10之间处理事件。
  3. 空闲任务和任务2
    空闲任务和任务2都是连续处理任务,它们的优先级都为0(空闲优先级)。连续处理任务不会进入“已阻止”的状态。
    未使用时间切片,因此处于“运行”状态的空闲优先级任务将继续处于“运行”状态,直到被高优先级任务1抢占。
    在图29中,空闲任务在时间t1开始运行,并保持在运行状态,直到在时间t6被任务1抢占——即在它进入运行状态后超过四个完整的滴答周期。
    任务2在时间t7开始运行,这是任务1重新进入阻塞状态以等待另一个事件的时间。任务2仍然处于运行状态,直到它也在时间t9被任务1抢占——这是在它进入运行状态后不到一个浮动周期。
    在时间t10,空闲任务重新进入运行状态,尽管它已经接收到比任务2多四倍以上的处理时间。
协作调度

这本书关注抢占调度,但FreeRTOS也可以使用合作调度。将FreeRTOS调度程序配置为使用协作调度的FreeRTOSConfig.h设置如表17所示。

Constant Value
configUSE_PREEMPTION 0
configUSE_TIME_SLICING Any Value

当使用协作调度程序时,只有当运行状态任务进入阻塞状态或运行状态任务通过调用taskYIELD()显式地请求重新调度(手动请求时),才会发生上下文切换。任务从不被抢占,所以不能使用时间切片。
图30展示了合作调度器的行为。图30中的水平虚线显示了任务处于“准备就绪”状态的时间。

参见图30:

  1. 任务1
    任务1具有最高的优先级。它以阻塞状态开始,等待一个信号量。在时间t3时,一个中断给出了信号量,导致任务1离开阻塞状态并进入准备状态(在第6章中介绍了提供来自中断的信号量)。
    在时间t3时,任务1是最高优先级的准备状态任务,如果使用了抢占调度器,任务1将成为运行状态任务。但是,由于使用合作调度器,Task 1一直处于就绪状态,直到运行状态任务调用taskYIELD()。
  2. 任务2
    任务2的优先级介于任务1和任务3之间。它以阻塞状态开始,等待任务3在时间t2发送给它的消息。在时间t2,任务2是最高优先级的准备状态任务,如果使用了抢占调度器,任务2将成为运行状态任务。但是,当使用合作调度程序时,任务2仍然处于就绪状态,直到运行状态任务进入调用taskYIELD()的阻塞状态。
    运行状态任务在时间t4时调用taskYIELD(),但是此时任务1是最高优先级的就绪状态任务,所以在任务1在时间t5重新进入阻塞状态之前,任务2实际上不会进入运行状态任务。
    在时间t6,任务2重新进入阻塞状态以等待下一个消息,此时任务3再次成为最高优先级的就绪状态任务。

在处理多任务的应用程序中,应用程序编写器必须注意到多个任务不会同时访问资源,因为同时访问可能会破坏资源。例如,考虑以下场景,其中被访问的资源是一个UART(串行端口)。两个任务是向UART写入字符串;任务1写“afbcdfagaf”,任务2写“123456789”:

  1. 任务1处于“正在运行”状态,并开始写入其字符串。它向UART写入“abcdefg”,但在编写剩余字符之前离开运行状态。
  2. 任务2进入运行状态,并在离开运行状态之前向UART写入“123456789”写入UART。
  3. 任务1重新进入“正在运行”状态,并将其字符串的其余字符写入UART。

在这种情况下,实际上写给UART的是“被劫持者123456789”。Task 1写入的字符串并没有按照预期的完整顺序写入UART,而是已经损坏,因为Task 2写入UART的字符串出现在其中。
通常在使用协同调度器时比在使用优先级抢占调度器时更容易避免同时访问所引起的问题:

  • 当使用优先级抢占调度程序时,运行状态任务可以随时被抢占,包括当它与另一个任务共享的资源处于不一致状态时。正如UART示例所示,让资源处于不一致的状态可能会导致数据损坏。
  • 当使用协作调度程序时,应用程序编写器控制何时可以切换到另一个任务。因此,应用程序写入器可以确保在资源处于不一致状态时不会切换到另一个任务。
  • 在上面的UART示例中,应用程序编写器可以确保在任务1的整个字符串写入UART之前不会离开运行状态,这样做,消除了字符串因激活另一个任务而损坏的可能性。

如图30所示,当使用协同调度程序时,系统的响应性将比当使用优先级抢占调度程序时更低:

  • 当使用优先级抢占调度程序时,调度程序将立即开始运行一个任务,该任务成为最高优先级的就绪状态任务。这在必须在定义的时间段内响应高优先级事件的实时系统中通常是必不可少的。
  • 当使用合作调度程序时,切换到运行状态任务进入阻塞状态或调用任务Y()时,不会执行已成为最高优先级的就绪状态任务。

FreeRTOS 教程指南 学习笔记 第三章 任务管理(二)相关推荐

  1. 《现代密码学》学习笔记——第三章 分组密码 [二] AES

    版本 密钥长度 分组长度 迭代轮数 AES-128 4 4 10 AES-192 6 4 12 AES-256 8 4 14 一.AES的整体结构 二.轮函数 (1)字节代换(SubByte) (2) ...

  2. 《Go语言圣经》学习笔记 第三章 基础数据类型

    <Go语言圣经>学习笔记 第三章 基础数据类型 目录 整型 浮点数 复数 布尔型 字符串 常量 注:学习<Go语言圣经>笔记,PDF点击下载,建议看书. Go语言小白学习笔记, ...

  3. 机器人导论(第四版)学习笔记——第三章

    机器人导论(第四版)学习笔记--第三章 3 操作臂运动学 3.1 引言 3.2 连杆的描述 3.3 连杆连接的描述 3.4 连杆坐标系的定义 3.5 操作臂运动学 3.6 驱动空间.关节空间和笛卡尔空 ...

  4. python编程16章教程_Python学习笔记__16.2章 TCP编程

    # 这是学习廖雪峰老师python教程的学习笔记 Socket是网络编程的一个抽象概念.通常我们用一个Socket表示"打开了一个网络链接",而打开一个Socket需要知道目标计算 ...

  5. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第三章:简单控件

    第 3 章 简单控件 本章介绍了App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图.容纳视图的常用布局.响应点击的按钮控件.显示图片的图像视图等.然后结合本章所学的知识,演示了一个实战项 ...

  6. Win32学习笔记 第三章 HelloWin 选择自 villager 的 Blog

    Win32学习笔记 作者: 姜学哲(netsail0@163.net) 教材: Windows程序设计(第五版)北京大学出版社  [美]Charles Petzold 著  北京博彦科技发展有限公司 ...

  7. ros2 Navigation 学习笔记 第三章(the construct 网站)

    第三章 如何在环境中给机器人定位 序言 本章包括: 机器人定位的意义(即需要知道它对于环境中的位置和方向) 如何在ROS2中用AMCL定位 如何设置机器人的初始位置(手动.自动与使用ROS API) ...

  8. maven 一个简单项目 —— maven权威指南学习笔记(三)

    目标: 对构建生命周期 (build  lifecycle),Maven仓库 (repositories),依赖管理 (dependency management)和项目对象模型 (Project O ...

  9. 信号与系统学习笔记 第三章

    第三章 周期信号的傅里叶级数表示 下面将讨论信号与线性时不变系统的另一种表示,讨论的出发点仍是将信号表示成一组基本信号的线性组合.这是因为,将信号表示成基本信号的线性组合是有利的,如果基本信号具有一下 ...

  10. 仿生学导论学习笔记——第三章

    第三章 仿生学基本要素 3.1 仿生需求 3.1.1 需求驱动 3.1.1.1 生存需求 3.1.1.2 健康需求 3.1.1.3 军事需求 3.1.1.4 发展需求 3.1.1.5 精神需求 3.1 ...

最新文章

  1. CentOS 7.7 安装cmake3
  2. 计算机的网络通信软件的作用,网络协议软件的作用是什么
  3. go 函数参数nil_深入理解 Go-Defer的机制
  4. arm remapping控制输入_解析机器视觉中运动控制卡与PLC的区别
  5. 通过开始关键字和结束关键字,查找所有的邮箱名称记录
  6. ping 计算机名 ipv4,09. 查看本地链路地址(fe80);ping主机名称时IP
  7. 数组名与函数的结合使用注意项
  8. 逻辑回归线性支持向量机
  9. 大数据学习笔记19:MR案例——汇总三科成绩表文件
  10. php mysql 导出到excel,php mysql数据导出到excel文件
  11. 中国移动停售华为5G手机?双方回应...
  12. 嵌入式 c 中结构体经常碰到_I、 __O 、__IO是什么意思?
  13. python接口自动化测试二十六:使用pymysql模块链接数据库
  14. js时间对象相关函数
  15. Tomcat 启动报does not exist or is not a readable directory错误,修改配置文件
  16. 如何在 Excel 中筛选数据透视表中的数据?
  17. [转载] 北京公交集团新LOGO
  18. 终端溯源图构建工具SPADE专题-1 SPADE工具安装
  19. vb实现微信自动投票——开发一个自动化微信投票器【有源码】
  20. php实现爬虫抓取法定节假日放假和补班安排数据

热门文章

  1. 元老职员离职申请书怎么写模板,共计10篇
  2. 什么是云计算机技术,云计算的核心技术是什么
  3. 手机数控模拟器安卓版_CNC模拟器2.5d中文手机版下载
  4. Javascript 调用MSAgent(调用office助手显示动画)
  5. cruise软件模型,cruise增程混动仿真模型,功率跟随控制策略,Cruise混动仿真模型,串联混动汽车动力性经济性仿真
  6. 计算机病毒——代码自解密
  7. Microsoft Office 2016 简体中文Vl批量官方授权版镜像下载
  8. 64位win7连接32位xp的共享打印机HP Laserjet P1008
  9. Linux : ext3_free_blocks: Freeing blocks not in datazone
  10. 偏安一隅的健身房和健身器材市场,还有多少故事可讲?