文章目录
- uC/OS-Ⅲ实时操作系统内核原理总结
- 一、为什么要用RTOSli>
- 二、内核探索篇
-
- 2.1 任务定义与切换
- 2.2 系统时基
- 2.3 阻塞延时与空闲任务
- 2.4 系统时间戳
- 2.5 系统临界段
- 2.6 任务就绪列表
- 2.8 任务时间片运行
- 2.9 系统时基列表
- 2.10 任务挂起与恢复
- 2.11 任务删除
- 三、总结
- 四、文章推荐
uC/OS-Ⅲ实时操作系统内核原理总结
学习uC/OS-Ⅲ时做的一些记录,整理了一下,结合自己的理解,做一篇总结(本总结适合有一定的基础的同学食用,主要还是自己看)
注意:文章插图有些不清晰,有需要可以私信我找我要哈
参考书籍:
- uC/OS-III 内核实现与应用开发实战指南
- uC/OS-III 中文翻译(屈环宇译)
- uC/OS-III 技术内幕
- uC/OS-III 源码
- uC-OS-III 3.06.01 API Reference
一、为什么要用RTOSh1>
玩单片机上RTOS前肯定有个疑惑,为什么要上RTOS裸机编程不可以吗p>
首先RTOS和裸机并不是谁绝对的好或者不好,更多情况下我们要根据实际情况来选择使用使用他,裸机编程方式适合代码量小,逻辑复杂度低的情景,其编程简单,开发速度快,而RTOS则适合代码量较大,逻辑复杂,适合稳定性要求高的场景中,缺点就是编程复杂,学习周期较长。
其次对于想要走上嵌入式驱动开发的工程师来说,其一般的路线如下:
2.2 系统时基
? RTOS 必须要一个时基来驱动,该时基本质上是一个定时器,每次计数到位后会进行一次中断,中断程序对时基进行校准,更新时基计数值,然后在系统任务(时基任务)中处理系统的运行状态,比如扫描任务阻塞延时的等待列表,看哪个任务等待结束了,把他脱离等待列表,或者看看其他内核对象的等待列表有没有可以脱离的任务,将他们加入到任务就绪列表里面去,可以说系统时基是系统的心脏,操作系统没有时基,根本运行不起来,同时系统任务调度的频率等于该时基的频率,通常该时基由一个定时器来提供,也可以从其它周期性的信 源获得,每个时钟运行周期称为一个TICK
Cortex-M 内核中有一个系统定时器SysTick,它内嵌在NVIC 中,是一个24 位的递减的计数器,计数器每计数一次的时间为1/SYSCLK。当重装载数值寄存器的值递减到0 的时候,系统定时器就产生一次中断,以此循环往复。因为SysTick 是嵌套在内核中的,所以使得OS 在Cortex-M 器件中编写的定时器代码不必修改,使移植工作一下子变得简单很多,SysTick 是最适合给操作系统提供时基,用于维护系统心跳的定时器。
学习野火《uC/OS-III 内核实现与应用开发实战指南》时记录的定义任务的流程,原文链接:uCOS-Ⅲ学习笔记-时基列表;
2.3 阻塞延时与空闲任务
? 裸机编程里面的延时,通常使用的是软件延时,即还是让CPU 空等来达到延时的效果,比如两个for循环嵌套,空耗CPU,这样太浪费CPU性能了,使用RTOS 的很大优势就是榨干CPU 的性能,永远不能让它闲着,如果要延时绝不能让 CPU 空等来实现延时的效果,那么RTOS中的延时是怎么实现的呢p>
? 在RTOS 中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃CPU 的使用权,进入到等待列表里面凉快去,CPU趁着这个功夫可以去干其它的事情,当任务延时时间到,对应的任务重新获取CPU 使用权,脱离等待列表,回到就绪列表,任务继续运行,这样就充分地利用了CPU 的资源,榨干CPU
? 但上面的延时会有一个问题,如果所有的任务都在等待状态,那CPU 又去干什么事情了就引入了空闲任务,如果没有其它任务可以运行,RTOS 都会为CPU创建一个空闲任务,这个时候CPU 就运行空闲任务。在uC/OS-III中,空闲任务是系统在初始化的时候创建的优先级最低的任务,空闲任务主体很简单,只是对一个全局变量进行计数。鉴于空闲任务的这种特性,在实际应用中,当系统进入空闲任务的时候,可在空闲任务中让单片机进入休眠或者低功耗等操作
2.4 系统时间戳
? 在RTOS里面经常需要精准计算一段时间的长度,因此引入时间戳的概念,时间戳实际上就是一个时间点,记录了一个随着程序运行不断自加的运行值,或许有同学会以为是几个TIM定时器,但实际上不是,定时器的时间精度一般是us级别,但程序运行可不是us级别的,比如主频72M的单片机,时钟周期才1/72M秒,执行一条指令也就几个时钟周期也就是几ns,精度特别高,所以这里用来记录时间戳的外设肯定不是定时器,而是一个叫 DWT 的外设,,该外设有一个32 位的寄存器叫 CYCCNT,它是一个向上的 计数器,记录的是内核时钟HCLK(高速时钟)的运行的个数,当CYCCNT溢出之后,会清0 重新开始向上计数,因此刚好可以用它来做时间戳!内核代码使用他很简单,关掉中断直接读寄存器就能获取到时间戳
2.5 系统临界段
? 临界段是一个代码段,这一段代码段有着特殊的性质,不可分割,相当于原子操作,严格禁止被打断,所以在进入临界段的时候要进行中断屏蔽,防止系统的调度中断打断了临界段的执行。临界段的内核代码实现挺简单,因为Cortex-M 内核专门设置了一条 CPS 指令有 4 种用法,可以用来控制开关中断和开关异常,使用的时候直接调用就行
2.6 任务就绪列表
? 准备运行的任务被放置于就绪列表中。就绪列表包括2 个部分:一个表示任务优先级的优先级表,一个存储任务TCB 的双向链表;优先级表本质上就是一个32位整形,表示32 个优先级,如果要扩展优先级的话,在增加几个32位整形来扩展优先级列表就行,但一般32位足够使用,优先级列表中越低位代表的优先级越高,当对应优先级有任务就绪的时候,会把优先级表中的对应位置1,表示该优先级至少有一个任务已经就绪了,在优先级表中遍历找0的方法主要有两种:一种是前导0(从高位向低位遍历),另外一种是后导0(从低位向高位遍历)
? 每个数组成员指向的链表里面的每个节点都是同一个优先级的任务TCB,因为和优先级表对应,所以内核根据优先级表可以很快的索引到就绪任务的TCB,进行调度操作
2.8 任务时间片运行
? 刚刚了解了就绪列表的概念,有的同学可能有疑惑,OSRdyList[]每个数组成员指向的链表里面的每个节点都是同一个优先级的任务TCB,那这些同优先级的任务如何运行呢引入了任务时间片运行的概念
? 时间片运行就是相同优先级的任务可以分配不同的时间片(TiCK数量),任务每运行一次心跳时钟(TICK)消耗一个时间片,当任务时间片用完的时候,任务会从同优先级链表的头部移动到尾部,让下一个任务共享时间片,以此循环,内核实现的方式也挺简单,就是TCB增加一个时间片计数成员变量,每次调度时候计算在统计一下,为0就进行一次链表的节点移动操作,就像下图的Task2和Task3优先级都是2,TCB中有TimeQuanta 表示任务需要多少个时间片,TimeQuantaCtr 表示任务还剩下多少个时间片,当它为0时,任务2和任务3进行切换
2.9 系统时基列表
? 时基列表我在前面写阻塞延时的时候有提到,由名字我们可以看出时基列表是跟时间相关的,处于延时的任务和等待事件有超时限制的任务都会从就绪列表中移除,然后插入到时基列表这个小黑屋里面,每次TICK都对他们单独计时,任务延时完成后从小黑屋出来插回到就绪列表去,而等待超时了之后还要在进行一个小判断,看任务是进行恢复还是挂起等其他操作,时基列表的结构很简单,在代码层面上由全局数组OSCfg_TickWheel[]和全局变量OSTickCtr 构成
全局数组OSCfg_TickWheel[]的每个成员都包含一条单向链表,被插入到该条链表的TCB 会按照延时时间做升序排列,其中FirstPtr 用于指向这条单向链表的第一个节点。
下图是我以前记录的uCos时基列表运行流程图
结构前三个成员就是指针域不多说了,主要是下面几个:
- PendObjPtr 指向任务所等待的内核对象
- RdyObjPtr 如果任务等待多个内核对象,该指针指向任务被放入挂起列表前已经被提交的内核对象
- 如果任务等待多个内核对象,该指针指向任务被放入挂起列表后被提交的内核对象
每个内核对象会有一个指针指向他的挂起队列,就绪下面的信 量的结构一样,把等待的任务和内核对象关联起来:
2.11 任务删除
? 任务的删除就很简单了,断开TCB与所有内核对象的关联,清除自身变量,回收资源,完结撒花,但注意一点空闲任务不能被删除,因为RTOS至少有一个任务在运行,大致的流程可以参考我以前做的Mind流程图
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!