RTOS的调度原理
我们这一系列的微信推送,仅仅是为了可以帮助大家快速地对操作系统有一个感性层面上的认识。当然,让人更好地记住一样复杂的东西,就是寻找另一样简单的东西来解释它。但是,这种方式的解释,是完全不符合教育应该有的严谨逻辑的。因此,让我们来收回这种非科学的论证,转而用科学的方法论来表述我们需要阐述的东西。
(一) 自己写一个调度算法
在很久很久以前,我大抵记得那是我大二学生时期,我就开始自己动手写过一个调度算法。其实现的功能非常简单,就是使两个“任务”分时地去交替执行,并且,利用定时器去决定这两个任务运行的时间。当时满心欢喜地想告诉全世界,自己完成了一个“内核”的框架。
现在想来,上述的这个行为真的是非常可笑的无知满足。因为这个算法的实现非常简单。简单阐述一下其原理,就是建立两个指向函数的指针变量,然后建立两个函数,并且用上述两个变量指向它们。定时器开始定时,假设周期为10ms,那么每隔10ms只要去切换着两个变量就可以了。这样子,如果宏观性上面,你就能看到两个任务是在同时运行了。
再来分析一下上述的东西,听着特别简单,但是经不住仔细推敲,它们肯定是不符合操作系统的任何特征的。首先,如果你的每个任务执行的时间小于10ms还好,万一超过了其分时分配的运行时间(10ms)而强行使用定时器去切换,那当前没运行完的数据完全就丢失了,没有任何拯救的余地。其次,它的任务没法被动态地建立,回收。还有很多很多的缺点。这是我大三恍然大悟的东西。因此,到现在我也不喜欢使用在主函数里面利用定时器产生计数值,根据计数值去切换运行函数的方式。为什么?因为你裸机编程的时候,特别是在某个函数里面有大量的通讯等待操作时,你无法保证每个函数的运行时间正好小于等于你设定的时间。因此总会出现,有些任务根本执行不到这一现象。
但是不幸的是,后来工作之后,发现很多电子工程师都是这样去处理他的程序流向的。
(二) 操作系统调度算法的原理
我开始研究操作系统,也是那时候在自信满满地用了我自己的“内核”做了几个项目之后,直到有一次做一个长距离Modbus高速通信,由于这个函数里面有一个做CRC检验失败等待重传的函数。而由于选用了高速的数据流,加上距离比较长,因此高频信号的畸变比较严重,导致了误码率很高。因此这个函数频繁被调用,但是后来发现,这个函数几乎不能被完整运行下来。分析了好久的原因,最后发现,原来这个函数执行的时间远远超过了我分配给它的运行时间了,因此,没等它执行完成,我的调度算法强制将其中断了。并且由于没有数据的保存机制,导致了整个函数只能执行一半就被销毁了。后来工作之后,也遇到过别的工程师这么做,劝其修改,还是不听,最后导致了严重的错误。从那以后,我宁愿while(1)到底,也不愿使用这样的方式了。
而一个成熟的操作系统内核,它的调度算法是会在中断当前任务之后,将数据保存下来的。
我们称当前运行的任务为“运行态”,而没有被运行的任务,我们称为“非运行状态”。但是关于“非运行状态”,这只是一个笼统的名称而已,这里面包含了众多的子状态。我们以后来详细说明一下“非运行状态”的具体细分。在这里注明一下,我们课程的逻辑当中,为了简化操作系统模型,因此把所有具体的非运行状态进行集合化,在一些高级的操作系统当中,这个问题有待讨论。
以最基本的分时操作系统来说,这里假设所有任务的优先级都是统一的,时间一到就会自动轮询。当时间到来时,运行态任务没有被执行完的数据,操作系统都会有一个机制,将这些数据进行保存,可以是压入堆栈。具体操作的过程,就是将寄存器里面的数据进行保存,有点类似于中断函数的调用。接着把可以回收的内存进行回收。然后,为下一个需要执行的任务分配内存,最后再去执行下一个需要执行的任务。而当时间又轮到了第一次没有执行完的任务之后,又会重新为其分配内存,然后把之前的数据取出来,继续执行。整个笼统的过程就是这样。