RT-Thread内核浅析

文章目录

  • 前言
  • 一、RTOS概述
  • 二、RTOS介绍
    • 1.RTOS内核&实时操作系统
    • 2.实时操作系统&嵌入式操作系统
  • 三、RTOS内核
    • 3.1 对象管理
    • 3.2 线程管理
      • 3.2.1 线程
      • 3.2.2 调度器
      • 3.2.3 线程栈
      • 3.2.3 线程调度
    • 3.3 时钟管理
      • 3.3.1 概念介绍
    • 3.4 内存管理
      • 3.4.1 概念介绍
    • 3.5 中断管理
      • 3.5.1 PendSV系统调用
      • 3.5.2 中断向量表
    • 3.6线程间同步&通信
      • 3.6.1 线程间同步
      • 3.6.2 线程间通信
  • 四、问题答疑
    • 4.1 什么是临界区?
    • 4.2 什么是线程互斥?
  • 五、总结


前言

  本文主要记录学习RT-Thread后的一些理解,文章中部分借鉴了火哥的《RT-Thread内核实现与应用开发实战》和网上一些大佬的理解,如有侵权请联系本人删除。


一、RTOS概述

  维基百科上实时操作系统定义:实时操作系统(Real-time operating systim, RTOS)又称即时操作系统,它会按照排列运行、管理系统资源,并为开发应用程序提供移植的基础。实时操作系统与一般的操作系统相比,最大的特色就是“实时性”,如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时,这种特征保证了各个任务的及时执行。   常见的实时操作系统有FreeRTOS、RT-Thread、uCOS等,这3种RTOS目前资料都比较丰富,尤其FreeRTOS是目前全球使用最广泛的RTOS,但是从初学者角度建议选择国内的RT-Thread,原因有3点:1.FreeRTOS经过近几年推广,资料也很丰富了,2.RT-Thread的内核代码量相对较少,对学习内核的关键知识是有利的,3.无论是哪种RTOS,机制都是一样的,先理解其中一种即可,有时间再去学习其它几种的框架和设计模式。
  本文主要从初学者角度,介绍RT-Thread内核组件及关键概念,并且总结一些初学者常见疑惑进行解答,目的是帮助初学者在会用的基础上更加深入的了解底层原理,通过学习一些基础技术,提高解决问题的效率,也能规避一些问题的产生。

二、RTOS介绍

1.RTOS内核&实时操作系统

  在多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并负责任务之间的通信,内核提供的基本服务是任务切换。 实时内核并不等于实时操作系统,实时内核只是实时操作系统的一部分。

2.实时操作系统&嵌入式操作系统

  这是完全不同的两个概念,虽然大多数实时操作系统都是嵌入式操作系统,但是嵌入式操作系统并不全都是实时的;实时操作系统一般具有速度快、吞吐量大、代码精简、代码规模小等特点,但是这些特点不是实时操作系统特有的,其它操作系统也可以做到,实时性才实时RTOS的最大特征。
  维基百科上关于实时运算的定义: 实时运算(Real-time computing)是计算机科学中对受到“实时约束”的电脑硬件和电脑软件系统的研究,实时约束像是从事件发生到系统回应之间的最长时间限制。实时程序必须保证在严格的时间限制内响应。通常实时回应时间会是以毫秒为单位,也有时是以微秒为单位。相比之下,非实时系统是一种无法保证在任何条件下,回应时间均符合实时约束限制的系统。有可能大多数的情形下,非实时系统都可以符合实时约束限制,甚至更快,只是无法保证在任何条件都可以符合约束限制。

三、RTOS内核

  实时内核是实时操作系统最基础也是核心的部分,实时内核一般包括以下几个模块:实时调度器、线程管理、时钟管理、内存管理、中断管理、 IPC通信,如下如所示:

RT-Thread内核浅析_第1张图片

  接下来以RT-Thread为实例介绍内核组件,下图是RT-Thread的内核架构图。

RT-Thread内核浅析_第2张图片

3.1 对象管理

  RT-Thread 内核采用面向对象的设计思想,系统级的基础设施都是一种内核对象,包括:线程,信号量、互斥量、事件、邮箱、消息队列和定时器,内存池、设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型、大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被连接到该链表,如下图所示:

RT-Thread内核浅析_第3张图片

3.2 线程管理

3.2.1 线程

 线程是最基本的调度单位,线程执行时的运行环境称为上下文,也就是各个变量和数据,包括所有寄存器变量、堆栈、内存信息。线程管理的主要功能是对线程进行管理和调度,实时操作系统和其它操作系统最大的不同是强调:严格按照优先级来分配CPU时间,并且时间片轮转不是实时调度器的一个必选项。系统中存在两类线程,分别是系统线程和用户线程,系统线程是由RT-Thread内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除。以下是线程的基本概念和关键点:
 1. 从系统的角度看,线程是竞争系统资源的最小运行单元。
 2. 在线程切入切出时保存上下文环境(寄存器值,堆栈内容)是调度器主要职责。
 3. RT-Thread的线程是抢占调度机制,同时支持时间片轮转调度机制。RT-Thread中提供的线程调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。
 4. 线程中不允许出现死循环(没有阻塞机制),否则比这个新城优先级低的线程都将无法执行。空闲线程是唯一不允许出现阻塞情况的线程,因为RT-Thread需要保证系统始终有一个可运行的线程。
 5. 空闲线程可以挂钩子函数,完成一些特殊功能,比如系统运行状态指示,系统省电模式。
 6. 线程五种状态迁移

RT-Thread内核浅析_第4张图片

3.2.2 调度器

 RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。当一个运行着的线程使得一个比它优先级更高的线程满足运行条件,当前线程的CPU使用权限就被剥夺,高优先级的线程立即得到CPU使用权。如果中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回这个线程时,线程调度器将该线程的上下文信息恢复。

3.2.3 线程栈

 RT-Thread 线程具有独立的栈,线程栈主要用于保存线程切换时的上下文和函数中的局部变量,线程栈的增长方向和芯片架构有关。应用开发时,一般结合MCU内存资源先分配一个较大的栈大小,线程运行过程中查看使用的最大栈深度,然后再加上一些余量作为最终的栈大小。

3.2.3 线程调度

 线程运行过程中,同一时间只允许一个线程在处理器中运行,按照运行过程可划分为5种状态,初始态,就绪态,运行态(其实并不实际存在,和就绪态是相等的)、挂起态,关闭态。操作系统根据实际情况实时改变各个线程的状态。

RT-Thread内核浅析_第5张图片

 当调度器查询到就绪列表中有更高优先级的线程时,总是会执行优先级最高的。如果就绪列表中最高优先级的线程不止一个,系统就采用时间片轮转的调度方式进行线程调度,也就是说时间偏仅对优先级相同的就绪态线程有效。 系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)。线程有对应参数可以设置每个线程轮询的时间片时长。
 作为实时操作系统,要保证线程中没有陷入死循环的操作,必须要有让出CPU使用权的动作,比如调用延时函数或者主动挂起,否者这个线程会一直占用CPU导致其它线程无法执行。

3.3 时钟管理

3.3.1 概念介绍

 任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的时间,比如线程的延时,线程时间片轮转调度以及定时器超时等。时钟节拍是特定的周期性中断,这个中断可以看作系统心跳。中断的时间间隔取决于不同的应用,一般是1ms-100ms,时钟节拍率越快,系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。
 时钟节拍由配置为中断触发模式的硬件定时器产生,中断到来时,调用一次某个特定接口通知操作系统已经过去一个系统时钟;定时器分两种:
  • 硬件定时器:
     芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件 定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
  • 软件定时器:
     由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。

3.4 内存管理

3.4.1 概念介绍

 RT-Thread主要有两种内存管理方式,动态内存堆管理和静态内存池管理。由于实时操作系统对时间要求很严格,因此内存管理比通用操作系统要求也苛刻的多:
  • 分配内存的时间必须是确定的
     一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。
  • 避免产生内存碎片
     随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片(因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一 整块的大内存分配出去),系统中还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存。对于通用系统而言,这种不恰当的内存分配算法 可以通过重新启动系统来解决 (每个月或者数个月进行一次),但是对于那些需要常年不间断地工作于野外的嵌入式系统来说,就变得让人无法接受了。
  • 适应不同嵌入式系统资源
     嵌入式系统的资源环境也是不尽相同,有些系统的资源比较紧张,只有数十 KB 的内存可供分配,而有些系统则存在数 MB 的内存,如何为这些不同的系统,选择适合它们的高效率的内存分配算法,就将变得复杂化。
     RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:
    第一种是针对小内存块的分配管理(小内存管理算法);
    第二种是针对大内存块的分配管理(slab 管理算法);
    第三种是针对多内存堆的分配情况(memheap 管理算法)

3.5 中断管理

 中断处理与CPU架构密切相关,中断能打断任何优先级的线程。这里只讲一个重要的知识点,PendSV异常, RTOS的上下文切换是依靠PendSV异常实现的。

3.5.1 PendSV系统调用

 是一种异常,可以像普通的中断一样被挂起,是专门用来辅助操作系统进行上下文切换的,PendSV异常会被初始化为最低优先级的异常,每次需要进行上下文切换的时候,会手动触发PendSV异常,在PendSV异常处理函数中进行上下文切换。

3.5.2 中断向量表

 所有中断处理程序的入口。

3.6线程间同步&通信

3.6.1 线程间同步

 同步是指按预定的先后顺序运行,线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序,也可以说在线程之间通过通过不建立起执行顺序的关系,如果没有同步,线程之前将是无序的。
 线程间同步方式有:信号量、互斥量、事件集,其核心思想都是:访问临界区的时候只允许一个(一类)线程运行,目的是使得多个线程通过特定的机制来控制线程之间的执行顺序,避免多个线程异步操作一些共享资源导致各种异常现象。
信号量的作用
 互斥: 线程使用临界资源时,先获取信号量使其变空,其他线程使用临界资源时就会无法获取信号量而进入阻塞,保证了临界区数据的安全,但是这样可能会导致优先级翻转。
 同步:信号量创建后被置空,线程获取信号量就进入阻塞态,其他线程或中断中释放信号量,就会使阻塞的线程进入就绪态,进入就绪态后如果是最高优先级的线程就会立即切换。比起在线程中轮询标记的做法,信号量的方式更能节省cpu资源。
 信号量有计数信号量和二值信号量之分,但是都是用于保护资源。计数信号量主要用于保证资源数量不为0时才能被访问,比如停车位的应用场景。二值信号量主要用于线程之间,中断与线程之间的同步。

互斥量
 互斥量(互斥型信号量)是一种特殊的二值信号量,和信号量的不同点在于:

  • 支持互斥量所有权
  • 支持递归访问
  • 具有优先级继承机制,以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。

     1.互斥量在任意时刻只有两种状态:开锁或关闭;
     2.当互斥量被线程持有时,互斥量处于闭锁状态,其他线程不能对该互斥量进行开锁或持有,除非该线程释放这个互斥量,这就是互斥量所有权。
     3.当线程持有互斥量时,该线程可以再次获得这个锁而不被挂起,这就是递归访问。(如果是在二值信号量中,再次获取信号量会导致线程挂起)因此,一般二值信号量用于实现同步,互斥量用于保护资源互锁。
     4.优先级继承算法&优先级继承&优先级翻转
    优先级继承算法:暂时提高某个占有保护资源的低优先级线程的优先级,使之和所有等待该资源的线程中优先级最高的那个线程优先级相等。当这个低优先级的线程释放资源时,优先级重新回到原始值。这样,继承优先级的线程可以避免系统资源被任何中间优先级的线程抢占。

     优先级继承机制:如果某个资源正在被一个低优先级的线程使用,互斥量处于闭锁状态,如果此时有高优先级的资源去访问这个资源,高优先级的线程申请不到互斥量就会进入阻塞态,系统会将当前持有该互斥量的线程临时提升到与高优先级线程的优先级相同,这个优先级提升的过程就是优先级继承。优先级继承机制确保高优先级线程进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最低。
    优先级翻转:当低优先级线程占用某些资源时,互斥量处于闭锁状态,高优先级的线程访问资源时,由于申请不到互斥量会进入阻塞态,只能等低优先级的使用完后释放资源才能访问。这种高优先级线程无法运行而低优先级线程可以运行的现象称为"优先级翻转"。

     之所以尽量使用互斥量进行资源保护是为了降低优先级翻转对操作系统的危害,高优先级的线程必须能及时响应,而优先级翻转会导致系统的高优先级线程阻塞时间过长。在没有优先级继承的情况下,如果系统中存在高优先级线程、中间优先级线程、低优先级线程,存在这样一种情况:低优先级线程正在使用某个资源,对应的互斥量处于闭锁状态,高优先级线程访问该资源时,由于获取不到互斥量进入阻塞状态;此时如果有中间优先级线程打断了低优先级线程,那么低优先级线程就要等中间优先级线程执行完后才能继续执行,然后使用完临界资源后释放,此时高优先级才能退出阻塞态,使用该资源。当然可能还有更多中间优先级的线程,阻塞时间可能更长,高优先级线程就做不到实时响应了。
    RT-Thread内核浅析_第6张图片

互斥量应用场景
互斥量是信号量的一种,初始化的时候处于开锁状态。适用于

  • 线程可能会多次获取互斥量的情况,可以避免同一线程多次递归持有而造成死锁的问题;
  • 可能会引起优先级翻转的情况;
  • 多线程环境下可用于临界资源的保护,从而实现独占式访问;

3.6.2 线程间通信

 线程间通信,顾名思义,主要是为了解决线程之前的通信问题,有几种方式:邮箱、消息队列,信号

  • 邮箱
     一封邮件只能容纳固定4字节内容,邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。如果需要在线程间传递比较大的消息时,可以发送一个指向缓存区的指针,指针刚好是4字节。

  • 消息队列
     是邮箱的拓展,可以用于线程间通信,消息不定长,消息队列有以下特征:
     1.当队列使用结束后,需要通过删除队列操作释放内存函数回收
     2.发送消息不带阻塞机制,因为可能是在中断中发送消息,如果消息队列满了,发送消息的线程或中断程序会收到一个错误码,也是为了保护数据。
     3.读消息队列有阻塞机制,分3种情况:1.不阻塞,2.阻塞 3. 阻塞一段时间后唤醒,时间(tick)可配置
     应用场景:线程和线程之前的消息交换、中断给线程发送消息,中断不能接收消息

  • 信号
     在软件层次上是对中断机制的一种模拟,本质是软中断,用作异步通信,收到信号的线程对各种信号的处理方法一般有3种。
    1.类似中断处理,指定处理函数,由函数来处理
    2.不处理,像未发生过一样
    3.对该信号的处理保留系统的默认值

四、问题答疑

4.1 什么是临界区?

 多个线程操作/访问同一块区域(代码),这块代码就称为临界区。

4.2 什么是线程互斥?

 线程互斥是指对于临界区资源访问的排它性。当多个线程都要使用临界区资源时,同一时刻最多只允许一个线程使用,线程互斥可以看成是一种特殊的线程同步。线程同步的核心思想都是:在访问临界区的时候只允许一个(或一类)线程运行。

五、总结

 上面是对RT-Thread学习的一个简单归纳,篇幅有限,后面会在专栏中更细致的讲解RTOS,协议栈相关的知识。

你可能感兴趣的:(RT-Thread)