400行程序写一个实时操作系统(十八):时间触发型RTOS的设计
前言
前面已经说过了,Sparrow采用的是时间触发系统的设计,本章将会讲解时钟触发相关的算法。
重新回顾一下时间触发系统的定义:
时间触发系统的任务调度基于定时器中断,适用于周期性任务和确定性要求高的场景,如控制系统。它的内部有一个时钟,每隔一定时间间隔就会触发一次时间中断,每次时钟中断时,调度器决定是否需要切换任务。
systick时钟
systick时钟是为RTOS而设计的的一个中断时钟,它使得RTOS在不同架构之间的移植性难度大大减小。
在前面的文章中,笔者曾经调试过Sparrow来了解RTOS内部的中断使用,当时得出的结论是:SysTick中断会在一定时间间隔响应,然后触发PendSV中断产生上下文切换:
systick中断用法如下:
如何使用时钟
定时上下文切换
参考上面的程序,我们可以先设置SysTick时钟,让它在一定时间间隔响应,然后在SysTick中断中加入PendSV中断的触发函数,这样就能达到定时上下文切换的目的。
除此之外,SysTick本身就是一个定时器,RTOS中需要定时的地方太多了,我们必须高效利用它的定时功能。
延时阻塞态
利用延时的时间
在Sparrow中,任务有就绪态、阻塞态、挂起态三种状态,任何任务只能有一种状态。延时就是一种阻塞态,表示它在等待某个事情的发生。
让我们想一想,在非RTOS的环境中,我们的延时一般都是空等,但是在RTOS环境中,任务被分解为线程的形式,每个线程尽量与其他线程并行执行,那么,我们是不是可以在其他线程延时等待时,把它移除就绪表,然后把时间让给其他线程执行呢?等时间一到,再把它加入就绪表。
这当然是可行的。
如何设计算法?
如图所示,我们需要一张延时表,当每个任务延时时,这个任务会被踢出就绪表,延时表会记录它延时到期的时间,每一次SysTIck中断时,时间基数就会加1,然后我们可以先比较延时表中的任务是否到时间,如果到时间,那么就从延时表删除,加入就绪表,如果没到,就继续等。
如何解决溢出的问题?
我们知道计算机中一个数的大小是有上限的,比如uint_32,最大只能到2^32 - 1。如果发生了溢出那怎么办呢?
笔者提供两种思路:
1.设置更大的计数单位
2.使用两个表进行维护。
当然,Sparrow使用的是后一种。
设置更大的计数单位
请读者想一想,我们每天都是24小时轮转,但我们是怎么表示时间的呢?是通过年、月、日来进行记录,也就是说,SysTick的计数也可以这样干,每溢出一次,前面的计数单位就加一,这样就能记录事情发生的先后了。
但是假设时间非常漫长,那么单片机会不断的设置更大的计数单位,从秒、分、时、日、月,一直到年,每一个数字都需要内存来存储,这样的方法,缺点是内存并不固定。
使用两个表进行维护
正常情况下,任务的延时加上当前时间最多溢出一次,例如1 + 2^32 -1 会溢出一次,虽然1 + 2^32 - 1 + 2^32会溢出两次 ,但是这个时间段太长了,太离谱了,根本不可能被使用。既然最多溢出一次,那么,只要多一个表就可以解决溢出的问题。
当时间溢出发生时,由于此时记录没有溢出的延时表的任务肯定已经完成了延时,也就是说,这张表现在是空的。
此时只有溢出的延时表上的延时需要完成,当溢出再次发生时,我们又需要一张表,难道我们要重新申请一块内存来存储表吗?
显然,完全不需要,因为没有溢出的延时表是空的,我们可以重复利用它。现在,之前溢出的延时表变成了未溢出的延时表,之前未溢出的延时表被重新利用,拿来记录现在溢出的延时任务!
使用两个表进行维护,这就是Sparrow的延时策略。
总结
先讲解了时间触发型RTOS的设计,然后讲解SysTick时钟,它通常作为RTOS的定时器。为了充分利用定时器,引入延时阻塞的概念,解决了裸机中常见的延时空等待的问题。