RT-Thread 内核基础(学习)

内核基础

本章介绍RT-Thread内核基础,包括:内核简介、系统的启动流程及内核配置的部分内容,为后面的章节奠定基础。

RT-Thread 内核基础(学习)_第1张图片
内核是一个操作系统的核心,是操作系统最基础也最重要的部分。
它负责管理系统的线程、线程间通信、系统时钟、中断及内存等。

内核处于硬件层之上,内核部分包含内核库、实时内核实现。

内核库是为了保证内核能够独立运行的一套小型的类似C库的函数实现子集。这部分根据编译器的不同自带C库的情况也会有些不同,当使用GNU GCC编译器时,会携带更多的标准C库实现。

C库:也叫C运行库(C Runtime Library),它提供了类似“strcpy”、“memcpy”等函数,有些也会包括“printf”、“scanf”函数的实现。
RT-Thread Kernel Service Library仅提供内核用到的一小部分C库函数实现,为了避免与标准C库重名,这些函数前会加上rt_前缀。

实时内核的实现包括:对象管理、线程管理及调度器、线程间通信管理、时钟管理及内存管理等等,内核最小的资源占用情况是 3KB ROM,1.2KB RAM。

线程调度

线程是RT-Thread操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁的部分的代码和禁止中断的代码是不可抢占的之外,其余部分都可以抢占,包括线程调度器自身。

支持 256 个线程优先级(也可通过配置文件更改为最大支持32个或8个线程优先级,针对STM32默认配置是32个线程优先级)。
0优先级代表最高优先级,最低优先级留给空闲线程使用;同时它也支持创建多个具有相同优先级的线程,相同优先级的线程间采用时间片的轮转调度算法进行调度,使每个线程运行相应时间;

调度器在寻找那些处于就绪状态的具有最高优先级的线程时,所经历的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关。

时钟管理

RT-Thread的时钟管理以时钟节拍为基础,时钟节拍是RT-Thread操作系统中最小的时钟单位。
RT-Thread的定时器提供两类定时器机制:

  1. 单次触发定时器:在启动后只会触发一次定时器事件,然后定时器自动停止。
  2. 周期触发定时器:定时器会周期性地触发定时器事件,直到用户手动的停止定时器否则将永远持续执行下去。

另外,根据超时函数执行时所处的上下文环境,RT-Thread的定时器可以设置为HARD_TIMER模式或者SOFT_TIMER模式。

通常使用定时器回调函数,完成定时服务。

线程间同步

采用信号量、互斥量与事件集实现线程间同步。

线程通过对信号量、互斥量的获取与释放进行同步。

互斥量采用优先级继承的方式解决了实时系统常见的优先级翻转问题。

线程同步机制支持线程按优先级等待方式获取信号量或互斥量。

线程通过对事件的发送与接收进行同步;事件集支持多事件的“或触发”和“与触发”,适合于线程等待多个事件的情况。

线程间通信

RT-Thread支持邮箱和消息队列等通信机制。

邮箱中一封邮件的长度固定为4字节大小。

消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。

邮箱效率较消息队列更为高效。

内存管理

支持静态内存池管理及动态内存堆管理。

当静态内存池具有可用内存时,系统对内存块分配的时间是恒定的。
当静态内存池为空时,系统将申请内存块的线程挂起或阻塞掉(即线程等待一段时间后仍未获得内存块就放弃申请并返回,或者立刻返回。等待的时间取决于申请内存块时设置的等待时间参数),当其它线程释放内存块到内存池时,如果有挂起的待分配内存块的线程存在的话,就会唤醒这个线程。

动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的SLAB内存管理算法。
还有一种动态内存堆管理叫做memheap,适用于系统含有多个地址且不连续的内存堆,使用memheap可以将多个内存堆粘贴在一起,让用户操作起来像是在操作一个内存堆。

I/O设备管理

RT-Thread将PIN、I2C、SPI、USB、UART等作为外设设备,统一通过设备注册完成。

实现了按名称访问的设备管理子系统,可按照统一的API界面访问硬件设备。

在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。

RTT启动流程

RTT支持多种平台和多种编译器,而rtthread_startup()是规定的统一启动入口。

RT-Thread 内核基础(学习)_第2张图片
以MDK-ARM为例,用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口函数 main()。

为了在进入main()之前完成RT-Thread系统功能初始化,我们使用了MDK的扩展功能 s u b sub sub 和 和 superKaTeX parse error: Can't use function '$' in math mode at position 12: 。 可以给main添加$̲sub的前缀符号作为一个新功能函数 S u b Sub Sub m a i n ,这个函数可以先调用一些要补偿在 m a i n 之前的功能函数(这里添加 R T − T h r e a d 系统启动,进行系统一系列初始化),再调用 main,这个函数可以先调用一些要补偿在main之前的功能函数(这里添加RT-Thread系统启动,进行系统一系列初始化),再调用 main,这个函数可以先调用一些要补偿在main之前的功能函数(这里添加RTThread系统启动,进行系统一系列初始化),再调用Super$$main转到main()函数指向,这样可以让用户不去管main()之前的系统初始化操作。

int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    //板级初始化,需要在该函数内部进行系统堆的初始化
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization */
    rt_system_signal_init();
#endif

    //创建一个用户main线程
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

启动调度器之前,系统所创建的线程在执行rt_thread_startup()后并不会立马运行,它们会处于就绪状态等待系统调度。
待启动调度器之后,系统才转入第一个线程开始运行,根据调度规则,选择的是就绪队列中优先级最高的线程。

rt_hw_board_init()中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。

RT-Thread程序内存分布

一般MCU包含的存储空间有:片内Flash与片内RAM,RAM相当于内存,Flash相当于硬盘。
编译器会将一个程序分为好几个部分,分别存储在MCU不同的存储区。

linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin
".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07

Program size包含以下几个部分:

  1. Code:代码段,存放程序的代码部分。
  2. RO-data:只读数据段,存放程序中定义的常量。
  3. RW-data:读写数据段,存放初始化为非0值的全局变量。
  4. ZI-data:0数据段,存放未初始化的全局变量及初始化为0的变量。

编译完工程会生成一个.map文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
  1. RO Size包含了Code及RO-data,表示程序占用Flash空间的大小。
  2. RW Size包含了RW-data及ZI-data,表示运行时占用的RAM的大小。
  3. ROM SIze表示烧写程序所占用的Flash空间的大小。

程序运行之前,需要有文件实体被烧录到STM32的Flash中,一般时bin或者hex文件,该被烧录文件称为可执行映像文件

RT-Thread 内核基础(学习)_第3张图片
左图:可执行映像文件烧录到STM32后的内存分布,它包含RO和RW段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。

STM32在上电启动之后默认从Flash启动,启动之后会将RW段中的RW-data(初始化的全局变量)搬运到RAM中,但不会搬运RO段,即CPU的执行代码从Flash中读取,另外根据编译器给出的ZI地址和大小分配出ZI段,并将这块RAM区域清零。

其中动态内存堆为未使用的RAM空间,应用程序申请和释放的内存块都来自该空间。

rt_uint8_t *msg_ptr;
msg_ptr = (rt_uint8_t *)rt_malloc(128);
rt_memset(msg_ptr,0,128);

代码中的msg_ptr指针指向的128字节内存空间位于动态内存堆空间中。

全局变量则是存放在RW段和ZI段中,RW段存放的是具有初始值的全局变量,ZI段存放的系统未初始化的全局变量。

#include 

const static rt_uint32_t sensor_enable = 0x000000FE;
rt_uint32_t sensor_value;
rt_bool_t sensor_inited = RT_FALSE;

void sensor_init()
{
     /* ... */
}

sensor_value存放在ZI段中,系统启动后会自动初始化成0(由用户程序或编译器提供的一些库函数初始化成零)。

你可能感兴趣的:(RT-Thread,RT-Thread,嵌入式实时操作系统)