RTT学习

RT-Thread SMP介绍与移植

SMP:对称多处理(Symmetrical Multi-Processiong)简称SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。

系统上电后,每个CPU都在ROM中的代码控制下独自运行,但是只有主处理器(简称CPU0)跳转到RTT的初始化入口,而其它处理器(次级CPU)会暂停在某个状态下,等待CPU0将它们唤醒。

CPU0完成RTT的全局初始化过程,包括外设初始化,中断控制器的中断分发部分初始化、全局变量的初始化、全局内核对象的创建等等。它还完成CPU0自身硬件初始化,包括MMU,中断控制器的CPU接口部分,以及中断向量表等。

最终,CPU0在执行线程之前,唤醒其它的次级CPU,引导它们执行次级CPU的初始化代码,这段代码会让各个次级CPU去完成自身相关的硬件初始化,并开启任务调度。

此后,系统进入正常运行阶段。

中断管理

什么是中断?简单的解释就是系统正在处理某一个正常事件,忽然被另一个需要马上处理的紧急事件打断,系统转而处理这个紧急事件,待处理完毕,再恢复运行刚才被打断的事件。

当你正在专心看书的时候,忽然来了一个电话,于是记下书的页码,去接电话,接完电话后接着刚才的页码继续看书,这是一个典型的中断的过程。

电话是老师打过来的,让你赶快交作业,你判断交作业的优先级比看书高,于是电话挂断后先做作业,等交完作业后再接着刚才的页码继续看书,这是一个典型的在中断中进行任务调度的过程。

中断处理与CPU架构密切相关

Cortex-M CPU架构基础

不同于老的经典ARM处理器(例如:ARM7,ARM9),ARM Cortex-M处理器有一个非常不同的架构,Cortex-M是一个家族系列,其中包括Cortex M0/M3/M4/M7多个不同型号,每个型号之间会有些许区别,例如Cortex-M4比Cortex-M3多了浮点计算功能,但它们的编程模型基本一致。

寄存器简介

Cortex-M系列CPU的寄存器组里有R0-R15共16个通用寄存器和若干特殊功能寄存器。

通用寄存器组里的R13作为堆栈指针寄存器,R14作为连接寄存器,用于在调用子程序时,存储返回地址;R15作为程序计数器,其中堆栈指针寄存器可以是主堆栈指针(MSP),也可以是进程堆栈指针(PSP)。

特殊功能寄存器包括程序状态字寄存器组(PSRs)、中断屏蔽寄存器组(PRIMASK,FAULTMASK,BASEPRI)、控制寄存器(CONTROL),可以通过MSR/MRS指令来访问特殊功能寄存器,例如

MRS R0,CONTROL
MSR CONTROL,R0

程序状态字寄存器里保存算术与逻辑标志,例如负数标志,零结果标志,溢出标志等等。
中断屏蔽寄存器组控制Cortex-M的中断使能。控制寄存器用来定义特权级别和当前使用哪个堆栈指针。

如果是具有浮点单元的Cortex-M4或者Cortex-M7,控制寄存器也用来指示浮点单元是否在使用,浮点单元包含了32个浮点通用寄存器S0-S31和特殊FPSCR寄存器。

操作模式和特权级别

Cortex-M引入了操作模式和特权级别的概念,分别为线程模式和处理模式,如果进入异常或中断处理则进入处理模式,其它情况为线程模式。

Cortex-M有两个运行级别,分别为特权级和用户级,线程模式可以工作在特权级或者用户级,而处理模式总工作在特权级,可通过 CONTROL 特殊寄存器控制。工作模式状态切换情况如上图所示。

Cortex-M 的堆栈寄存器 SP 对应两个物理寄存器 MSP 和 PSP,MSP 为主堆栈,PSP 为进程堆栈,处理模式总是使用 MSP 作为堆栈,线程模式可以选择使用 MSP 或 PSP 作为堆栈,同样通过 CONTROL 特殊寄存器控制。复位后,Cortex-M 默认进入线程模式、特权级、使用 MSP 堆栈。

嵌套向量中断控制器

Cortex-M中断控制器名为NVIC(嵌套向量中断控制器),支持中断嵌套功能。
当一个中断触发并且系统进行响应时,处理器硬件会将当前运行位置的上下文寄存器自动压入中断栈中,这部分寄存器包括PSR、PC、LR、R12、R3-R0寄存器。

当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样会打断当前运行的中断服务程序,然后把这个中断服务程序上下文的 PSR、PC、LR、R12、R3-R0 寄存器自动保存到中断栈中。

PendSV系统调用

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

中断向量表

中断向量表是所有中断处理程序的入口,如下图所示是Cortex-M系列的中断处理过程:
把用户中断服务程序和虚拟中断向量表中的中断向量联系在一起。当中断向量对应中断发生的时候,被挂接的用户中断服务程序就会被调用执行。

在这里插入图片描述
在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,每个中断服务程序必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义或在起始代码中给出,默认采用起始代码给出:

  __Vectors     DCD     __initial_sp             ; Top of Stack
                DCD     Reset_Handler            ; Reset 处理函数
                DCD     NMI_Handler              ; NMI 处理函数
                DCD     HardFault_Handler        ; Hard Fault 处理函数
                DCD     MemManage_Handler        ; MPU Fault 处理函数
                DCD     BusFault_Handler         ; Bus Fault 处理函数
                DCD     UsageFault_Handler       ; Usage Fault 处理函数
                DCD     0                        ; 保留
                DCD     0                        ; 保留
                DCD     0                        ; 保留
                DCD     0                        ; 保留
                DCD     SVC_Handler              ; SVCall 处理函数
                DCD     DebugMon_Handler         ; Debug Monitor 处理函数
                DCD     0                        ; 保留
                DCD     PendSV_Handler           ; PendSV 处理函数
                DCD     SysTick_Handler          ; SysTick 处理函数

… …

NMI_Handler             PROC
                EXPORT NMI_Handler              [WEAK]
                B       .
                ENDP
HardFault_Handler       PROC
                EXPORT HardFault_Handler        [WEAK]
                B       .
                ENDP
… …

中断 处理过程

RTT中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如图:
在这里插入图片描述
中断前导程序

  1. 保存CPU中断现场,这部分跟CPU架构相关,不同CPU架构的实现方式有差异。
    对于Cortex-M来说,该工作由硬件自动完成。当一个中断触发并且系统进行响应时,处理器会将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括PSR、PC、LR、R12、R3-R0寄存器。

  2. 通知内核进入中断状态,调用rt_interrupt_enter()函数,作用是把全局变量rt_interrupt_nest加1,用它来记录中断嵌套的层数。

用户中断服务程序
在用户中断服务程序(ISR)中,分为两种情况,第一种情况是不进行线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程。

另一种情况是,在中断处理过程中需要进行线程切换,这种情况会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。

在Cortex-M架构中,rt_hw_context_switch_interrupt()的函数实现流程如图所示,它将设置需要切换的线程rt_interrupt_to_thread变量,然后触发PendSV异常(PendSV异常是专门用来辅助上下文切换的,且被被初始化为最低优先级的异常)。PendSV异常被触发后,不会立即进行PendSV异常中断处理程序,因为此时还在中断处理中,只有当中断后续程序运行完毕,真正退出中断处理后,才进入 PendSV 异常中断处理程序。

中断后续程序
中断后续程序主要完成的工作是:

1 通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1。
2 恢复中断前的 CPU 上下文,如果在中断处理过程中未进行线程切换,那么恢复 from 线程的 CPU 上下文,如果在中断中进行了线程切换,那么恢复 to 线程的 CPU 上下文。

中断栈

在中断处理过程中,在系统响应中断前,代码需要把当前线程的上下文保存下来(通常保存在当前线程的线程栈中,)再调用中断服务程序进行中断响应、处理。在进行中断处理时(实质是调用用户的中断服务程序函数)。

中断处理函数很可能会有自己的局部变量,这些都需要相应的栈空间来保存,所以中断响应映入需要一个栈空间来作为上下文,运行中断处理函数。
中断栈可以保存在打断线程的栈中,当从中断中退出时,返回相应的线程继续执行。

中断栈也可以与线程栈完全分离开来,即每次进入中断时,在保存完打断线程上下文后,切换到新的中断栈中独立运行。在中断退出时,再做相应的上下文恢复。使用独立中断栈相对来说更容易实现,并且对于线程栈使用情况也比较容易了解和掌握(否则必须要为中断栈预留空间,如果系统支持中断嵌套,还需要考虑应该为嵌套中断预留多大的空间)。

RTT采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先预留出的中断栈空间中,等中断退出时再恢复用户的栈指针。这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。

内存池

内存堆管理器可以分配任意大小的内存块,非常灵活和方便。
但其也存在明显的缺点,一是分配效率不高,每次分配时,都要查找空闲内存块;二是容易产生内存碎片。为了提高内存分配的效率,并且降低内存碎片,使用内存池。

内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大加快内存分配与释放的速度,且能尽量避免内存碎片化。
此外,RTT的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒。

内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存池数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。

struct rt_mempool
{
    struct rt_object parent;

    void        *start_address;  /* 内存池数据区域开始地址 */
    rt_size_t     size;           /* 内存池数据区域大小 */

    rt_size_t     block_size;    /* 内存块大小  */
    rt_uint8_t    *block_list;   /* 内存块列表  */

    /* 内存池数据区域中能够容纳的最大内存块数  */
    rt_size_t     block_total_count;
    /* 内存池中空闲的内存块数  */
    rt_size_t     block_free_count;
    /* 因为内存块不可用而挂起的线程列表 */
    rt_list_t     suspend_thread;
    /* 因为内存块不可用而挂起的线程数 */
    rt_size_t     suspend_thread_count;
};
typedef struct rt_mempool* rt_mp_t;

内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块通过链表连接起来(此链表也称为空闲链表)。
每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。

物理内存中允许存在多个大小不同的内存池,每个内存池又由多个空闲内存块组成,内核用它们来内存管理。

内存管理

在计算系统中,通常存储空间可以分为两种:内部存储空间和外部存储空间。

内部存储空间通常访问速度比较快,能够按照变量地址随机地访问,也就是我们通常所说的RAM(随机存储器),可以把它理解为电脑的内存;而外部存储空间内存所保存的内容相对来说比较固定,即使掉电后数据也不会丢失,这就是通常所讲的ROM(只读存储器),可以理解为电脑的硬盘。

计算机系统中,变量、中间数据一般存放在RAM中,只有在实际使用时才将它们从RAM调入到CPU中进行运行。
一些数据需要的内存大小需要在程序运行过程中根基实际情况确定,也就是系统具有对内存空间进行动态管理的能力。
在用户需要一段内存空间时,向系统申请,系统选择一段合适的内存空间分配给用户,用户使用完毕后,再释放回系统,以便系统将该段内存空间回收再利用。

内存管理的功能特点

由于实时系统中对时间的要求非常严格,内存管理往往要比通用操作系统要求苛刻得多:

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

RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:

第一种是针对小内存块的分配管理(小内存管理算法);

第二种是针对大内存块的分配管理(slab 管理算法);

第三种是针对多内存堆的分配情况(memheap 管理算法)

内存堆管理用于管理一段连续的内存空间。

在这里插入图片描述
内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时,又可以释放回堆中供其他应用分配使用。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饼干饼干圆又圆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值