RT-Thread简介–>>>下载资料来源于RT-Thread文档中心
具体包括以下部分:
- 内核层:RT-Thread 内核,是 RT-Thread的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱、消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件
/ 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。 - 组件与服务层:组件是基于 RT-Thread 内核之上的上层软件,例如虚拟文件系统、FinSH 命令行界面、
- 网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。
内核
1.内核的组成部分、系统如何启动、内存分布情况以及内核配置方法
- 内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。
- 实时内核的实现包括:对象管理、线程管理及调度器、线程间通信管理、时钟管理及内存管理等等,内核最小的资源占用情况是 3KB ROM,1.2KB RAM。
线程调度:系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。
时钟管理:时钟节拍是 RT-Thread 操作系统中最小的时钟单位。通常使用定时器定时回调函数(即超时函数),完成定时服务。
线程间同步:RT-Thread 采用信号量、互斥量与事件集实现线程间同步。
线程间通信:支持邮箱和消息队列等通信机制。邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。邮箱效率较消息队列更为高效。 内存管理:支持静态内存池管理及动态内存堆管理。 I/O 设备管理:将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。当设备事件触发时,由驱动程序通知给上层的应用程序。
2.RT-Thread 启动流程
rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。
MDK 的扩展功能 $Sub$$ 和 $Super$$
startup_stm32f103xe.s 开始-> components.c
根据宏跳转到对应函数,如MDK: int $Sub$$main(void) ->int rtthread_startup(void)
rtthread_startup:关闭中断 ->板级初始化 -> 打印版本信息-> 定时器初始化->调度器初始化 -> 信号初始化->创建初始化线程(main线程) -> 定时器线程初始化 -> 空闲线程初始化->启动调度器。
启动调度器之前,系统所创建的线程在执行 rt_thread_startup() 后并不会立马运行,它们会处于就绪状态等待系统调度
3.程序内存分布
MCU 包含的存储空间有:片内 Flash 与片内 RAM。Program Size 包含以下几个部分 1)Code:代码段,存放程序的代码部分; 2)RO-data:只读数据段,存放程序中定义的常量; 3)RW-data:读写数据段,存放初始化为非 0 值的全局变量; 4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量 . map 的文件,说明了各个函数占用的尺寸和地址 1)RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小; 2)RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小; 3)ROM Size 包含了 Code、RO-data 以及 RW-data,表示烧写程序所占用的 Flash 空间的大小;
烧写过程
STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。动态内存堆为未使用的 RAM 空间,应用程序申请和释放的内存块都来自该空间。 一些全局变量则是存放于 RW 段和 ZI 段中,RW 段存放的是具有初始值的全局变量(而常量形式的全局变量则放置在 RO 段中,是只读属性的),ZI 段存放的系统未初始化的全局变量
RT-Thread 自动初始化机制–>此处应更深入了解何处被自动执行
自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行声明,就会在系统启动过程中被执行。 在哪里被调用的呢? 1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动 2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数 3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备 4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP 5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统 6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用
4.内核对象模型
RT-Thread 内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。内核对象分为两类:静态内核对象和动态内核对象,静态内核对象通常放在 RW 段和 ZI 段中,在系统启动后在程序中初始化;动态内核对象则是从内存堆中创建的,而后手工做初始化。如:静态线程对象thread1 ,线程控制块 thread1 与栈空间 thread1_stack 都是编译时决定的。thread2 是一个动态线程对象运行中用到的空间都是动态分配的 静态对象会占用 RAM 空间,不依赖于内存堆管理器,内存分配时间确定。动态对象则依赖于内存堆管理器,运行时申请 RAM 空间,当对象被删除后,占用的 RAM 空间被释放。
内核对象管理架构
RT-Thread 采用内核对象管理系统来访问 / 管理所有内核对象。不依赖于具体的内存分配方式。 RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上 RT-Thread 中各类内核对象的派生和继承关系。对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性)。每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。在对象管理模块中,定义了通用的数据结构,用来保存各种对象的共同属性,各种具体对象只需要在此基础上加上自己的某些特别的属性,就可以清楚的表示自己的特征。由对象控制块 rt_object 派生出来的有:线程对象、内存池对象、定时器对象、设备对象和 IPC 对象(IPC:进程间通信。此处作用是进行线程间同步与通信);由 IPC 对象派生出信号量、互斥量、事件、邮箱与消息队列、信号等对象。
对象控制块
struct rt_object
{
char name[RT_NAME_MAX];
rt_uint8_t type;
rt_uint8_t flag;
rt_list_t list;
};
type ->>>>> enum rt_object_class_type
如果是静态对象,那么对象类型的最高位将是 1,否则就是动态对象,系统最多能够容纳的对象类别数目是 127 个。
内核对象管理方式
struct rt_object_information
{
enum rt_object_class_type type;
rt_list_t object_list;
rt_size_t object_size;
};
一类对象由一个 rt_object_information 结构体来管理,每一个这类对象的具体实例都通过链表的形式挂接在 object_list 上。而这一类对象的内存块尺寸由 object_size 标识出来,每一类对象的具体实例,他们占有的内存块大小都是相同的
初始化(静态)对象
void rt_object_init(struct rt_object* object ,
enum rt_object_class_type type ,
const char* name)
脱离(静态)对象
调用该接口,可使得一个静态内核对象从内核对象容器中脱离出来,即从内核对象容器链表上删除相应的对象节点。对象脱离后,对象占用的内存并不会被释放。
void rt_object_detach(rt_object_t object);
分配(动态)对象
动态的对象则可以在需要时申请,不需要时释放出内存空间给其他应用使用。
rt_object_t rt_object_allocate(enum rt_object_class_type type ,
const char* name)
删除(动态)对象
void rt_object_delete(rt_object_t object);
辨别(静态/动态)对象
rt_err_t rt_object_is_systemobject(rt_object_t object);
RT-Thread 内核配置示例
通过修改工程目录下的 rtconfig.h 文件来进行,在实际应用中,系统配置文件 rtconfig.h 是由配置工具自动生成的,无需手动更改。
-1.RT-Thread 内核部分:
#define RT_NAME_MAX 8
#define RT_ALIGN_SIZE 4
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 100
#define RT_USING_OVERFLOW_CHECK
#define RT_DEBUG
#define RT_DEBUG_INIT 0
#define RT_DEBUG_THREAD 0
#define RT_USING_HOOK
#define IDLE_THREAD_STACK_SIZE 256
-2.线程间同步与通信部分
#define RT_USING_SEMAPHORE
#define RT_USING_MUTEX
#define RT_USING_EVENT
#define RT_USING_MAILBOX
#define RT_USING_MESSAGEQUEUE
#define RT_USING_SIGNALS
-3.内存管理部分
#define RT_USING_MEMPOOL
#define RT_USING_MEMHEAP
#define RT_USING_SMALL_MEM
#define RT_USING_HEAP
-4.内核设备对象
#define RT_USING_DEVICE
#define RT_USING_CONSOLE
#define RT_CONSOLEBUF_SIZE 128
#define RT_CONSOLE_DEVICE_NAME "uart1"
-5.自动初始化方式
#define RT_USING_COMPONENTS_INIT
#define RT_USING_USER_MAIN
#define RT_MAIN_THREAD_STACK_SIZE 2048
-6.FinSH
MSH:指令描述功能
#define RT_USING_FINSH
#define FINSH_THREAD_NAME "tshell"
#define FINSH_USING_HISTORY
#define FINSH_HISTORY_LINES 5
#define FINSH_USING_SYMTAB
#define FINSH_THREAD_PRIORITY 20
#define FINSH_THREAD_STACK_SIZE 4096
#define FINSH_CMD_SIZE 80
#define FINSH_USING_MSH
#define FINSH_USING_MSH_DEFAULT
#define FINSH_USING_MSH_ONLY
-7.关于 MCU
#define STM32F103ZE
#define RT_HSE_VALUE 8000000
#define RT_USING_UART1
-0.常见宏定义说明
#define rt_inline static __inline
#define RT_USED __attribute__((used))
#define RT_UNUSED __attribute__((unused))
#define RT_WEAK __weak
#define ALIGN(n) __attribute__((aligned(n)))
#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
5.线程管理
例如让嵌入式系统执行这样的任务,系统通过传感器采集数据,并通过显示屏将数据显示出来,在多线程实时系统中,可以将这个任务分解成两个子任务,如下图所示,一个子任务不间断地读取传感器数据,并将数据写到共享内存中,另外一个子任务周期性的从共享内存中读取数据,并将传感器数据输出到显示屏上。 线程是实现任务的载体,它是 RT-Thread 中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级. 线程运行时,它会认为自己是以独占 CPU 的方式在运行,线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。
线程管理的功能特点
线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程 每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。 RT-Thread 的线程调度器是抢占式的,当一个运行着的线程使一个比它优先级高的线程满足运行条件,高优先级的线程立刻得到了 CPU 的使用权。 如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。 当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。
线程的工作机制
-1.线程控制块
线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等
struct rt_thread
{
char name[RT_NAME_MAX];
rt_uint8_t type;
rt_uint8_t flags;
rt_list_t list;
rt_list_t tlist;
void *sp;
void *entry;
void *parameter;
void *stack_addr;
rt_uint32_t stack_size;
rt_err_t error;
rt_uint8_t stat;
rt_uint8_t current_priority;
rt_uint8_t init_priority;
rt_uint32_t number_mask;
rt_ubase_t init_tick;
rt_ubase_t remaining_tick;
struct rt_timer thread_timer;
void (*cleanup)(struct rt_thread *tid);
rt_uint32_t user_data;
};
|