当前位置: 首页 > news >正文

RT-Thread线程管理与调度

目录

1、基本概念

2、线程创建与删除

1、线程的组成

 2、线程栈

3、入口函数

3、创建和启动线程

4、线程优先级和Tick

1、线程优先级

2、时间片

5、线程状态

6、空闲线程

7、个人总结:


1、基本概念

对于整个单片机程序,我们称之为application,应用程序。

使用RT-Thread时,我们可以在application中创建多个线程(thread),有些文档也把线程称之为任务(task).

以日常生活为例,比如这个母亲要同时做两件事,

  • 喂饭:这是一个线程
  • 回消息:这是另一个线程

这可以引入很多概念:

线程状态(state):

当前正在喂饭,它是一个runnning状态,另一个回消息的线程就是not running状态

not running状态可以细分为:

init:初始状态

ready:就绪状态

suspended:挂起状态,等待某些资源暂时不能运行

close:关闭状态,线程退出

优先级(priority):

我生活工作兼顾:喂饭和回消息优先级一样,轮流做

我忙里偷闲,还有空闲线程,休息一下

厨房着火,什么都别说,先灭火,优先级更高

栈(Stack)

喂小孩时,我要记得上一口喂了米饭,这口要喂青菜了

回消息时,我要记得刚刚聊的是啥

做不同的线程,这些细节不一样

对于人来收,当然是记在脑子里

对于程序来说,是记在栈里

每个线程有自己的栈

事件驱动

孩子吃饭太慢,先休息一会,等他咽下去了,等他提醒我,再喂下一口

2、线程创建与删除

1、线程的组成

在RT-Thread中,线程是RT-Thread中最基本的调度单位,使用rt_thread结构体表示线程。

rt_thread描述了一个线程的运行环境,也描述了这个线程所处的优先级

系统中总共有两种线程,分别是系统线程和用户线程

  • 系统线程由RT-Thread内核创建
  • 用户线程由用户应用程序创建

这两类线程都会从内核对象容器中分配线程对象,如下图所示:

每个线程由三部分组成:线程控制块,线程栈和入口函数

 线程控制块

线程控制块由结构体rt_thread表示,线程控制块是操作系统用于管理线程的一个数据结构

它存放一些现成的信息,例如线程优先级,线程名称,线程状态等,也包括线程与线程之间连接用的链表结构,线程等待事件集合等

它在rtdef.h中定义:

 2、线程栈

在裸机系统中,涉及局部变量、子函数调用或中断发生,就需要用到栈,

在RTOS系统中,每个线程运行时,也是普通函数调用,也涉及局部变量、子函数调用、中断,也要用到栈。

但不同于裸机系统,RTOS存在多个线程,每个线程互不干扰的,因此需要为每个线程都分配独立的栈空间,这就是线程栈。

可以使用两种方法提供线程栈:静态分配和动态分配。栈的大小通常由用户定义,如下使用全局数组定义一个静态栈,大小为512字节。

rt_uint32_t test_stack[512]

对于资源较大的MCU,可以适当的设置较大的线程栈。

也可以在初始化时设置为较大的栈,比如1K或者2K,在进入系统后,通过终端的list_thread命令可以查看当前线程的栈的最大使用率,如果使用率超过了70%,将线程的栈在设置大一点,如果小于70%,将线程栈设置小一点。

3、入口函数

入口函数是线程要运行的函数,由用户自行设计。

可分为无限循环模式和顺序执行模式

void thread_entry(void *parameter)
{
    while(1)
    {
        /*等待事件发生*/
        /*对事件进行服务,进行处理*/
    }


}

使用这种模式时,需要注意,一个实时操作系统,不应该让一个线程一直处于最高优先级占用CPU,让其他线程得不到执行。

因此在这种模式下,需要调用延时函数或者主动挂起。这种无限循环模式设计的目的是让这个线程一直循环调度运行,而不结束。

顺序执行模式

static void thread_entry(void *parameter)
{
    /*处理事务1*/
    ...
    /*处理事务2*/
    ...
    /*处理事务3*/
}

使用这种模式时线程不会一直循环,最后一定会执行完毕。

执行完毕之后,线程将被系统自动删除。

以上就是线程的三要素:线程控制块,线程栈,入口函数;

线程的五种状态:初始态,就绪态,运行态,挂起态,关闭态。

线程的三种基本形式:单次执行,周期执行,资源驱动(时间也是资源的一种)

3、创建和启动线程

RT-Thread提供两种线程的创建方式:

  • 静态创建:使用rt_thread_init(void *parameter)
  • 动态创建:使用rt_thread_creare(void *paremeter)

这两种方式的区别就是分配线程控制块和线程栈的分配方式是静态的还是动态的,动态线程是系统自动从动态内存堆上分配栈空间与线程句柄,静态线程是由用户分配栈空间和线程句柄。

静态线程初始化函数如下:

 参数说明

参数描述
thread

线程句柄,指向线程控制块内存地址,由用户传入

name线程名字,由rtconfig.h中定义的RT_NAME_MAX宏指定最大长度
entry线程入口函数
parameter线程入口函数参数
stack_start线程栈起始地址
stack_size线程栈大小,单位是字节
priority线程优先级,由rtconfig.h中定义的RT_THREAD_PRIORITY_MAX宏指定优先级范围,假设支持的是256级优先级,范围是0-255,数值越小,优先级越高,0代表最高优先级
tick线程的时间片大小,时间片(Tick)是操作系统的时钟节拍,当系统中存在优先级相同的线程时,时间片大小指定线程一次调度能够运行的最大时间长度。当在时间片运行结束之后,运行另外的同优先级线程。
返回值

成功:RT_EOK

失败:RT_ERROR

动态创建函数如下:

参数说明:

参数描述
name线程名字,在rtconfig.h中定义的RT_NAME_MAX宏定义指定最大长度
entry线程入口函数
parameter线程入口函数参数
stack_size线程栈大小,单位是字节 
priority

线程优先级,由rtconofig.h中定义的RT_THREAD_PRIORITY_MAX宏指定优先级范围,假设支

持的是256级优先级,那么范围是0-255,数值越小,优先级越高

tick

线程的的时间片大小,时间片(Tick)是操作系统的时钟节拍,当系统中存在

相同优先级的线程时,时间片大小指定线程一次调度能够运行的最大时间长度

当前线程的时间片运行结束后,运行另外的同优先级线程

返回值

成功:thread,线程句柄

失败:RT_NULL

创建线程后,还需要启动线程,才能让线程运行起来。

启动线程函数如下:

rt_err_t rt_thread_startup(rt_thread_t thread)

参数说明:

参数描述
thread线程句柄
返回值

成功:RT_EOK

失败:-RT_ERROR

 示例1:创建线程

使用静态创建和动态创建两种方法分别创建两个线程

线程1的代码:

/*线程1的入口函数*/
static void thread1_entry(void *parameter)
{

    const char *thread_name = "Thread1_run\r\n";
    volatile rt_uint32_t cnt = 0;
    

    /*线程1*/
    while(1)
    {
        /*打印线程1的信息*/
        rt_kprintf(thread_name);

        /*延迟一会(比较简单粗暴)*/
        for(cnt=0;cnt<100000;cnt++)
        {

        }
    }

}

线程2的代码:
 

/*线程2入口函数*/
static void thread_entry(void *parametr)
{
    const char *thread_name = "Thread2 run";
    volatile rt_uint32_t cnt = 0;
    
    /*线程2*/

    while(1)
    {
        /*打印线程2的信息*/
        rt_kprintf(thread_name);

    }

    /*延迟一会,简单粗暴*/
    for(cnt = 0;cnt<100000;cnt++)
    {
    
    }

}

 静态创建线程:

rt_thread_init(&thread1,            //线程句柄
                "thread1"            //线程名字
                thread1_entry,        //入口函数
                RT_NULL,               //入口函数参数
                &thread1_stack[0],    //线程栈起始地址
                sizeof(thread1_stack),    //栈大小
                THREAD_PRIORITY,        //线程优先级
                THREAD_TIMESLICE        //线程时间片大小

                );

rt_thread_startup(&thread1);

动态创建线程:

thread2 = rt_thread_create("thread2",        //线程名字
                            thread2_entry,        //入口函数
                            RT_NULL,                //入口函数参数
                            THREAD__STACK_SIZE,    //栈大小
                            THREAD_PRIORITY,        //线程优先级
                            THREAD_TIMESLICE        //线程时间片
                            )

4、线程优先级和Tick

1、线程优先级

RT-Thread的线程优先级是指线程被调度的优先程度

每个线程都有优先级,线程的重要性越高,优先级应该设置的越高,被调度的可能才会更大。

由rtconfig.h中定义的RT_THREAD_PRIORITY宏指定优先级范围,RT-Thread最大支持256级优先级,数值越小的优先级越高,0为最高优先级。

在学习调度方法之前,只要粗略的知道,RT-Thread会确保优先级最高,可运行的线程,马上就能执行。

RT-Thread会确保优先级最高的,可运行的线程马上就能执行。

对于相同优先级的,可运行的线程,轮流执行。

举个例子,

厨房着火了,当然优先灭火

喂饭,回复信息同样重要,轮流做

2、时间片

RT-Thread中也有心跳,它使定时器产生固定间隔的中断,这叫Tick,滴答,比如每1ms发生一次时钟中断。

5、线程状态

简单分为运行态和非运行态,具体的非运行态又可以细分,

初始态(RT_THREAD_INIT):创建线程的时候会将线程的状态设置为初始态

就绪态(RT_THREAD_READY):该线程就在就绪列表中,就绪的线程已经具备执行的能力,只等待CPU

运行态(RT_THREAD_RUNNING):该线程正在运行,此时它正占用CPU

挂起态(RT_THREAD_SUSPEND):如果线程当前正在等待某个时序或外部中断,我们就说这个线程处于挂起状态,该线程不在就绪列表中,包含线程被挂起、线程被延时,线程正在等待信号量、读写队列或者等待读写事件

关闭态(RT_THREAD_CL):该线程运行结束,等待系统回收资源。

线程状态迁移

RT_Thread系统中的每一个线程都有多种运行状态,他们之间的转换关系如下图所示,从运行态变成阻塞态、或者从阻塞态变成就绪态,这些线程状态是怎么迁移的呢?

6、空闲线程

空闲线程是系统中一个比较特殊的线程,它具有最低的优先级,当系统中无其他线程可运行时,调度器将调度到空闲线程。空闲线程通常是一个死循环,永远不挂起。

RT_Thread实时操作系统为空闲线程提供了一个钩子函数(钩子函数:用户提供的一段代码,在系统运行的某一路径上设置一个钩子,当系统经过这个位置时,转而执行这个钩子函数,然后再返回到它的正常路径上) ,可以让系统在空闲的时候执行一些特定的任务,例如系统运行指示灯闪烁,电源管理等,除了调用钩子函数,RT_Thread也把线程清理(rt_thread_cleanup)函数,真正的线程删除动作放到了空闲线程中(在删除线程时,仅改变线程的状态为关闭状态不在参与系统调度)。

调度器相关接口

调度器初始化

在系统启动时需要执行调度器的初始化,以初始化系统调度器用到的一些全局变量。调度器初始化可以调用下面的接口:

void rt_system_schduler_init(void)

启动调度器

void rt_system_scheduler(void)

执行调度

void rt_schedule(void)

设置调度器钩子函数

在整个系统的运行时,系统都处于线程运行、中断触发-响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个响应的钩子函数。在系统线程切换时,这个钩子函数将被调用

void rt_scheduler_sethook(void (*hook)(struct rt_thread* from,struct rt_thread* to))

7、个人总结:

创建线程主要可以看成四个步骤:

1、动态分配或者静态分配线程控制块

2、动态分配或者静态分配线程栈

3、创建入口函数

4、构造栈内容(线程控制块结构体的成员)

上述函数的作用:调整sp和虚构栈内容

 线程调度

由抢占式优先级和时间片轮转调度算法进行调度

RT-Thread启动流程RT-Thread启动流程_~Old的博客-CSDN博客

相关文章:

  • Docker 化你的 Go 应用程序
  • 3.ROS2笔记-ROS2开发环境配置
  • 手写堆(Heap)
  • java-php-python-ssm白樵校园物品交易系统计算机毕业设计
  • C语言详解《位段+联合体+枚举》
  • 2.ROS2笔记-ROS2命令行操作
  • 【Proteus】:入门学习
  • SpringBoot启动流程简析
  • 如何在网站上安装 WordPress
  • java毕业设计社区健康管理系统源码+lw文档+mybatis+系统+mysql数据库+调试
  • 17、Java——汽车租赁系统
  • SpringSecurity系列 - 15 SpringSecurity 记住我 RememberMe
  • 2022出海东南亚:越南电商市场现状及网红营销特点
  • 测试用例设计专栏
  • Intel汇编-间接寻址
  • [PHP内核探索]PHP中的哈希表
  • JS 中的深拷贝与浅拷贝
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 4个实用的微服务测试策略
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • AngularJS指令开发(1)——参数详解
  • CentOS6 编译安装 redis-3.2.3
  • Flannel解读
  • Fundebug计费标准解释:事件数是如何定义的?
  • JS基础之数据类型、对象、原型、原型链、继承
  • MaxCompute访问TableStore(OTS) 数据
  • PHP 程序员也能做的 Java 开发 30分钟使用 netty 轻松打造一个高性能 websocket 服务...
  • Python - 闭包Closure
  • Redis字符串类型内部编码剖析
  • spring boot 整合mybatis 无法输出sql的问题
  • Spring Cloud Feign的两种使用姿势
  • tensorflow学习笔记3——MNIST应用篇
  • Vue.js-Day01
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 百度地图API标注+时间轴组件
  • 闭包,sync使用细节
  • 使用SAX解析XML
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 用Node EJS写一个爬虫脚本每天定时给心爱的她发一封暖心邮件
  • 在weex里面使用chart图表
  • 栈实现走出迷宫(C++)
  • AI算硅基生命吗,为什么?
  • 容器镜像
  • ​520就是要宠粉,你的心头书我买单
  • $ git push -u origin master 推送到远程库出错
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (附源码)ssm跨平台教学系统 毕业设计 280843
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (七)理解angular中的module和injector,即依赖注入
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (一)WLAN定义和基本架构转