邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛
分类: 嵌入式
2015-09-17 16:58:59
xTaskCreate() API 函数的参数uxPriority 为创建的任务赋予了一个初始优先级。例子3:
这个侁先级可以在调度器启动后调用vTaskPrioritySet() API 函数进行修改。
应用程序在文件FreeRTOSConfig.h 中设定的编译时配置常量configMAX_PRIORITIES 的值,即是最多可具有的优先级数目。
FreeRTOS 本身并没有限定这个常量的最大值,但这个值越大,则内核花销的内存空间就越多。
所以总是建议将此常量设为能够用到的最小值。
看看FreeRTOSConfig.h
这里面有各种配置选项,如果需要对内核进行裁剪,可以在这里看看需要哪些功能,哪些不需要的就删掉。
对于如何为任务指定优先级,FreeRTOS 并没有强加任何限制。任意数量的任务可以共享同一个优先级—以保证最大设计弹性。当然,如果需要的话,你也可以为每个任务指定唯一的优先级(就如同某些调度算法的要求一样),但这不是强制要求的。
低优先级号表示任务的优先级低,优先级号0 表示最低优先级。有效的优先级号范围从0 到(configMAX_PRIORITES – 1)。
调度器保证总是在所有可运行的任务中选择具有最高优先级的任务,并使其进入运行态。如果被选中的优先级上具有不止一个任务,调度器会让这些任务轮流执行。这种行为方式在之前的例子中可以明显看出来。两个测试任务被创建在同一个优先级上,并且一直是可运行的。所以每个任务都执行一个”时间片”,任务在时间片起始时刻进入运行态,在时间片结束时刻又退出运行态。
要能够选择下一个运行的任务,调度器需要在每个时间片的结束时刻运行自己本身。一个称为心跳( tick)中断的周期性中断用于此目的。时间片的长度通过心跳中断的频率进行设定,心跳中断频率由FreeRTOSConfig.h 中的编译时配置常量configTICK_RATE_HZ 进行配置。比如说,如果configTICK_RATE_HZ 设为100(HZ),则时间片长度为10ms。
心跳计数(tick count)值表示的是从调度器启动开始,心跳中断的总数,并假定心跳计数器不会溢出。用户程序在指定延迟周期时不必考虑心跳计数溢出问题,因为时间连贯性在内核中进行管理。
改变优先级,利用一个空循环来延时周期性打印字符串,则会发生什么现象呢?阻塞状态
打印,task2一直占用CPU。点击(此处)折叠或打开
- #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 delay(uint32_t t)
- {
- uint32_t x, y;
- for(x=t; x>0; x--)
- for(y = 150; y>0; y--);
- }
- void vTaskFunction(void *pvParameters)
- {
- char *taskName;
- taskName = (char *)pvParameters;
- while(1)
- {
- printf(taskName);
- //vTaskDelay(1000/portTICK_RATE_MS);
- delay(20000); // 利用软件延时的方式,则cpu会一直占用,运行优先级较高的任务。
- }
- }
- static const char * task1 = "task 1 is running \r\n";
- static const char * task2 = "task 2 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, 2, NULL );
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
调度器总是选择具有最高优先级的可运行任务来执行。任务2 的优先级比任务1高,并且总是可运行,因此任务2 是唯一一个一直处于运行态的任务。而任务1 不可能进入运行态,所以不可能输出字符串。这种情况我们称为任务1 的执行时间被任务2”饿死(starved)”了。
任务2 之所以总是可运行,是因为其不会等待任何事情——它要么在空循环里打转,要么往终端打印字符串。也就是说任务2 不会主动退出对CPU的占用。
为了使我们的任务切实有用,我们需要通过某种方式来进行事件驱动。一个事件驱动任务只会在事件发生后触发工作(处理),而在事件没有发生时是不能进入运行态的。
调度器总是选择所有能够进入运行态的任务中具有最高优先级的任务。一个高优先级但不能够运行的任务意味着不会被调度器选中,而代之以另一个优先级虽然更低但能够运行的任务。因此,采用事件驱动任务的意义就在于任务可以被创建在许多不同的优先级上,并且最高优先级任务不会把所有的低优先级任务饿死。
如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。阻塞态是非运行态的一个子状态。挂起状态
任务可以进入阻塞态以等待以下两种不同类型的事件:
1. 定时(时间相关)事件——这类事件可以是延迟到期或是绝对时间到点。比如说某个任务可以进入阻塞态以延迟10ms。
2. 同步事件——源于其它任务或中断的事件。比如说,某个任务可以进入阻塞态以等待队列中有数据到来。同步事件囊括了所有板级范围内的事件类型。
FreeRTOS 的队列,二值信号量,计数信号量,互斥信号量(recursive semaphore, 递归信号量,本文一律称为互斥信号量,因为其主要用于实现互斥访问)和互斥量,都可以用来实现同步事件。
任务可以在进入阻塞态以等待同步事件时指定一个等待超时时间,这样可以有效地实现阻塞状态下同时等待两种类型的事件。比如说,某个任务可以等待队列中有数据到来,但最多只等10ms。如果10ms 内有数据到来,或是10ms 过去了还没有数据到来,这两种情况下该任务都将退出阻塞态。
“挂起(suspended)”也是非运行状态的子状态。处于挂起状态的任务对调度器而言是不可见的。让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend() API 函数;
而把一个挂起状态的任务唤醒的唯一途径就是调用vTaskResume() 或vTaskResumeFromISR() API 函数。大多数应用程序中都不会用到挂起状态。
如果任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就绪(ready,准备或就绪)状态。处于就绪态的任务能够被运行,但只是”准备(ready)”运行,而当前尚未运行。
例子4:
使用空循环的方式延迟,导致任务一直在占用CPU,从而使得比它优先级低的任务一直得不到运行而”饿死“。vTaskDelayUntil() API 函数
其实以任何方式的查询都不仅仅只是低效,还有各种其它方面的缺点。在查询过程中,任务实际上并没有做任何有意义的事情,但它依然会耗尽所有处理时间,对处理器周期造成浪费。例4 通过调用vTaskDelay() API 函数来代替空循环,对这种”不良行为”进行纠正。
函数原型:
void vTaskDelay( portTickType xTicksToDelay );
xTicksToDelay:
延迟多少个心跳周期。调用该延迟函数的任务将进入阻塞态,经延迟指定的心跳周期数后,再转移到就绪态。
举个例子,当某个任务调用vTaskDelay( 100 )时,心跳计数值为10,000,则该任务将保持在阻塞态,直到心跳计数计到10,100。
常数portTICK_RATE_MS 可以用来将以毫秒为单位的时间值转换为以心跳周期为单位的时间值。
vTaskDelay( 250 / portTICK_RATE_MS );
/* 延迟一个循环周期。调用vTaskDelay()以让任务在延迟期间保持在阻塞态。延迟时间以心跳周期为单位,常量portTICK_RATE_MS可以用来在毫秒和心跳周期之间相换转换。本例设定250毫秒的循环周期。 */
尽管两个任务实例还是创建在不同的优先级上,但现在两个任务都可以得到执行。
空闲任务是在调度器启动时自动创建的,以保证至少有一个任务可运行(至少有一个任务处于就绪态)。点击(此处)折叠或打开
- #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 vTaskFunction(void *pvParameters)
- {
- char *taskName;
- taskName = (char *)pvParameters;
- while(1)
- {
- printf(taskName);
- vTaskDelay(1000/portTICK_RATE_MS);
- }
- }
- static const char * task1 = "task 1 is running \r\n";
- static const char * task2 = "task 2 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, 2, NULL );
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
仔细想想它是怎么运行的??
从上图中的执行流程中可以看到,任务在整个延迟周期内都处于阻塞态,只在完成实际工作的时候才占用处理器时间(本例中任务的实际工作只是简单地打印输出一条信息)。
在上图所示的情形中,任务离开阻塞态后,仅仅执行了一个心跳周期的一个片段,然后又再次进入阻塞态。所以大多数时间都没有一个应用任务可运行(即没有应用任务处于就绪态),因此没有应用任务可以被选择进入运行态。这种情况下,空闲任务得以执行。空间任务可以获得的执行时间量,是系统处理能力裕量的一个度量指标。
vTaskDelayUntil()类似于vTaskDelay()。函数vTaskDelay()的参数用来指定任务在调用vTaskDelay()到切出阻塞态整个过程包含多少个心跳周期。例子5:
任务保持在阻塞态的时间量由vTaskDelay()的入口参数指定,但任务离开阻塞态的时刻实际上是相对于vTaskDelay()被调用那一刻的。
vTaskDelayUntil()的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。API 函数vTaskDelayUntil()可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时候)。由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对时间更精确(即比调用vTaskDelay()可以实现更精确的周期性)。
函数原型:
void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );
pxPreviousWakeTime
此参数命名时假定vTaskDelayUntil()用于实现某个任务以固定频率周期性执行。这种情况下pxPreviousWakeTime保存了任务 上一次离开阻塞态(被唤醒)的时刻。这个时刻被用作一个参考点来计算该任务下一次离开阻塞态的时刻。pxPreviousWakeTime 指向的变量值会在API 函数vTaskDelayUntil()调用过程中自动更新,应用程序除了该变量第一次初始化外,通常都不要修改它的值。程序清单14 展示了这个参数的使用方法。xTimeIncrement
此参数命名时同样是假定vTaskDelayUntil()用于实现某个任务以固定频率周期性执行——这个频率就是由xTimeIncrement 指定的。xTimeIncrement 的单位是心跳周期, 可以使用常量portTICK_RATE_MS 将毫秒转换为心跳周期。
例4 中的两个任务是周期性任务,但是使用vTaskDelay()无法保证它们具有固定的执行频率,因为这两个任务退出阻塞态的时刻相对于调用vTaskDelay()的时刻。通过调用vTaskDelayUntil()代替vTaskDelay(),把这两个任务进行转换,以解决这个潜在的问题。
例子6:点击(此处)折叠或打开
- #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 vTaskFunction(void *pvParameters)
- {
- char *taskName;
- portTickType xLastWakeTime;
- taskName = (char *)pvParameters;
- /* 变量xLastWakeTime需要被初始化为当前心跳计数值。说明一下,这是该变量唯一一次被显式赋值。之后,
xLastWakeTime将在函数vTaskDelayUntil()中自动更新。 */
- xLastWakeTime = xTaskGetTickCount();
- while(1)
- {
- printf(taskName);
- //vTaskDelay(1000/portTICK_RATE_MS);
- /* 本任务将精确的以250毫秒为周期执行。同vTaskDelay()函数一样,时间值是以心跳周期为单位的,
可以使用常量portTICK_RATE_MS将毫秒转换为心跳周期。变量xLastWakeTime会在
vTaskDelayUntil()中自动更新,因此不需要应用程序进行显示更新。 */- vTaskDelayUntil( &xLastWakeTime, ( 1000 / portTICK_RATE_MS ) );
- }
- }
- static const char * task1 = "task 1 is running \r\n";
- static const char * task2 = "task 2 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, 2, NULL);
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
之前的范例分别测试了任务以查询方式和阻塞方式工作的系统行为。本例通过合并这两种方案的执行流程,再次实现具有既定预期的系统行为。
在优先级1 上创建两个任务。这两个任务只是不停地打印输出字符串,其他什么事情也不做。这两个任务没有调用任何可能导致它们进入阻塞态的API 函数,所以这两个任务要么处于就绪态,要么处于运行态。具有这种性质的任务被称为”不停处理(或持续处理,continuous processing)”任务,因为它们总是有事情要做,虽然在本例中的它们做的事情没什么意义。
第三个任务创建在优先级2 上,高于另外两个任务的优先级。这个任务虽然也是打印输出字符串,但它是周期性的,所以调用了vTaskDelayUntil(),在每两次打
印之间让自己处于阻塞态。
为了能在打印的字符串看到更好的效果,可以将tick时间延长,
我设置了: 100ms产生一次systemTick中断,所以在FreeRTOSConfig.h文件中修改
#define configTICK_RATE_HZ ( ( TickType_t ) 10 )
打印结果:点击(此处)折叠或打开
- #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 delay(uint32_t t)
- {
- uint32_t x, y;
- for(x=t; x>0; x--)
- for(y = 150; y>0; y--);
- }
- void vContinousProcessingTask(void * pvParameters)
- {
- char *TaskName;
- TaskName = (char *)pvParameters;
- while(1)
- {
- printf(TaskName);
- delay(2000);
- }
- }
- void vTaskPeriodic(void *pvParameters)
- {
- char *taskName;
- portTickType xLastWakeTime;
- taskName = (char *)pvParameters;
- xLastWakeTime = xTaskGetTickCount();
- while(1)
- {
- printf(taskName);
- vTaskDelayUntil( &xLastWakeTime, ( 200 / portTICK_RATE_MS ) );
- }
- }
- static const char * ContinousTask1 = "Continous task1\r\n";
- static const char * ContinousTask2 = "Continous task2\r\n";
- static const char * PeriodicTask = "Periodic task \r\n";
- int main(void)
- {
- // board initialize.
- LED_Init();
- uart_init(115200);
- //create two tasks, with the same priority.
- xTaskCreate( vTaskPeriodic, "PeriodicTask", configMINIMAL_STACK_SIZE, (void *)PeriodicTask, 2, NULL);
- xTaskCreate( vContinousProcessingTask, "ContinousTask1", configMINIMAL_STACK_SIZE, (void *)ContinousTask1, 1, NULL);
- xTaskCreate( vContinousProcessingTask, "ContinousTask2", configMINIMAL_STACK_SIZE, (void *)ContinousTask2, 1, NULL);
- // start scheduler now
- vTaskStartScheduler();
- return 0;
- }
执行流程图:
在这里,空闲任务是得不到执行的,因为有查询任务一直在执行。
而具有相同优先级的任务轮流执行时间片,直到高优先级的任务从阻塞态唤醒。