在学习FreeRTOS任务调度的时候,一般分为抢占式调度和时间片调度,抢占式调度用于不同优先级的任务,而时间片调度用于相同优先级的任务。时间片调度就是几个同样优先级的任务轮流执行,每个任务执行一个时间片; 如果一个优先级就序列表中有ABC三个任务,那么首先执行A任务,下次再进来就执行B任务,然后是C任务,再是A任务。实现方式是改变该优先级就序列表的pxindex指针,开始指向A任务这个列表项就返回A任务句柄,然后指针指向B任务列表项,以此类推。
但是学到后面我发现,任务切换的本质就是PendSV中断服务函数,而时间片调度之所以可以完成也只是每个时间片过后进入Systick中断时调用了PendSV中断服务函数而已,这样就保证了相同优先级每个任务执行一个时间片。那我就有个疑问,假设任务A运行了半个时间片,这个时候使用任务切换,那任务A会不会被切换到任务B去呢?这个时候任务A可还没运行一个时间片,其实这个问题就相当于相同优先级的任务能不能相互打断?。我们知道,所谓高优先级任务就绪后打断低优先级任务,并不是高优先级处于就绪态了就会有个中断完成打断,而是高优先级处于就绪态后执行了一次任务切换,高优先级才能打断低优先级(这个任务切换可以在systick中断中进行,其次freertos基本上涉及到任务阻塞态和就绪态之间转换的api函数中都会带一个任务切换函数portYIELD(),以此保证当高优先级任务为就绪态时能够及时执行),那么按照理论来说,根据时间片调度的实现原理,ABC三个相同优先级任务,只要调用了任务切换函数,不管任务ABC哪一个有没有把当前时间片执行完,都会切换到另一个任务执行,所以时间片调度的本质应该只是在systick中断中调用了一次任务切换。你可以人为的调用任务切换函数来打破时间片调度的规则。
因此我们可以从理论上得到一个结论:FreeRTOS相同优先级任务是可以相互打断的,只要执行了任务切换函数。
结论有了,剩下的就是用实验去验证了,我的实验方法是:设置FreeRTOS时间片为100ms;创建两个相同优先级的任务,任务一和任务二,他们一直处于就绪态;做两组对照试验,一组是任务一和任务二正常执行,按理来说应该每个任务执行100ms再切换到另一个任务。另一组是在任务一和任务二执行几十ms的时候调用一次任务切换函数,看看有没有发生任务切换。如果发生任务切换就说明相同优先级是能够相互打断的,且这个打断不用一定要遵守时间片规则,只要另一个同优先级任务就绪且调用任务切换函数即可。
实验环境:stm32Cubemx的FreeRTOS系统,版本为CMSIS_V2,时间片为100ms
IDE:keil5
开发板:正点原子miniV3.0
实验的代码如下:
第一组实验:
freertos.c:
void StartTask01(void *argument)
{
/* USER CODE BEGIN StartTask01 */
/* Infinite loop */
for(;;)
{
printf("任务一刻度:0\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务一刻度:1\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务一刻度:2\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务一刻度:3\r\n");
Delay_Ms(20);
//portYIELD();
LED_RED_Toggle();
}
/* USER CODE END StartTask01 */
}
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
printf("任务二刻度:0\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务二刻度:1\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务二刻度:2\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务二刻度:3\r\n");
Delay_Ms(20);
//portYIELD();
LED_GREEN_Toggle();
}
/* USER CODE END StartTask02 */
}
按理来说输出结果应该为:每个任务执行100ms。下图结果符合理论
第二组实验:在第一组实验的代码基础上,取消所有的portYIELD()函数的注释,每隔20ms进行一次任务切换。结果应该是任务一打印一次,任务二打印一次,任务一打印一次以此类推。下图结果符合结论。
接下来我们再添加一组实验,第三组实验,我们在第一组实验代码基础上,只取消第一个portYIELD()函数的注释,如下:
freertos.c:
void StartTask01(void *argument)
{
/* USER CODE BEGIN StartTask01 */
/* Infinite loop */
for(;;)
{
printf("任务一刻度:0\r\n");
Delay_Ms(20);
portYIELD();
printf("任务一刻度:1\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务一刻度:2\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务一刻度:3\r\n");
Delay_Ms(20);
//portYIELD();
LED_RED_Toggle();
}
/* USER CODE END StartTask01 */
}
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
printf("任务二刻度:0\r\n");
Delay_Ms(20);
portYIELD();
printf("任务二刻度:1\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务二刻度:2\r\n");
Delay_Ms(20);
//portYIELD();
printf("任务二刻度:3\r\n");
Delay_Ms(20);
//portYIELD();
LED_GREEN_Toggle();
}
/* USER CODE END StartTask02 */
}
这次实验的结果应该是,任务一打印一次,任务二打印一次,任务一打印四次,任务二打印四次;一个循环以此类推,让我们看看结果:
欸嘿,怎么不符合预期呢?按理来说任务二打印四次再切任务一啊,而且任务一只能打印四次然后调用任务切换,再让任务二来打印啊?大家不要忘了,systick中断也会调用任务切换函数,每100ms进行一次任务切换。我们看执行到红框的时候刚刚差不多执行了100ms,这个时候systick中断调用一次任务切换,因此任务二打印三次就切任务一了。而任务一打印四次之后,又差不多过了100ms,这时我们又人为调用了一次任务切换,相当于短时间内切了两次,任务一切任务二,任务二又切任务一,就导致任务一一直在打印。
所以,理论结合实验,我们得出来再FreeRTOS中,相同优先级是可以相互打断的,就像高优先级可以打断低优先级一样。不过相同优先级遵循依次打断,还是ABC三个任务,都处于就绪态,A运行时调用任务切换函数,B来运行,B运行时调用任务切换函数C来运行这样子。不一定非要一个时间片完了才能切换另一个同优先级任务,毕竟本质都是调用任务切换函数罢了。
/************************ 下面说一下我做实验遇到的一个问题 **********************/
FreeRTOS时间片最长为多久?我看cubemx中可以配置这个频率为1-1000HZ,当1000HZ时时间片为1ms,10HZ时间片为100ms,1HZ时间片应该为1s。但是实测1HZ时的时间片反而比10HZ时的时间片要更短一些,这让我很疑惑?实验如下:
把TICK_RATE_HZ改为1,这时时间片应该为1s,同样调用实验一的代码,应该任务一打印几十次,完了任务二也打印几十次,以此类推,但实际结果如下:每个任务只打印了几次
把TICK_RATE_HZ改为2,这时时间片应该为500ms,同样调用实验一的代码,应该任务一打印20次左右,完了任务二也打印20次左右,以此类推,实际结果如下:每个任务同样只打印
把TICK_RATE_HZ改为5,这时时间片应该为200ms,同样调用实验一的代码,应该任务一打印8次左右,完了任务二也打印8次左右,以此类推,实际结果如下:和理论相符合
这让我百思不得其解,难道Freertos说是最长设置为1s时间片,实际时间片最多只能到200ms?为什么呢?还有TICK_RATE_HZ改为1和2时的打印情况是为什么呢?希望又大佬看见了能为小弟解答一二。