【RT-Thread】内核启动以及线程demo

内核启动以及线程的概念

运行环境:Vscode+env

开发板:星火一号

一、目录结构

先分析一下工程的目录结构

image-20240723190958235

pycache存放Python 编译器生成的字节码文件
.vscode存放vscode配置文件
applications应用层
board板级初始化
build编译的中间文件
figures存放图像
libraries存放主控的库函数
rt-threadrt-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

image-20240723214203291

可以看到先执行的函数是entry.

启动流程

不同编译器先执行的文件不同,可以查看上图。

不管是什么编译器,最终还是先调用启动函数 rtthread_startup() ,最后进入用户入口函数 main()

现在我们来看一下rtthread_startup()干了什么事情。

路径rt-thread\src\components.c

image-20240723214643021

image-20240723215243678

具体启动的过程大家可以进行断点调试自行体会。

启动调度器,之后系统就会选在优先级最高的线程进行执行。

1.rt_hw_local_irq_disable

汇编操作,关闭全局中断,避免收到其他因素干扰。

路径:rt-thread\libcpu\arm\cortex-m3\context_gcc.S

image-20240723223258147

2.rt_hw_board_init

板极初始化

image-20240723223735780

自动初始化机制,INIT_BOARD_EXPORT(fn):非常早期的初始化。

将函数插入到特殊的段中,最后由rt_components_board_init寻找并执行函数。

路径:.\rt-thread\src\components.c

image-20240723223808129

3.rt_show_version

显示rt-thread版本

路径:.\rt-thread\src\kservice.c

image-20240723224211409

4.rt_system_timer_init

系统定时器初始化

路径:.\rt-thread\src\timer.c

image-20240723224355205

5.rt_system_scheduler_init

调度器初始化

路径:.\rt-thread\src\scheduler_up.c

image-20240723224620807

6.rt_application_init

创建一个main线程

路径:.\rt-thread\src\components.c

image-20240723224713720

7.rt_system_timer_thread_init

定时器线程初始化

路径:.\rt-thread\src\timer.c

image-20240723224832833

8.rt_system_scheduler_start

路径:.\rt-thread\src\scheduler_up.c

image-20240723222832531

五、rt-thread线程

1.线程的定义

  • 进程是一个独立的运行实体,包含自己的代码、数据、堆栈和其他资源。
  • 每个进程都有自己的地址空间,相互隔离,保证安全性和稳定性。

rt-thrad标准版本只有线程的概念,没有进程的概念。

rt-thrad是抢占式调度的。

2.线程控制块

路径:.\rt-thread\include\rtdef.h

image-20240723230728383

a.线程栈

RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。

根据芯片的架构保存相应的寄存器。线程切换的过程就是保存现场(保存相对应的寄存器),线程的恢复就是还远相对应的寄存器。

路径:.\rt-thread\libcpu\arm\cortex-m33\cpuport.c

image-20240723231347162

可以看到在创建线程的时候就是初始化了相对应的寄存器。

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.线程错误码

image-20240723232112552

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);

    }
}

具体线程的创建以及使用可以参考官网文档

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值