邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛
分类: 嵌入式
2015-09-18 11:17:38
例4 中创建的任务大部份时间都处于阻塞态。这种状态下所有的任务都不可运行,所以也不能被调度器选中。空闲任务钩子函数
但处理器总是需要代码来执行—所以至少要有一个任务处于运行态。为了保证这一点,当调用vTaskStartScheduler()时,调度器会自动创建一个空闲任务。空闲任务是一个非常短小的循环—和最早的示例任务十分相似,总是可以运行。
空闲任务拥有最低优先级(优先级0)以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级。
运行在最低优先级可以保证一旦有更高优先级的任务进入就绪态,空闲任务就会立即切出运行态。当高优先级的任务退出阻塞态时,空闲任务立即切换出来以让高优先级任务执行。高优先级额任务被看作是抢占(pre-empted)了空闲任务。抢占是自动发生的,也并不需要通知被抢占任务。
通过空闲任务钩子函数(或称回调,hook, or call-back),可以直接在空闲任务中添加应用程序相关的功能。空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。空闲任务钩子函数的实现限制
通常空闲任务钩子函数被用于:
1. 执行低优先级,后台或需要不停处理的功能代码。
2. 测试处系统处理裕量(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。
3. 将处理器配置到低功耗模式——提供一种自动省电方法,使得在没有任何应用功能需要处理的时候,系统自动进入省电模式。
空闲任务钩子函数必须遵从以下规则例子6:
1. 绝不能阻或挂起。空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!
2. 如果应用程序用到了vTaskDelete() AP 函数,则空闲钩子函数必须能够尽快返回。因为在任务被删除后,空闲任务负责回收内核资源。如果空闲任务一直运行在钩子函数中,则无法进行回收工作。
空闲任务钩子函数原型:
void vApplicationIdleHook( void );
例4 调用了带阻塞性质的vTaskDelay() API 函数,会产生大量的空闲时间——在这期间空闲任务会得到执行,因为两个应用任务均处于阻塞态。本例通过空闲钩子函数来使用这些空间时间。
改变任务优先级
/* Declare a variable that will be incremented by the hook function. */
unsigned long ulIdleCycleCount = 0UL;
/* 空闲钩子函数必须命名为vApplicationIdleHook(),无参数也无返回值。 */
void vApplicationIdleHook( void )
{
/* This hook function does nothing but increment a counter. */}
ulIdleCycleCount++;
FreeRTOSConfig.h 中的配置常量configUSE_IDLE_HOOK 必须定义为1,这样空闲任务钩子函数才会被调用。
main.c函数:
打印结果:点击(此处)折叠或打开
- #include "led.h"
- #include "delay.h"
- #include "sys.h"
- #include "usart.h"
- // FreeRTOS head file, add here.
- #include "FreeRTOS.h"
- #include "task.h"
- #include "queue.h"
- #include "list.h"
- #include "portable.h"
- #include "FreeRTOSConfig.h"
- extern unsigned long ulIdleCycleCount;
- void vTaskFunction(void *pvParameters)
- {
- char *taskName;
- taskName = (char *)pvParameters;
- while(1)
- {
- printf(taskName);
- printf("%d \r\n", ulIdleCycleCount);
- vTaskDelay( 500 / portTICK_RATE_MS );
- }
- }
- static const char * Task1 = "task1 is running \r\n";
- static const char * Task2 = "task2 is running \r\n";
- int main(void)
- {
- // board initialize.
- LED_Init();
- uart_init(115200);
- //create two tasks, with the same priority.
- xTaskCreate( vTaskFunction, "Task1", configMINIMAL_STACK_SIZE, (void *)Task1, 1, NULL);
- xTaskCreate( vTaskFunction, "Task2", configMINIMAL_STACK_SIZE, (void *)Task2, 1, NULL);
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
想一想:为什么每次循环调用的次数是450000 左右呢?接近500,000 呢??
在这里,设置 #define configTICK_RATE_HZ ( ( TickType_t )100)
vTaskPrioritySet() API 函数:
API 函数vTaskPriofitySet()可以用于在调度器启动后改变任何任务的优先级。
例子8:
函数原型:
void vTaskPrioritySet( xTaskHandle pxTask, unsigned portBASE_TYPE uxNewPriority );pxTask
被修改优先级的任务句柄(即目标任务)——参考xTaskCreate() API函数的参数pxCreatedTask 以了解如何得到任务句柄方面的信息。任务可以通过传入NULL 值来修改自己的优先级。uxNewPriority
目标任务将被设置到哪个优先级上。如果设置的值超过了最大可用优先级(configMAX_PRIORITIES – 1),则会被自动封顶为最大值。常量configMAX_PRIORITIES 是在FreeRTOSConfig.h 头文件中设置的一个编译时选项。
uxTaskPriorityGet() API 函数
uxTaskPriorityGet() API 函数用于查询一个任务的优先级。
函数原型:
unsigned portBASE_TYPE uxTaskPriorityGet( xTaskHandle pxTask );pxTask
被查询任务的句柄(目标任务) ——参考xTaskCreate() API 函数的参数pxCreatedTask 以了解如何得到任务句柄方面的信息。任务可以通过传入NULL 值来查询自己的优先级。返回值
被查询任务的当前优先级。
调度器总是在所有就绪态任务中选择具有最高优先级的任务,并使其进入运行态。本例即是通过调用vTaskPrioritySet() API 函数来改变两个任务的相对优先级,以达到对调度器这一行为的演示。
在不同的优先级上创建两个任务。这两个任务都没有调用任何会令其进入阻塞态的API 函数,所以这两个任务要么处于就绪态,要么处于运行态——这种情形下,调度器选择具有最高优先级的任务来执行。
例8 具有以下行为方式:删除任务
- 任务1创建在最高优先级,以保证其可以最先运行。任务1 首先打印输出两个字符串,然后将任务2的优先级提升到自己之上。
- 任务2 一旦拥有最高优先级便启动执行(进入运行态)。由于任何时候只可能有一个任务处于运行态,所以当任务2 运行时,任务1 处于就绪态。
- 任务2 打印输出一个信息,然后把自己的优先级设回低于任务1 的初始值。
- 任务2 降低自己的优先级意味着任务1 又成为具有最高优先级的任务,所以任务1 重新进入运行态,任务2 被强制切入就绪态。
打印结果:点击(此处)折叠或打开
- #include "led.h"
- #include "delay.h"
- #include "sys.h"
- #include "usart.h"
- // FreeRTOS head file, add here.
- #include "FreeRTOS.h"
- #include "task.h"
- #include "queue.h"
- #include "list.h"
- #include "portable.h"
- #include "FreeRTOSConfig.h"
- xTaskHandle xTask2Handle;
- static const char * Task1 = "task1 is running \r\n";
- static const char * Task2 = "task2 is running \r\n";
- void task1(void * pvParameters)
- {
- unsigned portBASE_TYPE priority;
- char *TaskName = (char *)pvParameters;
- priority = uxTaskPriorityGet(xTask2Handle);
- while(1)
- {
- printf(TaskName);
- printf("about to raise the task2 priority \r\n");
- vTaskPrioritySet(xTask2Handle, (priority + 1));
- }
- }
- void task2(void *pvParameters)
- {
- unsigned portBASE_TYPE priority;
- char *taskName = (char *)pvParameters;
- priority = uxTaskPriorityGet(NULL); // ????×??í????????
- while(1)
- {
- printf(taskName);
- printf("about to lower the task2 priority \r\n");
- vTaskPrioritySet(NULL, (priority - 2));
- }
- }
- int main(void)
- {
- // board initialize.
- LED_Init();
- uart_init(115200);
- //create two tasks, with the same priority.
- xTaskCreate( task1, "Task1", configMINIMAL_STACK_SIZE, (void *)Task1, 2, NULL);
- xTaskCreate( task2, "Task2", configMINIMAL_STACK_SIZE, (void *)Task2, 1, &xTask2Handle);
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
执行流程:
任务可以使用API 函数vTaskDelete()删除自己或其它任务。例子9:
任务被删除后就不复存在,也不会再进入运行态。
空闲任务的责任是要将分配给已删除任务的内存释放掉。因此有一点很重要,那就是使用vTaskDelete() API 函数的任务千万不能把空闲任务的执行时间饿死。
需要说明一点,只有内核为任务分配的内存空间才会在任务被删除后自动回收。任务自己占用的内存或资源需要由应用程序自己显式地释放。
函数原型:
void vTaskDelete( xTaskHandle pxTaskToDelete );
pxTaskToDelete
被删除任务的句柄(目标任务) —— 参考xTaskCreate() API 函数的参数pxCreatedTask 以了解如何得到任务句柄方面的信息。任务可以通过传入NULL 值来删除自己。
(1)任务1:在main()里创建任务1,优先级为1。任务1 运行时,以优先级2 创建任务2。现在任务2 具有最高优先级,所以会立即得到执行。调度算法:
(2)任务2 什么也没有做,只是删除自己。可以通过传递NULL 值以vTaskDelete()来删除自己,但是为了纯粹的演示,传递的是任务自己的句柄。
(3)当任务2 被自己删除之后,任务1 成为最高优先级的任务,所以继续执行,调用vTaskDelay()阻塞一小段时间。
(4)当任务1 进入阻塞状态后,空闲任务得到执行的机会。空闲任务会释放内核为已删除的任务2 分配的内存。
(5)任务1 离开阻塞态后,再一次成为就绪态中具有最高优先级的任务,因此会抢占空闲任务。又再一次创建任务2,如此往复。
程序源码
打印现象:点击(此处)折叠或打开
- #include "led.h"
- #include "delay.h"
- #include "sys.h"
- #include "usart.h"
- // FreeRTOS head file, add here.
- #include "FreeRTOS.h"
- #include "task.h"
- #include "queue.h"
- #include "list.h"
- #include "portable.h"
- #include "FreeRTOSConfig.h"
- void task2(void *pvParameters);
- xTaskHandle xTask2Handle;
- static const char * Task1 = "task1 is running \r\n";
- static const char * Task2 = "task2 is running \r\n";
- void task1(void * pvParameters)
- {
- char *TaskName = (char *)pvParameters;
- while(1)
- {
- printf(TaskName);
- xTaskCreate( task2, "Task2", configMINIMAL_STACK_SIZE, (void *)Task2, 2, &xTask2Handle);
- vTaskDelay(1000/portTICK_RATE_MS);
- }
- }
- void task2(void *pvParameters)
- {
- char *taskName = (char *)pvParameters;
- printf(taskName);
- printf("task 2 is running and about to delete itself \r\n");
- vTaskDelete(xTask2Handle); // 删除自己
- // == vTaskDelete(NULL);
- }
- int main(void)
- {
- // board initialize.
- LED_Init();
- uart_init(115200);
- //create task here.
- xTaskCreate( task1, "Task1", configMINIMAL_STACK_SIZE, (void *)Task1, 1, NULL);
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
执行流程:
(1)优先级抢占式调度
本章的示例程序已经演示了FreeRTOS 在什么时候以及以什么方式选择一个什么样的任务来执行。
1. 每个任务都赋予了一个优先级。
2. 每个任务都可以存在于一个或多个状态。
3. 在任何时候都只有一个任务可以处于运行状态。
4. 调度器总是在所有处于就绪态的任务中选择具有最高优先级的任务来执行。
这种类型的调度方案被称为”固定优先级抢占式调度”。所谓”固定优先级”是指每个任务都被赋予了一个优先级,这个优先级不能被内核本身改变(只能被任务修改)。”抢占式”是指当任务进入就绪态或是优先级被改变时,如果处于运行态的任务优先级更低,则该任务总是抢占当前运行的任务。
任务可以在阻塞状态等待一个事件,当事件发生时其将自动回到就绪态。时间事件发生在某个特定的时刻,比如阻塞超时。时间事件通常用于周期性或超时行为。任务或中断服务例程往队列发送消息或发送任务一种信号量,都将触发同步事件。同步事件通常用于触发同步行为,比如某个外围的数据到达了。
(2)选择任务优先级
作为一种通用规则,完成硬实时功能的任务优先级会高于完成软件时功能任务的优先级。但其它一些因素,比如执行时间和处理器利用率,都必须纳入考虑范围,以保证应用程序不会超过硬实时的需求限制。
单调速率调度(Rate Monotonic Scheduling, RMS)是一种常用的优先级分配技术。其根据任务周期性执行的速率来分配一个唯一的优先级。具有最高周期执行频率的任务赋予高最优先级;具有最低周期执行频率的任务赋予最低优先级。这种优先级分配方式被证明了可以最大化整个应用程序的可调度性(schedulability),但是运行时间不定以及并非所有任务都具有周期性,会使得对这种方式的全面计算变得相当复杂。(3)协作式调度
FreeRTOS 可以选择采用协作式调度。
采用一个纯粹的协作式调度器,只可能在运行态任务进入阻塞态或是运行态任务显式调用taskYIELD()时,才会进行上下文切换。任务永远不会被抢占,而具有相同优先级的任务也不会自动共享处理器时间。协作式调度的这作工作方式虽然比较简单,但可能会导致系统响应不够快。
实现混合调度方案也是可行的,这需要在中断服务例程中显式地进行上下文切换,从而允许同步事件产生抢占行为,但时间事件却不行。这样做的结果是得到了一个没有时间片机制的抢占式系统。或许这正是所期望的,因为获得了效率,并且这也是一种常用的调度器配置。