OS和裸机的区别

OS即(operating system)操作系统,比如我们常用的windows系统,mac系统,android系统,ios系统,linux系统等,都属于操作系统。操作系统的本质是一个特殊的软件,它直接管理硬件,同时为各个应用程序划分资源(内存,堆栈,时间片等),并提供控制(调度,同步)。不管是计算机还是单片机,在任意时刻都只能运行一段代码,顶多是运行速度上会有差距,为什么我们能够在电脑上打开多个软件同时流畅的使用,就需要归功于操作系统对于软件的控制,操作系统会将各个应用程序抽象成进程,给每个进程独立的分配资源,同时对他们进行调度,使得每一个进程都仿佛是在独占整个计算机。

以下图为例,该图较为清晰的反映了硬件,操作系统和应用程序之间的层次关系。可以发现操作系统operating system位于硬件computer hardware之上,应用程序application programs又位于操作系统operating system之上,各个应用程序如compiler(编译器),assembler(组译器),text editor(文本编辑器),database system(数据库系统)等都被抽象成了用户(user)。

另外,虽然目前主流的操作系统都是有图形界面便于用户交互,但是实际上图形界面并不是一个操作系统必要的一环,熟悉linux系统的同学会知道,linux可以通过命令行来完成几乎所有的操作。操作系统最本质的功能还是作为一个连接计算机硬件和应用程序的中间连接者。

由于单片机的资源比较少,一般在单片机上运行的操作系统具有功能精简,实时性强的特点,也被称为RTOS(real time operating system)实时操作系统。单片机上的RTOS非常多,国外常见的RTOS包括FreeRTOS,uCOSⅡ等,国内这几年做的RT-Thread操作系统也发展的很好,阿里和腾讯蹭物联网热点弄了些AliOS啥的乱七八糟的东西,但是好像没啥动静,个人感觉国内真正有竞争力的还是RT-Thread。

没有运行OS的计算机统称为裸机,一般我们利用中断和循环构建前后台系统完成的工程都是裸机工程,中断是前台,针对各种突发的中断源进行及时响应,循环是后台,稳定执行一些常驻的重复性工作。在裸机工程中,编写者对于代码的执行情况是一清二楚的,只要编写者清楚中断到来的时刻,就能知道每一时刻中单片机在执行哪段代码,另外裸机工程由用户手动分配堆栈,所以总体上裸机工程是完全可控的,这也使得裸机的调试难度比较低,但是当工程复杂,耗时、耗资源的任务多时,裸机工程必须在中断中编写复杂的逻辑或者执行耗时的任务,这就会导致裸机工程的执行效率非常低下,响应丧失实时性。

而OS则不一样,由于任务调度和堆栈分配都是由OS来完成的,编写者并不知道任意时刻OS内部的执行情况,因此如果OS的执行出了问题,调试难度是比较大的,很多时候需要借助特殊的调试工具帮助查找问题,比如FreeRTOS就有专用的调试工具FreeRTOSViewer。

但是OS的好处是能够有效的提高单片机的资源利用效率,一方面是空间利用效率高,假设单片机的RAM实际大小为1Mb,一个任务运行需要256Kb的存储空间,则对于裸机工程来说,能够运行的任务数量上限就只有1Mb/256Kb等于4个,而对于OS来说,虽然同时运行的任务数量同样最多只能有4个,但是OS可以通过释放执行完毕的任务的资源来空出新的存储空间,让新的任务得以执行,能够执行的总任务数量大大高于裸机。

另一方面是时间利用率高,之前说过,裸机工程最大的问题就是在工程复杂时,不得不往中断中增加一些执行起来很耗时的代码,继而导致实时性大大下降,比如工程中有一个定时器中断和一个串口中断,两个中断到来的间隔时间位1ms,定时器中断的优先级高于串口中断,只要定时器中断中的代码需要大于1ms的时间来执行,就会导致串口中断无法得到响应。可以想象当中断数量更多,各个中断之间的间隔时间更短,而需要执行的任务耗时更长的情况下,裸机工程跑起来会是个什么惨状。而使用OS时,耗时的代码全部放到任务中,交给OS来调度;中断中只需要执行耗时短的重要代码,这样中断就能够得到及时的响应,即使有多个复杂,耗时的任务也能够实时的进行处理,下图就是一个典型的RTOS执行的时序,可以看到中断消耗的时间很短,耗时的代码都被放到任务中去了。

此外OS还可以提供一些裸机不具备的功能,比如信号量,消息队列,任务通知等,用来管理复杂情况下的资源分配或者进程同步。以信号量为例,信号量的功能是实现各个任务对临界资源的互斥访问,比如一辆步兵车采用双板方案,云台和底盘各有一块开发板,两板采用串口进行通讯。下板需要将底盘yaw轴电机角度信息和功率信息发送给上板,yaw轴电机角度的发送和功率信息的发送各自在一个定时器中断和一个串口中断中进行,这时上下板通讯所使用的串口就是一个临界资源,必须采用信号量进行保护,即当一个任务正在访问通讯串口时,会占有信号量,另一个任务到来之后必须处于阻塞状态,等待上一个任务访问完毕,释放信号量后才能访问通讯串口。

如果不进行保护的话,有可能会出现这种情况:串口中断中发送的功率信息只发送到一半 ,定时中断就绪了,串口中断被打断,剩下的一半发送的信息变成了yaw轴电机角度信息,两个信息一拼之后就变成了没有意义的乱码,发送给上板之后会引发各种奇奇怪怪的问题。

最后,现在的RTOS基本都是有自己的生态圈的,各种开发商会基于RTOS提供各种便利的组件,包括网络,蓝牙,GUI图形界面,文件管理系统等。选择使用OS开发的话就能够直接在工程中调用这些组件的接口,并且有丰富的文档支持,而如果是裸机的话就得自己造很多轮子,过于浪费时间精力。

所以在RM比赛中,到底有没有必要跑OS?我觉得其实是没有必要的,按照上文所说,除非出现工程过于复杂,耗时耗资源的任务过多的情况下,裸机才会有比较严重的问题,其他情况下裸机的可控性和调试方便程度都优于OS,而RM比赛中,一般一段中断里面的代码不过几百行,执行起来的耗时根本到不了毫秒级,用OS也并不能体现出任何优势,另外上面举例的信号量处理临界资源竞争的问题,其实两个中断撞到一起的概率非常非常的小,就算有也可以通过合理的设置中断优先级,或者代码逻辑来避免问题。

但是没有必要不等于不能够或者不应该上OS,如果编写者对OS的机制比较熟悉的话,使用OS就能够有非常好的编码体验,整个工程的抽象度得到了提升,代码的逻辑分层更加的清晰,不同兵种之间进行代码迁移也会比较容易。目前使用OS的参赛队还是很多的,我观摩过几个学校的代码,还是很有水平的。

任务调度机制

这里以FreeRTOS为例,介绍一下OS的任务调度机制。

我们先简单介绍一下进程的概念,对于进程,我们很难找到一个准确的定义,一般我们会将程序的一次执行当成一个进程,更准确的说,我们将一个程序在一个数据集合上的运行过程当成一个进程,这说明进程包含着动态的概念,一段程序执行时,我们一般划分成三个阶段,开始执行--->执行中--->执行完成。这也恰好对应了进程的工作状态:就绪态--->运行态--->挂起态。

进程除了以上三种状态,还有一个重要的状态被称位阻塞态(Blocke),对应的是一个程序执行到一般时被暂停的状态。

在FreeRTOS中,进程的四种基本工作状态是就绪态(Ready),运行态(Running),阻塞态(Blocked)和挂起态(Suspended),各个状态的相互转换关系如下图:

我们编写一段代码,来展示OS下的任务代码编写和裸机代码编写之间的区别:

void green_led_task(void const * argu)
{
	while(1)
	{
		HAL_GPIO_TogglePin(GREEN_LED_PORT,GREEN_LED_PIN);
		osDelay(100);
	}
}

这段程序的功能是控制一个绿色LED闪烁,如果我们在普通的裸机工程中将其作为一个函数调用,程序就会一直卡在这段闪烁的循环里,不会执行后续的代码,假如我们再写一个红色LED闪烁的代码,在裸机工程中调用,红色LED是不会闪烁的(这里我使用的是OS中的延时函数osDelay,裸机工程中对应的是HAL_Delay函数)。

void red_led_task(void const * argu)
{
	while(1)
	{
		HAL_GPIO_TogglePin(RED_LED_PORT,RED_LED_PIN);
		osDelay(100);
	}
}

但是我们通过如下的代码将上面两个函数注册为两个进程之后:

osThreadDef(GreenLEDTask, green_led_task, osPriorityNormal, 0, 128);
green_led_task_t = osThreadCreate(osThread(GreenLEDTask), NULL);

osThreadDef(RedLEDTask, red_led_task, osPriorityNormal, 0, 128);
red_led_task_t = osThreadCreate(osThread(RedLEDTask), NULL);

OS就会自动将上面两个代码进行调度,最后我们看到的结果是红绿LED一起以1s为周期闪烁。

OS的调度过程是这样的——当绿色LED闪烁进程green_led_task执行到osDelay处时,OS会将该进程由运行态变成阻塞态,直到500ms之后才会将其恢复为就绪态。当绿色LED闪烁进程处于运行态时,如果红色LED闪烁进程red_led_task在此时就绪了,就需要优先等待绿色LED闪烁进程从运行态变成阻塞态,才可以从就绪态变成运行态。

所以调度的实质就是OS按照某种调度算法的原则,安排各个进程的运行状态,使得它们以近乎“并行”的方式得到执行。关于调度的具体算法,这里不加以详细的介绍,主要是三个:先来先服务(FCFS)调度算法,优先级调度算法和时间片轮转调度算法。三种算法同时执行,合作完成任务调度功能。

我其实很久之前开了一个FreeRTOS的教程坑,已经写了四节,其中第三节有详细的介绍OS的进程调度算法,有兴趣的同学可以看一看。

https://www.cnblogs.com/sasasatori/p/12231918.html

总结

这是Robomaster电控入门系列教程预定的最后一篇,各位读者如果还有想要了解的东西的话可以私信我,如果我觉得有必要写一写,就会再拓展一下这个系列教程。

我从4个月前开始挖了这个系列的坑,断断续续的写到了现在。教程的目的是服务各位新入RM坑的坑友,有很多同学通过评论,私信和我讨论问题,让我感到自己的这个教程还是帮助到了一些人的。参加RoboMaster这个比赛是我本科生涯中最为浓墨重彩的一笔,我也想通过这种方式来反哺这个为我带了许多成长的比赛,以上,感谢你能够看到这里,谢谢