内核启动以及线程的概念
运行环境:Vscode+env
开发板:星火一号
一、目录结构
先分析一下工程的目录结构
pycache | 存放Python 编译器生成的字节码文件 |
---|---|
.vscode | 存放vscode配置文件 |
applications | 应用层 |
board | 板级初始化 |
build | 编译的中间文件 |
figures | 存放图像 |
libraries | 存放主控的库函数 |
rt-thread | rt-thread源码 |
二、env借助Kconfig剪裁配置
C语言项目的裁剪配置本质上通过条件编译和宏的展开来实现的,RT-Thread借助Kconfig这套机制更方便的实现了这一功能。
Kconfig文件在源码中呈现树形结构,需要在工程的根目录下存在一份顶层Kconfig文件,顶层Kconfig文件在文件中通过source语句显示地调用各子目录下的Kconfig文件。Env在根目录下执行menuconfig命令后会递归解析各级Kconfig文件,然后提供如下配置界面,完成相应的配置后并保存,根目录下会存在一份.config文件保存当前选择的配置项,并将.config文件转为RT-Thread的系统配置文件rtconfig.h。
想了解Kconfig的语法可以查看rt-thread的文档中心。
三、env使用Scons构建编译
SCons 是一个软件构建工具,类似于 Make,但使用 Python 脚本作为其配置语言。SConscript
文件用于定义特定目录下的构建指令,而 SConstruct
文件则是顶层构建脚本。
如果我们想在工程中新建一个文件夹存放自己的文件。就可以写一个SConscript
脚本文件。
from building import *
import os
cwd = GetCurrentDir()
CPPPATH = [cwd]
src = ['hello.c'] #补上自己的源文件
group = DefineGroup('Hello_Test', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
这时候我们重新在env里编译scons
,生成vscode的json,就可以看到我们在main
函数里面可以正常调用我们所创建的文件夹中的hello.c中的函数。
四、rt-thread启动流程
不知道大家有没有这样的疑惑,一开始拿到rt-thread工程的时候,反正main函数跟普通的裸机开发也没有很大的区别,那么他是怎么实现调度以及终端的初识化呢?
为了在进入 main() 之前完成 RT-Thread 系统功能初始化,以gcc编译器为例子,我们执行了entry
,最后才会创建main的线程并启动。这边可以看启动文件的汇编文件。
以星号一号的板子为例,启动文件的路径.\libraries\STM32F4xx_HAL\CMSIS\Device\ST\STM32F4xx\Source\
Templates\gcc\startup_stm32f407xx.s
可以看到先执行的函数是entry
.
不同编译器先执行的文件不同,可以查看上图。
不管是什么编译器,最终还是先调用启动函数 rtthread_startup()
,最后进入用户入口函数 main()
。
现在我们来看一下rtthread_startup()
干了什么事情。
路径rt-thread\src\components.c
具体启动的过程大家可以进行断点调试自行体会。
启动调度器,之后系统就会选在优先级最高的线程进行执行。
1.rt_hw_local_irq_disable
汇编操作,关闭全局中断,避免收到其他因素干扰。
路径:rt-thread\libcpu\arm\cortex-m3\context_gcc.S
2.rt_hw_board_init
板极初始化
自动初始化机制,INIT_BOARD_EXPORT(fn)
:非常早期的初始化。
将函数插入到特殊的段中,最后由rt_components_board_init
寻找并执行函数。
路径:.\rt-thread\src\components.c
3.rt_show_version
显示rt-thread版本
路径:.\rt-thread\src\kservice.c
4.rt_system_timer_init
系统定时器初始化
路径:.\rt-thread\src\timer.c
5.rt_system_scheduler_init
调度器初始化
路径:.\rt-thread\src\scheduler_up.c
6.rt_application_init
创建一个main线程
路径:.\rt-thread\src\components.c
7.rt_system_timer_thread_init
定时器线程初始化
路径:.\rt-thread\src\timer.c
8.rt_system_scheduler_start
路径:.\rt-thread\src\scheduler_up.c
五、rt-thread线程
1.线程的定义
- 进程是一个独立的运行实体,包含自己的代码、数据、堆栈和其他资源。
- 每个进程都有自己的地址空间,相互隔离,保证安全性和稳定性。
rt-thrad标准版本只有线程的概念,没有进程的概念。
rt-thrad是抢占式调度的。
2.线程控制块
路径:.\rt-thread\include\rtdef.h
a.线程栈
RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
根据芯片的架构保存相应的寄存器。线程切换的过程就是保存现场(保存相对应的寄存器),线程的恢复就是还远相对应的寄存器。
路径:.\rt-thread\libcpu\arm\cortex-m33\cpuport.c
可以看到在创建线程的时候就是初始化了相对应的寄存器。
b.线程的状态
状态 | 描述 |
---|---|
初始状态 | 当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT |
就绪状态 | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY |
运行状态 | 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING |
挂起状态 | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND |
关闭状态 | 当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE |
c.线程的优先级
RT-Thread 线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,线程越重要,赋予的优先级就应越高,线程被调度的可能才会越大。
注意:rt-thrad线程的优先级是越小越高,与FreeRTOS相反
d.时间片
时间片轮转:时间片只对优先级一致的线程起作用,当线程的时间片用完事,就会让出cpu,使同优先级的线程运行
e.线程的入口函数
线程控制块中的 entry 是线程的入口函数,它是线程实现预期功能的函数。
void thread_entry(void* paramenter)
{
while (1)
{
/* 等待事件的发生 */
/* 对事件进行服务、进行处理 */
}
}
f.线程错误码
g.线程的切换
RT-Thread 提供一系列的操作系统调用接口,使得线程的状态在这五个状态之间来回切换。几种状态间的转换关系如下图所示:
3.线程的创建
rt-thrad创建线程的方式有两种
- 静态创建
static char thread1_addr[256];
static struct rt_thread thread1;
void thread1_entry(void * paramenter)
{
while(1)
{
rt_kprintf("this is thread1\r\n");
rt_thread_mdelay(500);
}
}
int main(void)
{
rt_err_t ret= rt_thread_init(&thread1,"thread1",thread1_entry,RT_NULL,thread1_addr,256,10,10);
if(ret == RT_EOK)
{
rt_thread_startup(&thread1);
}
while (1)
{
rt_thread_mdelay(500);
}
}
- 动态创建
void thread2_entry(void * paramenter)
{
while(1)
{
rt_kprintf("this is thread2\r\n");
rt_thread_mdelay(500);
}
}
int main(void)
{
rt_thread_t thread2;
thread2 = rt_thread_create("thread2",thread2_entry,RT_NULL,512,10,10);
if(thread2 != RT_NULL)
{
rt_thread_startup(thread2);
}
while (1)
{
rt_thread_mdelay(500);
}
}
具体线程的创建以及使用可以参考官网文档