1. 空闲任务和钩子函数
1.1 空闲任务的作用
释放被删除的任务的内存。
1.2 空闲任务的创建
通常情况下,一个良好的任务都是事件驱动的,平时大部分时间处于阻塞状态,当我们所创建的任务都无法运行时,调度器就必须要找到一个可以运行任务来执行,因此程序需要提供一个空闲任务,不用特意创建该任务,空闲任务的创建在vTaskStartScheduler()中创建,启动调度器时,函数内部会创建空闲任务。
空闲任务的优先级为0,这以为着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让 这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。
要注意的是:如果使用vTaskDelete()来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。
/* 启动调度器 */
vTaskStartScheduler();
1.3 空闲任务的举例
在main函数中创建优先级为1的任务1,在任务1中再创建优先级为2的任务2,使其创建后立即抢占开始执行,任务2中调动vTaskDelete函数使任务2自杀,继续执行任务1,任务1继续创建新任务2。这种情景创建一种使得任务1不断创建任务2,并不删除任务2所使用的内存,导致不断的消耗内存,使得内存的堆创建任务失败,程序执行几次之后就因为内存不足报错。
1.4 空闲任务清理自杀任务TCB和栈
1.5 vTaskDelay函数的内存清理
原因在于:自杀的任务,无法清理自己所使用的内存,比如栈、释放栈的函数要哟经到栈,这是矛盾的。
1.5.1 函数原型
当创建任务时,需要分配栈和TCB结构体;
当删除任务时,需要释放栈和TCB结构体;
1.6 钩子函数的概念
我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环没执行一次,就会 调用一次钩子函数。
1.7 钩子函数的作用
1.8 钩子函数的限制
1.9 钩子函数的使用
在FreeRTOS\Source\tasks.c中,可以看到如下代码,所以前提就是:
2.任务调度算法
2.1 任务四种状态
正在运行的任务,被称为"正在使用处理器",它处于运行状态。在单处理系统中,任何时间里只能有一 个任务处于运行状态。
非运行状态的任务,它处于这3中状态之一:阻塞(Blocked)、暂停(Suspended)、就绪(Ready)。就绪态的任务,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态任务并让它进入运行状态。
阻塞状态的任务,它在等待"事件",当事件发生时任务就会进入就绪状态。
时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪 状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。
同步事件就是:某个任务在等待某些信息,别的任务或者中断服务程序会给它发送信息。怎么"发送信息"?方法很多,有:任务通 知(task notification)、队列(queue)、事件组(event group)、信号量(semaphoe)、互斥量(mutex)等。 这些方法用来发送同步信息,比如表示某个外设得到了数据。
刚开始所有的任务都在Read状态,然后调度器挑选任务进入Running状态
2.2 调度算法的配置
对于调度算法的配置要从这三个角度开始理解
可否抢占?高优先级的任务能否优先执行(配置项: configUSE_PREEMPTION)
可抢占的前提下,同优先级的任务是否轮流执行(配置项:configUSE_TIME_SLICING)
在"可抢占"+"时间片轮转"的前提下,进一步细化:空闲任务是否让步于用户任务(配置项: configIDLE_SHOULD_YIELD)
2.3 问题:为什么打印时,空闲任务的波形有大有小?
为什么IDLE任务的波形有大有小?
task2运行了1个tick,轮到idle任务。
idletask->hook->flagIdleTaskrun=1, 礼让,轮到task1运行,
task1从printf中间继续运行,打印完下一个字符后,才设置flagIdleTaskrun=0,
可以看到,flagIdleTaskrun等于1的时间:在idle任务里,也在task1里,
所以这个变量用来表示任务的运行时间:并不准确。
改成这样就没问题:
现在可以看到了,空闲任务运行的时间非常非常短: