【吐血总结】FreeRTOS难点、Systick中断-滴答定时器、PendSV中断-任务切换、SVC中断-系统底层、时间片调度-时钟节拍【已完结】
(第1-8讲)STM32F4单片机,FreeRTOS基础知识总结【视频笔记、代码讲解】【正点原子】【原创】
(第9-10讲)STM32F4单片机,FreeRTOS任务创建和删除(动态方法)【视频笔记、代码讲解】【正点原子】【原创】
(第12讲)STM32F4单片机,FreeRTOS任务创建和删除(静态方法)【视频笔记、代码讲解】【正点原子】【原创】
(第13-14讲)STM32F4单片机,FreeRTOS任务挂起和恢复【视频笔记、代码讲解】【正点原子】【原创】
(第16-17讲)STM32F4单片机,FreeRTOS中断管理简介【视频笔记、代码讲解】【正点原子】【原创】
(第18-19讲)32单片机,FreeRTOS临界段代码保护、任务调度器的挂起和恢复【视频笔记、代码讲解】【原创】
(第20-22讲)STM32F4单片机,FreeRTOS列表和列表项API函数讲解【视频笔记、代码讲解、正点原子】【原创】
(第40-44讲)STM32F4单片机,FreeRTOS信号量【二值、计数、翻转、互斥】【代码讲解】【正点原子】【原创】
Systick异常请求=滴答定时器中断,原子的这颗F429芯片晶振是180MHZ,重装载值是18000,从18000计数减到0是一次心跳周期=1/1000s=1ms,这个systick定时器开启是在开启任务调度函数里面的
这个空任务就是空闲任务,任务优先级是0,即任务最低优先级(不是中断优先级)
其实我仍然不太明白嵌套计数器的含义,前几篇文章也分析过,总觉得理解不对
这个时间片计数轮询表述不恰当,这里其实是开启滴答定时器中断,用于PendSV任务切换使用
至于Systick和PendSV两者之间谁的优先级高?这个我也不知道,待补充
开启定时器中断就是滴答定时器,里面有PendSV切换, 第6步里面全是汇编语言,反正我看不懂,待补充
已经创建好任务了,才进入的SVC中断,SVC中断是讲任务结构体数据依次按地址pop到cpu寄存器中(SVC中断只在启动第一次任务时会调用一次,以后均不调用),这里的使能全局中断相当于汇编语言开启中断,因为前面有进入临界区保护时关掉了中断,此时手动又开启了(具体为什么,我没有详细研究,会用大致上理解就行了)
绿色颜色很重要,这块原子讲得很详细,建议去看下视频
上图中的if( xTaskIncrementTick() != pdFALSE )判断中的xTaskIncrementTick() 函数里面就是一些链表的操作,即列表项之间的判断,插入,删除,阻塞之类的,因为列表项之间判断决定了是否进行任务切换,因为创建任务就是创建了列表
原子中的delay延时函数内部调用了上面橙色圈的函数,也就是说delay可以导致任务切换
这个是宏替换之后的PendSV中断函数,全是汇编语言,后面不想看了,太细了也很难
定时器中断是在开启任务调度器时开启的Systick中断函数(寄存器开启,1ms进入一次),里面有PendSV中断函数(寄存器开启,用来根据列表项操作进行任务函数切换),开启任务调度器之后创建了任务(创建任务的同时也就保存了数据),之后打开了SVC中断(汇编语言,获取任务堆栈地址,将任务数据进行出栈处理给cpu寄存器,用于操作硬件底层)(SVC中断只在启动第一次任务时会调用一次,以后均不调用)
没用完的时间片丢掉
时间片设置的是50ms,这个实验还是比较容易理解滴!因为没有加临界区,导致task1打印一半被阻塞跳到task2接着打印了
taskENTER_CRITICAL(); /* 进入临界区 */
printf("task1运行次数:%d\r\n",++task1_num);
taskEXIT_CRITICAL(); /* 退出临界区 */
taskENTER_CRITICAL(); /* 进入临界区 */
printf("task2运行次数:%d\r\n",++task2_num);
taskEXIT_CRITICAL(); /* 退出临界区 */
解决办法是printf上下加上临界区保护即可
void vTask1( void *pvParameters )
{
const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );
BaseType_t ret;
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务的信息 */
printf("Task1 is running\r\n");
ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
if (ret != pdPASS)
printf("Create Task2 Failed\r\n");
// 如果不休眠的话, Idle任务无法得到执行
// Idel任务会清理任务2使用的内存
// 如果不休眠则Idle任务无法执行, 最后内存耗尽
vTaskDelay( xDelay100ms );
}
void vTask2( void *pvParameters )
{
/* 打印任务的信息 */
printf("Task2 is running and about to delete itself\r\n");
// 可以直接传入参数NULL, 这里只是为了演示函数用法
vTaskDelete(xTask2Handle);
}
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
阻塞≠挂起(挂起只能使用具体的挂起函数,阻塞无需),只有freertos自带的taskdelay函数实现了任务切换,即task1被taskdelay挂起,task2执行完之后就delete了自己,因为taskdelay里面调用了vTaskSuspendAll();挂起函数,这里的阻塞就是PendSV里面的列表项操作
这个tick中断,时间片就是taskdelay延时,taskdelay(1000)就是1000个时间片,这1000ms内这个任务被阻塞,其它任务可以执行,这就是实时操作系统的妙处所在
svs操作系统底层,pendsv用于任务切换,任务的重要程度肯定比不上系统内核底层,根据上面的小节可知,svs就是获取任务堆栈地址,将任务数据进行出栈处理,用于操作硬件底层
FreeRTOS任务切换——PendSV
这篇文章主要就是解释了上面张图,中断可打断PendSV,PendSV的作用,以及具有延时特点。
RTOS系列文章(2):PendSV功能,为什么需要PendSV
这篇文章写得很详细了,和我看完原子视频理解的大差不差
1、将Systick的优先级设置成高于IRQ
缺点,IRQ=外部中断被耽误,不可容忍
2、将Systick和PendSV的优先级设置为最低
缺点,IRQ影响Systick时钟,可容忍
3、将SysTick的优先级设置成最高,PendSV的优先级设置为低
缺点,耽误IRQ,不可容忍
1 PendSV是可悬起系统中断,具有【缓期执行】特征。
2 PendSV一般被嵌入式OS用于 任务调度的上下文切换。
3 使用PendSV时,一般会将PendSV的优先级设置为最低,让外部IRQ先执行,等到没有外部中断后,在执行上下文切换。
4 嵌入式实时操作系统的【实时】概念,并不仅仅指应用程序、任务的调度实时,而是指整个系统的实时性高,具备【可剥夺】特点,当有高优先级的中断、任务具备执行条件时,立刻中断当前正在运行的任务,去执行高优先级的IRQ和高优先级任务。
5 嵌入式OS一般会将SysTick的优先级也设置为最低,保证外部中断IRQ优先,详细的分析,我们下一篇文章讨论。
至于sys中断和pendsv中断谁的优先级更高?不清楚,还是没明白,反正两个都是设置最低!
有了Systick中断为什么还要PendSV中断?
文章1
文章2