etas os调度表任务切换源码分析--Apple的学习笔记
一,前言
之前说过一共3个example,第一个是周期alarm触发的任务,第二个example是调度表触发的任务,第三个exmaple是时间触发的任务、因为需求理解起来都容易,实现方法就多种多样了,所以要探秘下,也就是看看源码。
二,调度表
- 调度表设计方法
同样是在Os_IncrementCounter_xxx函数中来设置match,之前周期alarm的match只要加cycle值就是下一个预期是就绪时间点。那么基于调度表就绪也是在此函数中为tick加1,且设置下一个match的时间点,然后判断就绪就调用activeTaskKL来设置当前core的ReadyTasks.P0的flag。最后就是判断优先级高于当前就调用os_disapatch来切换激活高优先级任务。天呢!看懂一个的设计思路再看另外一个就简单了,因为他们套用的是同一套代码框架及结构变量。
if ((current_std->state == SCHEDULETABLE_RUNNING) && (now == current_std->match)) {
(void)Os_PerformEP(current_stc, current_std, (TickType)(24U), os_current_core_const, os_current_controlled_core);
}
调度表主要就是2个数组,当前clive的example就一个调度表,里面task的EP点的时间间隔就生成在Os_const_expiry_intervals数组中,然后这些EP点对应的task的index值相关的mask就生成在了Os_const_expiry_actions数组中,若有5个EP带你,那么此数组有6个对象,最后一个值为1,1>>1就是0,用于下图for循环的final break的作用。
image.png
一个调度表完成了,cfg_index为0从break退出,此时state变成stop了。再看是否还有调度表,若没有了就进入else,若是重复调度的,那么config就变0了。
} else {
if (current_stc->repeat) {
current_std->config = current_stc->config;
current_std->state = SCHEDULETABLE_RUNNING;
}
这里config用在2个数组下标中,一个靠取余数一个靠取整数,所以才会看到加8和加1,这里调度表中不同数量的task生成的值也是不同的。感觉设计的有点复杂,直接用index不行吗?估计是为了节约内存。
-
关于切换效率
之前我提及freertos都是到TCB取任务地址直接切换任务,但是周期alarm的当时看起来每次tick中断中切换一个任务。其实我理解错误,周期alarm的每次tick中断可以切换多个任务,而且调度表关于os_dispatch的代码和周期alarm是一样的。
每个task的最后一句都是TerminateTask(),此函数中就调用了Os_longjmp,Os_longjmp是恢复上下文,也就是从pcxi的链表中pop出来。也就是任务挂起。
如下图entry_function里面就是task函数最后一句就是TerminateTask()
image.png
上图可以也看得出os运行前会打开全局中断,运行完成后关闭,所以task运行可以被下一个sw tick打断。另外就是用了do while循环,只要这个core有就绪的task,就会一个个任务进行执行。若1个sw tick中运行不完,csa的栈空间就会加大,因为又会调用一个中断函数,再次跑到这里嵌套,这个sw tick若没完成,下一个的sw tick完成了所有的dispatch,再return到这里就直接可以退出do while循环了。
三,小结
多看几个就知道核心内容就是tick counter加1的时候进行match比较来设置就绪任务flag,然后根据就绪任务和当前任务的优先级来进行切换任务。而关键变量就是每个core有一个动态结构体保存当前运行的情况,每个task有自己的固定的结构体保存一下csa和栈的信息,然后Os_dyn_alarms里面保存的是期望match的tick counter时间点。