上手实操操作系统
许多玩单片机等嵌入式设备的小伙伴在熟练之后,都想接触一下实时操作系统。但往往拿到各个操作系统的官方手册后却又不知道怎么入手。如果有这样的问题,小水建议先读一读经典的操作系统教材,补充一些操作系统的基础知识。
也可以参考这个链接《写一个操作系统内核有多难?》进行学习。
有了基本的概念之后,就可以去各官网获取手册和源码开始学习具体的操作系统啦,比如下面这几个链接分别是实时操作系统RT-Thrad、FreeRTOS和Vxworks的官网
RT-Thread:https://www.rt-thread.org/
FreeRTOS:https://freertos.org/
VxWorks:https://www.windriver.com/products/vxworks
1 嵌入式操作系统
狭义上的嵌入式系统硬件资源较为紧张,使得嵌入式软件的设计在很大程度受制于硬件条件,嵌入式操作系统也因之组成相对简单。随着嵌入式硬件的复杂度的上升,嵌入式操作系统的内容和功能也愈发地丰富。譬如RT-Thread,它不仅仅是一个实时内核,还具备丰富的中间层组件,如下图所示。
抛开上层组件不谈,这里梳理一下嵌入式操作系统内核层的基本组件,这些组件能够让我们实现多任务创建和调度管理、任务间通信等功能。所以内核的介绍文档一般包含以下几个部分。
2 基本组件
初始化(Initialization)
用于建立内部数据据结构和全局变量,完成硬件的初始化。
内存管理(Memory Handling)
建立系统和任务堆栈。任务堆栈用于操作系统上的任务的内存资源,可分为静态栈和动态栈。
静态栈包含在操作系统的镜像中,在系统初始化时被建立。
动态栈则不在系统镜像里,在创建任务时被建立。内存管理随处理器内核、处理器功能、物理内存的布局的不同而有所不同。
中断和异常处理(Interrupting and Exception Handling)
用于处理包括Data Abort,Fast Interrupt Request,Interrupt Request,Prefetch Abort,Reset, Software Interrupt (SWI)在内的各类异常。若要支持中断有不同的优先级,则应该提供周期中断方式。
任务调度器(Scheduler)
负责完成任务的上下文切换。要注重效率和公平。上下文切换根据处理器的不同而有所不同。
设备驱动框架(Device driver framework)
即为不同外设提供统一的驱动接口。除了通用性以外,这个框架还应当保证设备驱动的操作是安全的(不会发生多个应用同时访问同一个设备的情况)。
3 RT-Thread实例分析
咱以RT-Thread为例,感性地认识一下这些组件。
还是看上面那张图,此图展示了RT-Thread的组件,我们这里关注的主要是红色框框里边的内核部分。
内核构建在硬件之上,芯片厂商和操作系统厂商会完成系统的芯片移植,我们希望在某个芯片上使用操作系统,可以用对应的芯片包或者某个开发板的板级支持包。
这些操作系统的文档一般都会介绍支持包的获取方法,比如FreeRTOS就把这部分说明放到了Start页 的一个链接里面。
当然啦,RT-Thread 的说明文档是全中文的,学起来更加顺畅。它的内核包含实时内核实现(RT-Thread Kernel)和内核库(Kernel Library)两部分,我们现在只看实时内核部分。
咱按第二部分的顺序审视RT-Thread,初始化部分大概是boot相关的,我也搞球不懂,略去不说了。
3.1 RT-Thread内存管理
通常情况下,计算机中的存储空间分为内部存储和外部存储两类,变量和中间数据存放在内部存储RAM中,需要使用时调入CPU。某些数据的存储空间大小需要根据实际情况确定,这就需要系统支持用户申请和释放内存,即进行内存管理。
实时操作系统的内存管理不同于通用操作系统,要求分配时间确定、不依赖重启解决内存碎片、针对不同资源环境提供高效的分配算法。
针对上面提到的特别要求,RT Thread根据上层应用和系统资源提供针对性的算法,大体上包括内存堆管理和内存池管理两类。内存堆管理根据内存的情况提供小内存、大内存、多内存堆三种针对算法。
系统运行时只能选择几种管理算法其中之一,或者不使用。各类算法的应用程序接口API完全一致。不应当在中断中分配/释放内存,可能会导致当前上下文被挂起。
小内存分配以如上图所示的双向链表的形式进行管理,每一块内存都分为数据头和内存数据两个区域。数据头不能被用户访问,magic被初始化为0x1ea0,used标记该内存块是否被分配,next和prev分别指向下一个和前一个数据块。
小内存管理算法为申请者提供一个或多个这样的内存块串接而成的内存空间(各内存块大小不一定完全一样)。
当然还有解决内存堆效率低、碎片多等问题的内存管理方法——内存池(Memory pool)。
RT Thread的内存池支持线程挂起,当内存空间中没有空闲内存块时,将申请内存的线程挂起等待。这种特性方便了需要大量动态内存的任务,比如数据处理时,可以先处理一部分,存储/转移结果以后有了空闲内存再处理剩余的数据。
内存池由多个等大的内存块组成,创建内存池须指定内存块的数目和大小。删除内存池时,系统将唤醒等待内存池对象的所有线程,然后释放从内存堆上分配的内存池区域,然后删除对象容器中的内存池对象。
到这里咱就知道啦,使用内存池,相当于把内存堆的某一块区域划出来,等分为数个内存块,让线程在这个区域中申请内存块。
3.2 RT-Thread中断和异常处理
中断嘛,大家都知道,具体看RT-Thread的文档吧。链接:RT-Thread的中断和异常处理
3.3 RT-Thread任务调度器
也就是线程的创建和调度啦。
RT Thread的线程调度算法是一种基于优先级的抢占式多线程调度算法,系统本身不限制任务数量,优先调入就绪态的高优先级任务。支持256个不同优先级(0为最高优先级),对不同优先级的任务实行抢占式调度,相同优先级的任务实行时间片轮转调度(根据线程的时间片设置提供运行时间,运行时间到达该线程设置的时间片长度后调入下一同优先级的就绪线程)。
线程分为内核创建的系统线程和应用程序创建的用户线程,两种线程都在对象容器里对应的链表中。线程属性包含线程控制块、线程栈、线程入口函数等。
如上图,RT Thread的线程有独立的堆栈,在线程管理器的管理下,高优先级线程就绪时低优先级的线程被挂起,寄存器信息拷出到线程堆栈,高优先级线程抢占CPU,堆栈被拷贝到寄存器;中断中就绪的高优先级线程在中断结束后抢占CPU。这里提到的挂起、就绪等概念就是线程的状态。
状态 | 描述 | 宏定义 |
---|---|---|
初始状态 | 刚创建,尚未运行 | RT_THREAD_INIT |
就绪状态 | 等待CPU被高优先级线程让出 | RT_THREAD_READY |
运行状态 | 在处理器中运行 | RT_THREAD_RUNNING |
挂起状态 | 阻塞态,资源不可用被挂起或主动延时 | RT_THREAD_SUSPEND |
关闭状态 | 不参与线程调度 | RT_THREAD_CLOSE |
注:RT Thread中实际上不存在运行状态,运行状态和就绪状态等同。
值得注意的是,RT-Thread中有一个空闲线程。系统创建的线程有主线程(main thread)和空闲线程(idle thread)两类。主线程就是系统启动的时候创建的进入main函数的线程。空闲线程则是被用于做资源回收等工作的线程,它在对象容器中没有其他线程时占用CPU。
3.4 RT-Thread的设备和驱动
RT Thread把设备驱动划分为三层,提供I/O设备管理层提供应用程序的接口,中间的设备驱动框架层负责同类设备驱动的共性,设备的特性由设备驱动层负责支持。开发者只需要编写驱动层,不用管上层接口。
4 结语
额,懒得写,就到此为止吧,不一定啥时候再补。