R0和R1寄存器是用来保存地址的,【】是取地址里面的值的意思。
R2寄存器辛辛苦苦读取寄存器r0所指向的存储单元(也就是a)的值赋给自己,还没来得及把R2寄存器保存到栈里面就触发中断了,或者当R3寄存器好不容易从栈里面取出值来,本来想接下来用作与R2寄存器保存数值相加的,结果还没用中断就进来,不能够白费力气,所以应该先保存起来,应该怎么保存现场呢?
现场可以理解成是寄存器,CPU的寄存器有R1、R2、...一直到R15。当触发中断瞬间需要马上把16个寄存器的值都保存到栈里面,栈在哪里呢?在Flash里面。
任务其实就是运行中的函数和它的栈。
注意:
什么是现场?现场就是被打断瞬间所有寄存器的当前值。
2)主程序在执行时需要执行子程序,主程序把参数传给子程序需要用到寄存器R0、R1、R2,主程序在执行程序过程一般都不会用到这几个寄存器,除非调用函数才需要用到,因此无需保存上述几个寄存器,其它寄存器就要保存;
3)程序在执行时需要任务切换,由于不知道任务执行过程用到了哪些寄存器,因此所右的寄存器都需要保存,即需要保存CPU的15个寄存器。
4、创建任务的函数简析
创建一个任务时,肯定需要一个函数,pxTaskCode就是函数,这个挺重要的;pcName就是函数名,usStackDepth是栈的深度,当函数在执行前,需要用到的局部变量肯定需要通过malloc分配一段空间作为栈空间,当任务被切换时肯定需要保存现场,也需要放在栈里面;pvParameters参数不重要,uxPriority是优先级来的,pxCreatedTask是TCB结构体,用来记录当前任务的信息,相当于一个身份证。
5、创建任务的内部细节
函数调用时LR寄存器的值并没有入栈,这是为什么呢?因为子程序太简单了,在子程序里面并没有再进行函数的调用。下边的函数有嵌套调用,可以看看。
感觉程序很智能,在第一个函数被调用且开辟局部变量的栈空间之前,就仿佛知道了被调用的函数里面还有内嵌了一个函数,知道先把LR保存起来.
因此栈的大小取决于:1、局部变量的个数;2、调用深度(被调用函数里面有多少个嵌套函数)。
Freertos是从哪里分配栈空间的呢?
是从这个叫做ucHeap的数组里面,这个数组有17KB大小,各个任务的栈都是从这个数组的空间里面划分的。
通过配置数组的大小,这样程序一开始就会开辟一段空闲的空间,这是堆空间,当任务创建的时候所需要开辟的栈空间就从这个堆空间进行划分。
xTaskCreate函数里面的1000的意思在数组ucHeap的栈空间里面划分了1000*4的存储空间,从哪里开始划分呢?从pxStack指针变量指向的存储单元开始划分。
执行任务的函数入口地址在哪里呢?
vTask1函数的地址就是任务的入口,当执行xTaskCreate函数时就会开辟TCB结构体和栈,为什么vTask1和函数参数在TCB结构体中查找不到呢?因为vTask1表示任务函数的地址,当执行xTaskCreate函数时就会把vTask1的函数地址赋给PC指针,同时函数的参数pvParameters要赋给R0,本例中参数为NULL。
任务栈里面保存着所有寄存器的值,任务函数的起始位置保存在该任务栈存放R15的存储空间,需要把它赋给PC才能执行该任务函数vTask1,当执行该任务函数时就会知道参数保存在存储空间的R0位置。
阻塞态是CPU想去执行但是没办法执行,只能等待。
暂停是CPU不会自动恢复执行的,必须手动恢复。
程序会从优先级最高的就绪链表开始找,当发现当前就绪链表有任务时,就去执行,没有的话就去下一级就绪链表里面找,相同优先级的任务会轮流执行,比如Task1执行一个TICK后就切换到Task2执行,Task2执行一个TICK后就切换到Task1执行。那么,是谁在调度任务呢?
TICK中断!
当触发TICK中断时,会先把当前任务现场保存到栈里面去,然后恢复新的任务现场。
6、任务状态的切换
对于实时操作系统,高优先级的任务只要不休眠,低优先级的任务就无法执行到。
当执行到vTaskDelay函数时,高优先级的任务就会从就绪态链表中移出到pxDelayTaskList链表中。
假设在第2ms时vTaskDelay函数开始执行,那么高优先级任务就会从就绪态到阻塞态中去,程序开始执行任务1,到第3ms时发现vTaskDelay函数没记够5ms,因此执行任务2,到第4ms时发现vTaskDelay函数没记够5ms,因此执行任务1。。。。。,当到第7ms时vTaskDelay函数已经记够5ms,于是又把vTaskDelay任务函数从阻塞态移动到就绪态中去。
7、任务调度深度探讨
pxReadyTaskLists【0】链表中包含一个空闲任务,空闲任务有什么用呢?当Task1或者Task2不需要占用栈空间时就需要Idle Task来释放空间。
创建的同优先级的任务优先级是怎么样的呢?
假设创建的任务优先级都是0,首先创建任务1,这时currentTCB会指向当前Task1,接着创建任务2,由于Task2的优先级大于或等于Task1,这时currentTCB会指向Task2,接着创建任务3,由于Task3的优先级大于或等于Task2,这时currentTCB会指向Task3,接着启动调度,由于空闲任务的优先级是0,idle Task的优先级大于或等于Task3,这时currentTCB会指向idle Task,因此任务运行顺序:idle task先于TASK1先于TASK2先于TASK3。
假设创建的任务优先级都是1,首先创建任务1,这时currentTCB会指向当前Task1,接着创建任务2,由于Task2的优先级大于或等于Task1,这时currentTCB会指向Task2,接着创建任务3,由于Task3的优先级大于或等于Task2,这时currentTCB会指向Task3,接着启动调度,由于空闲任务的优先级是0,idle Task的优先级小于Task3,这时currentTCB会指向TASK3,因此任务运行顺序: task3先于TASK1先于TASK2先于IDLE TASK。
创建3个优先级都是1的任务,观察哪个任务优先执行
创建3个优先级都是0的任务,观察哪个任务优先执行
本来应该是空闲任务先执行才对,这是因为设置了礼让,才会导致任务1先执行,当我把礼让配置为0后,实验现象如下:
汇编文件的栈和任务的栈有什么差别吗?
Stm32启动文件里面的向量表的前四个字节是_initial_sp函数,这个函数其实就是为STM32开辟了一段栈空间,栈大小为0X200,栈顶为_Iinitial_sp对应的地址,因为103芯片有两个sp寄存器,一个叫main sp,一个叫process sp,这个地址会赋给main sp寄存器。
创建任务时是需要分配任务栈的,创建空闲任务也是需要分配栈的,还有触发中断函数也是需要分配栈的,main_sp执行的栈其实就是一个总栈,用户程序需要的栈都是从中分配的。
8、
假设Task1、Task2、Task3的优先级都是0,当开启空闲任务时,空闲任务先执行,空闲任务在程序运行过程发现有当前优先级链表的任务数为4,4大于1,于是执行任务礼让程序,然后保存现场,发起了调度,于是task1先执行,接着task2再执行,task3再接着执行,最后又从空闲任务上一次的程序执行处接着执行,这就完成了一个循环。下次程序执行也是跟上面一样空闲任务先执行一会发现有其他同优先级的任务于是放弃。
假设第1ms时Task1任务开始执行,那么,什么情况下,Task1会中途放弃运行呢?
可以分为主动放弃和被动放弃,主动放弃包括:vTaskDelay、QueueRev(队列为空)。
被动放弃包括中断比如按键中断,按键中断触发的话就可以发送数据到队列里面,可能唤醒某个更高优先级的任务从而抢占。
如果配置成不抢占、不礼让、轮流执行、优先级都为0,会怎么样?
上面这个程序是空闲任务的程序,只要配置成不抢占,不管有没有配置成可礼让,就会一直在清理工作完成后礼让;配置成抢占,也要看有没有其它同优先级的任务。
这个程序是任务Tick中断次数增加函数,用于每次触发tick中断要不要切换任务,如果配置了不可抢占,那么就不会轮流执行了,不抢占是大前提,只要设置成不抢占,其它任务连轮流执行的机会都没有。
最低级优先级的中断都比最高优先级的任务的优先级高。
最低级优先级的中断都比最高优先级的任务的优先级高。
Task1在不抢占、不礼让的前提下会一直执行,即便触发了Task中断也是不会发生任务切换,那么,Task1要如何切换到其它任务呢?
可以通过主动放弃的方式(主动放弃不会重新触发TICK中断),比如执行vTaskDelay函数,就会让Task1由就绪态切换到阻塞态,从而触发调度切换到其它任务执行,其它任务会一直永久执行下去。