内核介绍
- 内核处于硬件层之上,内核部分包括内核库、实时内核实现。
-
实时内核的实现包括:对象管理、线程管理及调度器、线程间通信管理、时钟管理及内存管理等等,内核最小的资源占用情况是 3KB ROM,1.2KB RAM。
线程调度
- 线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。
- 支持 256 个线程优先级(也可通过配置文件更改为最大支持 32 个或 8 个线程优先级,针对 STM32 默认配置是 32 个线程优先级),0 优先级代表最高优先级,最低优先级留给空闲线程使用;
- 同时它也支持创建多个具有相同优先级的线程,相同优先级的线程间采用时间片的轮转调度算法进行调度,使每个线程运行相同时间;
- 系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关。
时钟管理
- RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的时钟单位。
- RT-Thread 的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止定时器否则将永远持续执行下去。
线程间同步
- RT-Thread 采用信号量、互斥量与事件集实现线程间同步。
- 互斥量采用优先级继承的方式解决了实时系统常见的优先级翻转问题。
- 线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。线程通过对事件的发送与接收进行同步;事件集支持多事件的 “或触发” 和 “与触发”,适合于线程等待多个事件的情况。
线程间通信
- RT-Thread 支持邮箱和消息队列等通信机制。邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。
- 邮箱效率较消息队列更为高效。邮箱和消息队列的发送动作可安全用于中断服务例程中。
- 通信机制支持线程按优先级等待或按先进先出方式获取。
内存管理
- RT-Thread 支持静态内存池管理及动态内存堆管理。
- 当静态内存池具有可用内存时,系统对内存块分配的时间将是恒定的;当静态内存池为空时,系统将申请内存块的线程挂起或阻塞掉 (即线程等待一段时间后仍未获得内存块就放弃申请并返回,或者立刻返回等待的时间取决于申请内存块时设置的等待时间参数),当其他线程释放内存块到内存池时,如果有挂起的待分配内存块的线程存在的话,则系统会将这个线程唤醒。
- 动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 SLAB 内存管理算法。
I/O 设备管理
- RT-Thread 将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。实现了按名称访问的设备管理子系统,可按照统一的 API 界面访问硬件设备。
- 对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。
RT-Thread 启动流程
- MDK裸机启动流程:系统启动后,先从汇编代码startup_stm32f103xe.s 开始运行,然后跳转到 C 代码的main()函数进行运行
-
1 IMPORT __main 2 LDR R0, =SystemInit 3 BLX R0 4 LDR R0, =__main 5 BX R0 6 ENDP
- RT-Thread在进入main()之前会进行RT-Thread系统功能初始化,
-
我们使用了 MDK 的扩展功能 $Sub$$ 和$Super$$ 。可以给 main 添加 $Sub$$ 的前缀符号作为一个新功能函数 $Sub$$main ,这个 $Sub$$main 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统初始化功能),再调用 $Super$$main
转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。 -
T-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一入口点,所以 $Sub$$main 函数只需调用rtthread_startup() 函数即可;
-
在 components.c 的代码中找到rtthread_startup() 函数,我们看到 RT-Thread 的启动流程如下图所示:
-
1 /* components.c 中定义的这段代码*/ 2 /* re-define main function */ 3 int $Sub$$main(void) 4 { 5 rt_hw_interrupt_disable(); 6 rtthread_startup(); 7 return 0; 8 }
-
1 int rtthread_startup(void) 2 { 3 rt_hw_interrupt_disable(); 4 5 /* board level initialization 6 * NOTE: please initialize heap inside board initialization. 7 */ 8 rt_hw_board_init(); 9 10 /* show RT-Thread version */ 11 rt_show_version(); 12 13 /* timer system initialization */ 14 rt_system_timer_init(); 15 16 /* scheduler system initialization */ 17 rt_system_scheduler_init(); 18 19 #ifdef RT_USING_SIGNALS 20 /* signal system initialization */ 21 rt_system_signal_init(); 22 #endif 23 24 /* create init_thread */ 25 rt_application_init(); 26 27 /* timer thread initialization */ 28 rt_system_timer_thread_init(); 29 30 /* idle thread initialization */ 31 rt_thread_idle_init(); 32 33 /* start scheduler */ 34 rt_system_scheduler_start(); 35 36 /* never reach here */ 37 return 0; 38 }
1 /* the system main thread */ 2 void main_thread_entry(void *parameter) 3 { 4 extern int main(void); 5 extern int $Super$$main(void); 6 7 /* RT-Thread components initialization */ 8 rt_components_init(); 9 10 /* invoke system main function */ 11 #if defined(__CC_ARM) || defined(__CLANG_ARM) 12 $Super$$main(); /* for ARMCC. */ 13 #elif defined(__ICCARM__) || defined(__GNUC__) 14 main(); 15 #endif 16 } 17 18 void rt_application_init(void) 19 { 20 rt_thread_t tid; 21 22 #ifdef RT_USING_HEAP 23 tid = rt_thread_create("main", main_thread_entry, RT_NULL, 24 1024*100, 0, 20); 25 RT_ASSERT(tid != RT_NULL);
- rt_hw_board_init() 中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。
- main() 函数是 RT-Thread 的用户代码入口,用户可以在 main() 函数里添加自己的应用。
-
1 int main(void) 2 { 3 /* user app entry */ 4 return 0; 5 }
RT-Thread 程序内存分布
- 一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。编译器会将一个程序分类为好几个部分,分别存储在 MCU 不同的存储区。
- Code:代码段,存放程序的代码部分;
- RO-data:只读数据段,存放程序中定义的常量;
- RW-data:读写数据段,存放初始化为非 0 值的全局变量;
- ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;
- RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小;
- RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小;
- ROM Size 包含了 Code、RO Data 以及 RW Data,表示烧写程序所占用的 Flash 空间的大小;
-
程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如图下图中左图所示,是可执行映像文件烧录到 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 区域清零。
RT-Thread 自动初始化机制
- 自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。
-
1 int stm32_hw_usart_init(void) 2 { 3 struct stm32_uart *uart; 4 struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; 5 6 #ifdef RT_USING_UART1 7 uart = &uart1; 8 uart->UartHandle.Instance = USART1; 9 10 serial1.ops = &stm32_uart_ops; 11 serial1.config = config; 12 13 /* register UART1 device */ 14 rt_hw_serial_register(&serial1, 15 "uart1", 16 RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, 17 uart); 18 #endif /* RT_USING_UART1 */ 19 ...... 20 ...... 21 } 22 INIT_BOARD_EXPORT(stm32_hw_usart_init);
-
代码最后的 INIT_BOARD_EXPORT(stm32_hw_usart_init) 表示使用自动初始化功能,按照这种方式,stm32_hw_usart_init() 函数就会被系统自动调用
-
系统启动流程图中,有两个函数:rt_components_board_init() 与 rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数
- “board init functions” 为所有通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数。
- “pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn) 申明的初始化函数。
- “device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。
- “components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn) 申明的初始化函数。
- “enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。
- “application init functions” 为所有通过 INIT_APP_EXPORT(fn) 申明的初始化函数。
- rt_components_board_init() 函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数。
- rt_components_init() 函数会在操作系统运行起来之后创建的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。
- RT-Thread 的自动初始化机制使用了自定义 RTI 符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。
- 用来实现自动初始化功能的宏接口定义详细描述如下
- 初始化函数主动通过这些宏接口进行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到 RTI 符号段中,该符号段位于内存分布的 RO 段中,该 RTI符号段中的所有函数在系统初始化时会被自动调用。