需要对FreeRTOS等操作系统深入学习的朋友可以报下韦东山老师的系统训练营,很充实的课程内容,并且老师很负责。
中度掌握——知道内部机制
备用:并无高端技巧,该文件在STM32F103上搭建好环境方便测试而已。
下载链接
任务: 本质就是一个运行起来的函数,包含信息有:一段保存在flash上的代码(无法更改)、任务运行的位置、任务运行的环境(各种局部变量)。
函数引申出下列的问题,任务切换时会暂停任务,那么如何暂停任务并且恢复任务
怎么暂停/恢复任务,需要保存哪些东西:任务执行的位置,变量的值不能被破坏以及其他一些需要注意的事项。
通过add_val函数理解函数运行过程中如果被打断,需要保存哪些东西,理解函数运行的本质。并且理解需要哪些东西才可以让这个函数恢复运行。
通过Keil工具查看add_val函数的反汇编代码,进行分析
读a、读b、计算a+b、写入a+b
CPU从内存中将数据读到哪里?CPU内部有多个寄存器,读入的数据保存在CPU内部寄存器中
CPU如何读取数据?需要知道源、目的、长度
CPU怎么知道执行这样的一个操作来对数据进行执行?CPU读Flash得到指令,执行读取内存的指令
CPU如何知道要将读取的数据放到哪一个CPU内部寄存器呢?CPU不知道,它所有操作都是从程序中读取出来的,程序告诉它保存在哪,这些指令叫做机器码
函数开始,假设刚开始sp=addr1
PUSH指令本质是一条写内存指令,将R3和Lr寄存器的值放入栈中。
lr等于当前程序的返回地址,也就是下一条指令prvsetupHardware()函数的地址。
栈的地址由sp指针决定,高标号的寄存器放在高地址,低标号寄存器在低地址,即括号里面的内容没有序号的排放也可以。
假设sp(R13)为addr1,那么效果图如下
temp = *pa
temp=temp+*pb
R2 = [R1+0x00] = [&b] = b
R3 =[sp + 0x00] = temp = a
R2 = R2 + R3 = b + temp
[sp + 0x00] = R2 = b + temp
即,temp = temp + *pb
*pa=temp
假设在下图中的位置突然发生中断,如何保存现场?
为了使得返回现场时,能继续工作,在我们假设的这个场景里面需要在跳转去中断的时候提前保存R2的值,不然在跳转执行的程序中可能会修改R2的值。而在其他的场景中,则需要保存不同的值。
**现场:**被打断的瞬间,所有CPU内部的寄存器的值;
**怎么保存现场:**保存在内存中;
**现场保存在内存哪里:**保存在栈里,把16个寄存器保存在栈里,如下图;
一个任务,其中必定包含一个函数,这个函数可能还会调用多个其他的函数,记录当前程序执行的状态也称之为保存现场。
可以简单的认为:一个任务就是由函数和它的栈组成,也称为运行中的函数。
对于一个任务,它的局部变量保存在它的栈中,它运行过程中所使用的寄存器也保存在栈里面,调用关系也保存在栈里面,如何表示这个任务,如何找到被保存的栈,所以得有一个任务结构体。
注意传参这里栈的大小这里写的1000指1000*4字节的栈大小,然后从FreeRTOS的数组中划分出来使用。
针对创建任务函数,需要在内存中使用TCB来保存,查看精简后TCB结构体(删除配置项)
该语句创建了1000*4字节大小的栈
xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
假设刚创建的这个任务处于暂停的状态,想要其恢复现场,再次运行,就需要从栈里面恢复寄存器。
下图中pxStack的位置不对,位于于栈开始的低地址,在该图中形象表示在内存的最下方;在创建任务函数中,它等于malloc后的值。
回顾本文开头内容,依次创建三个任务123,任务1和2 的优先级为0,3的优先级为2,三个任务分别放入如下的链表。
调度就只负责就绪链表,从上往下按照优先级高低对任务执行,执行完毕后讲任务放到链表尾部。
注意事项:在就绪链表中,还有一个空闲函数。
以任务3为例,这里调用vTaskDelay函数延时了5ms,此时调度器就会将任务3从pxReadyTasksList链表中移动到pxDelayTaskList链表中,所以只有就绪链表中的任务会消耗CPU资源,其他链表中的任务并不消耗CPU资源;
空闲任务的优先级为0,并且就绪链表中优先级为0的链表中有其他任务时,空闲任务先执行,然后判断得出发生礼让,进行调度,然后重新运行。
在创建任务函数添加就绪链表的函数中,有这样一个判断,如果当前新添加的TCB优先级等于当前的TCB,那么当前的TCB就等于新添加的TCB,即当前运行的任务就是最后添加的任务。
在程序执行启动调度器函数vTaskStartScheduler时,在函数中,会根据用户静态创建任务与动态创建任务的区别,创建一个空闲任务Idle任务,这个任务的优先级为0,放入就绪链表0。
**空闲任务的作用:**通常完成一些清理工作,如果其他任务自杀,那么空闲任务就需要去释放其他任务的栈,避免内存溢出。
如果有同是优先级0的其他就绪任务,空闲任务主动放弃一次运行机会,下一次再正常运行,因此在优先级都为0的时候,空闲任务只执行了一小会,判断得出需要礼让,就调用taskYIELD函数进行重新调度。
单片机上电复位的时候会执行Reset_Handler函数,函数会跳转到__main函数,然后跳转到main函数,main函数所用到的栈是汇编中设置的msp,也给中断函数使用