伪操作系统简介

软件开发大郭
0 评论
/
11 阅读
/
6505 字
02 2023-12

伪操作系统,它不是mcu上的操作系统,就是一种编程思想,是一种对代码的组织策略,一种问题的解决方案。 主要是解决在资源短缺的mcu平台上运行多任务,不需要操作系统,不需要写复杂的调度系统,只需要在编程时按照固定的格式编程,格式化代码就可以了,也不需要额外编写一些属于操作系统的代码。 伪操作系统的编程要求如下: 为了实现像操作系统一样的多任务,又不能操作PC寄存器进行线程切换和线程管理的情况下, 让多个任务同时进行的办法就是让多个任务运行在一个while循环中,为了让多个任务能同时运行而 又同时在一个while中,必须让每个任务进行重组,每个任务在while中的线程是不能阻塞的,全部 作为状态机存在,每个任务在状态未达到要求时必须返回,在有任务需求时再执行必要的代码。代码比如:

        #include"task1module.h"
        #include"task2module.h"
        #include"task3module.h"
        //主函数
        int main(){
            task1init();//多个服务初始化
            task2init();
            task3init();
            while (1) {
                task1server();//多个服务并行
                task2server();
                task3server();
            }
        }

以上时一个典型的伪操作系统的主函数部分,其中每个task任务都是要写两个函数,一个是init函数,主要 是用来初始化线程必须的内容时调用的,比如io口的初始化,系统内部的模块时钟初始化,模块初始化等, 然后回在main的主循环中回调用server函数,主要是用来实现多任务的。如果线程初始化后不需要server可以不写不调用。 由上面的主要任务调度部分可见,每个任务的server函数是不可阻塞的,而且init函数是只会执行一次的,为了不让server函数阻塞,所以必须做一些调整。如下:

        #include"task1module.h"
        unsigned char modulebuffer[120];
        int modulebufindex;
        void task1init() {
            memset(modulebuffer,0,120);
            modulebufindex = 0;
            gpio_init();
            uart1_init();
        }
        void task1server() {
            if(modulebufindex < 1) return;//未收到数据时返回
            if(modulebuffer[modulebufindex - 1] != '#'){//未收到指定的特殊数据时返回
                modulebufindex = 0;//必须清空缓存区
                return;
            }
            modulebufindex = 0;
            while(modulebufindex < 4);//如果时间短可以直接等待接收到完整命令,或者先接收一部分,改成异步方式等和上面的一样
            switch(modulebuffer[1]){
                case '1':break;//相关命令处理,由于这里时耗时处理,所以当处理命令时就会造成其他任务的阻塞,所以尽量在这里只做任务的异步分派,不要做太过耗时的操作。
                defalut: break;
            }
            modulebufindex = 0;
        }
        void uart1interrupt(){
            clear_interrupt_flag();
            if(modulebufindex < 120)
                modulebuffer[modulebufindex++] = sbuf;
        }

以上面的串口接受任务为演示,串口接受命令的任务中,主要包括三部分,init函数是主要初始化串口部分server函数中主要就是处理 各种命令,主要的线程即为server函数,也就是不可阻塞的且会被循环调用的函数,其次是interrupt函数,这是系统的中断函数,主要 是处理系统中断,中断主要是清除标记和接收数据,不会处理数据,这一点很重要!中断不可做耗时任务。

参考

更一下,

先不谈怎么写代码,先举个例子,思考一个问题:

假如我们开了一家饭店,每天有很多的顾客,当然,我们也有服务员,我们暂且不考虑怎么做饭,单单考虑以服务员的角度考虑怎么了解到每个客人的需求以及怎么给他们送饭的问题,而且我们先用一个服务员来完成这个任务。

简单的思考之后,我们很容易的想到就平常来说,服务员都是看到有客户来了然后过去问情况,然后再让座,然后再上菜,每来一个就重复这个过程,这很容易理解吧。

但是我们做这个的过程中就会遇到很多问题,客户他不是一次点完的,他老是点菜,服务员老是要给他上菜,而且所有客户都这样,而我只有一个服务员,为了不让一些客户等的时间太长,那么我怎么办?

说到时间,有人可能就想到了,每个客户规定一段时间,所有的客户轮流,这样每个客户都能分配到一段服务时间,这样每个客户等待的时间是一样的,被服务的时间也是一样的,服务员每个客户轮流服务,一轮又一轮,且每个客户的服务时间一样。学习过操作系统的人可能想到了我说的这个例子其实就是时份操作系统,服务员就是CPU,客户就是每一个进程,CPU轮流执行每一个进程1毫秒左右的时间,多个线程排队执行,宏观上看起来这些线程是一起运行的,而且利用中断或者其他手段每1毫秒强制切换另一个线程,这样就很容易造成一个问题就是时序要求很高的场合下完全不能用,因为你的程序指不定在执行什么代码的时候就给你强行中断了不知道多长的时间,所以很多操作系统环境中不可能做时序操作,除非能强制不让切换到其他线程。

这种时分的服务方式显然对所有的客户来说是公平的,因为大家都获得了差不多的服务时间和等待时间,但是,它不是一种公正的服务方式,因为很显然有的客户可能正在吃,没空点菜,这时候刚好又到了服务这个客户的时间,此时服务员按照规定只能在这里等,就算是客户不点菜,服务员也得等,这不就是浪费大好时光?要是服务员是个人,这种磨洋工的时间作为老板的我看见了也就不说什么了,但是这服务员很明显是的CPU,是个机器啊!作为程序员的我那是必须要把他的剩余价值全部榨干!

所以我想了个办法,修改了规则,规定服务员必须一刻不停,所有的客户先问再上菜,不能有任何的懈怠,任何的等待都是不被允许的,所有的客户不得恶意占用服务员的时间,所有的客户必须做该做的事,每次服务员轮询时必须直接说出自己的需求,如果没有需求,无需求直接停止服务,切换下一个客户,这种累死服务员的办法,就是我所说的伪操作系统。

接下来就是怎么从代码上去实现的问题了,且看下回分解。

更新一下

上回说到累死服务员的办法,有人说是轮询,也差不多吧,不过这种轮询我觉得还是比较特殊的,因为它是不行被阻塞的!

我先写个大概框架:

while(true){

if(客户1需要点菜) 执行点菜

if(客户2需要点菜)执行点菜

……

}

好吧没错,就是一个大的轮询,每个符合条件的任务都会被执行,就是这样,但是,每个符合条件的任务都不得阻塞,且在最短的时间里将任务转化成异步任务后及时返回,就像这样:

if(串口缓存区不为空) return

if(没有数据需要发送) return

串口数据写入到缓存或者记录好串口数据指针,然后启动硬件串口中断,把第一个数据写入sbuf,return

这就是一个完整的串口发送服务,他不会阻塞,也几乎不会耗时,

而主要的工作发生在串口中断中,串口中断使用发送缓存区空中断,当第一个数据发送结束之后更具缓存区信息直接就可以继续发送直到数据全部发完,这写操作完全是异步完成的,不影响点菜过程。

以上是串口传输数据,再举个例子:

if(usb端点无数据)return

if(usb端点数据不合规){

记录一次传输失败情况

return

}

if(全局变量里显示有大量数据传输){

if(大量数据已经传输完毕){

清除大量数据传输标记

清除usb端点数据有效标记

}

else if(usb端点不忙){

将下一块数据写入端点缓存区并使能端点中断

}

}

switch(usb端点传输内容解析后的cmd){

case 把数据发到串口上:

和上面异步发送数据是一样的

break

case 从串口读取数据:

把串口接受数据缓存区的内容复制到usb端点缓存区,使能usb端点中断

break

case 大量数据的读取:

把数据量的信息描述记录下来,记录到全局变量,把数据成若干分块,先传输第一块数据并记录已经传输的量

return

}

清除usb端点数据有效标记表示一个usb命令被处理

以上是一个稍微复杂一点的usb与串口之间互动的逻辑,整个过程中无阻塞,每次被轮询时都是很快就过去了,最耗时的也就是内存拷贝的开销,由于stm32这种单片机的usb端点缓存区并不大,所以这种数据转移的开销也不会很大,作为服务员来说在这里她基本停留的时间也是很短的,接下来再举个例子:

点阵刷新时,根本不需要轮询,也不需要if,直接设定一个定时器自动完成即可,定时器的中断间隔时间为

time = 1 / 矩阵的行数 * 帧数

当每次定时器中断后:

消影(将所有列置0)

将新行的数据输出到gbio

return

这样的话用一个定时器实现了整个矩阵的数据刷新,而且服务员(cpu)完全不需要参与,一切都是异步完成的,cpu只要修改全局变量里面的内容矩阵屏幕就会自动更新。

相反的情况,比如ccd数据的读取,我们还是使用定时器,定时器的时间和上面一样,但是由于读取外部数据时要经过io,读取数据的过程耗时较长,所以我们不能再在定时器中断中完成作业,这次我们就要改变思路,定时器中断只作为一个行同步信号,每次中断后修改全局变量中的行同步信号为真即可,如果ccd本身有行同步信号,那么这个信号中断之后也是一样只修改全局变量里的行同步信号,仅此而已,接下来就需要服务员(cpu)的参与,写一个服务如下:

if(行同步信号无效)return

读取线性ccd的数据到内存,更具数据的去处使能相关外设的中断比如串口数据中断,吧读出的数据交给串口

重置行同步型号为无效

return

这样的话,在每一个行同步信号之间服务员做了读取的工作,如果这个工作能很快完成,那么她就可以继续做其他工作,如果没有及时完成,那么她就会错过一个行同步信号,或者多个,这时就必须要优化读取的代码,或者降低帧数,以保证服务员有足够的时间作业。

好了以上的例子都是切实有用的实战例子,都是一些具体的服务实现,但作为一个不正经的操作系统,怎么着也得有个任务调度系统,为了实现任务的动态添加和规范化,我们可以写一个这样的结构体:

struct 伪操作系统服务{

void (* 初始化服务)();

void (* 非阻塞轮询服务)();

}

然后再做一个定长的(也可以修改成链表等)的数组:

struct 伪操作系统服务们[100];

每当我们添加新服务时,先调用这个服务的初始化方法,然后再扔到数组里就可以了,这个数组我们使用就相当简单了:

int i=0;

while(true){

伪操作系统服务们[i++].非阻塞轮询服务();

i%=伪操作系统服务总数;

}

好了,整个体系架构我都讲清楚了,你们说它是状态机也好,是轮询也好,主要的框架思路就是主要的while是不可阻塞的,每一个服务之间都是独立的,每个服务在被加载之前都会调用它的初始化函数,这样他就有机会创建它的运行环境,主要工作是初始化全局变量,io口,外设等,然后就会轮询每个服务的非阻塞轮询函数,在这个函数中必须最短的耗时来返回,不得拖延时间或者阻塞,这样所有的服务就都会正常运行,当遇到多个服务之间的交互时,就需要用共享内存的方式,比如usb的数据转移到串口时,他们使用共享缓存,一个异步写,另一个异步读就可以了,为了发生不必要的数据混乱,采用双缓存模式即可,读写分开,都完成后切换缓存块。多个服务共享数据时可以采用多缓存模式,当然由于数据有如果只有一个写服务,那么多个读服务并不需要多个缓存,只需要不同的进度指针即可。

以上就是伪操作系统的全部。

就放一个我的实际中使用的项目的吧。

以上项目在f103ret6上实现了

一个串口用于通过bc35实现http请求,三个spi设备同时挂载到spi1实现串口oled及中文字库还有一块flash,usb的cdc设备作为上位机控制和音频数据写入flash,ad监测电池电量,一路dac输出flash中的音频,主要是用于在点击按钮时输出音效,以及运行时输出提示音效,后来这种提示音效做成了使用讯飞的语音芯片,那个更方便。

实现这些并不需要操作系统。 https://mobile.zhaimaojun.top/ZmjOS/

标签:
    暂无数据