RT-Thread快速入门-线程管理

news2024/11/23 6:45:25

在 RT-Thread 中,最基本的调度单位是线程,其他 RTOS 也叫任务。如果学习过或者了解过 RTOS,任务这种叫法是最为熟知的。

本篇文章来学习一下 RT-Thread 线程方面的内容。对于初学者来说,转换一下思维,建立多任务(线程)的编程思想。

 

引言

对于裸机编程,整个软件系统只有一个线程(任务)在执行,实现方式是通过一个大循环完成的。应用程序是一个无限循环,循环中调用各个功能模块的函数,完成相应的操作。

RTOS 是一个多任务系统,可以把总体功能划分为多个小模块,每个小模块独立运行完成某项功能,即将任务分解。这样使得复杂项目的实现变得简单了。

关于为什么要用 RTOS,或者说 RTOS 的优点,网上资料很多,在此不再赘述。

线程基础

1. 线程调度

对于一款 RTOS 来说,最核心的部分就是线程(任务)调度器。调度器的作用是根据调度算法来决定当前需要执行的线程。

RT-Thread 的线程调度器是抢占式的,基于优先级对线程进行调度。每个线程均具有一个优先级,调度器的主要工作是,从就绪线程列表中查找最高优先级线程,然后将 CPU 的使用权分配给它。

"可抢占"意味着,如果高优先级线程就绪或者满足运行条件,RT-Thread 马上将 CPU 的控制权交给高优先级线程。

2. 线程优先级

RT-Thread 线程的优先级表示线程被调度的优先程度。每个线程都具有优先级,对于重要的线程,应该赋予其高优先级,这样才能保证线程被优先调度。

RT-Thread 最大支持 256个优先级(0~255),数值越小的线程优先级越高。0 为最高优先级。最低优先级默认分配给空闲线程,用户一般不用。

可以根据实际情况配置优先级个数,对于 ARM Cortex-M 系列,普遍采用 32 个优先级(0~31)。

3. 时间片

RT-Thread 允许多个线程具有相同的优先级,相同优先级的线程之间采用时间片轮转的方式进行调度。创建线程的时候,可以配置线程的时间片参数。时间片仅对优先级相同的就绪线程有效。

时间片的作用是约束线程单次运行的时长,其单位是系统时钟节拍(OS Tick)。

4. 线程栈

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

线程栈还用来存放函数中的局部变量。当一个函数调用另外一个函数时,函数中的局部变量会暂时存放在栈中。

线程栈的增长方向由芯片架构决定的:由高地址向低地址增长、由低地址向高地址增长。这两种增长方式 RT-Thread  均支持。对于 ARM Cortex-M 架构,线程栈构造如下图所示:

图片

 

5. 线程的入口函数

入口函数是线程实现预期功能的函数。线程的入口函数由用户设计,一般有以下两种形式:

  • 无线循环模式

这种线程会一直被系统循环调度,执行任务,不会删除。

void thread_entry(void *parameter)
{
    while(1)
    {
      /* 线程处理 */
    }
}
  • 顺序执行或有限次循环模式

这类线程不会循环或者不会永久循环,执行完毕之后,线程将被系统自动删除。

void thread_entry(void *parameter)
{
    /* 处理任务 */
    ...
}

线程状态

对于单 CPU 来说,系统运行过程中,同一时刻只有一个线程在处理器运行。在运行过程中,线程有多种不同的运行状态:

  • 初始状态,线程刚创建还未开始运行时处于的状态,此状态下,线程不参与调度。

  • 就绪状态,线程具备运行条件的状态,等待被调度器调度执行。

  • 运行状态,线程正在运行。

  • 挂起状态,也称为阻塞状态。由于资源不可用或线程主动延时一段时间,而进入的状态。线程不能执行。

  • 关闭状态。线程运行结束处于的状态。此时线程不参与调度。

下图是各个状态之间的转换图,通过此图可以对 RT-Thread 线程的运行状态有一个整体的认识。

图片

 

图中涉及到的系统调用函数,在后面的学习会进行详细讲解。此处进行简单的说明:

  • rt_thread_create/init() 创建或初始化一个线程,此线程处于初始状态。

  • rt_thread_startup() 函数使得初始化状态的线程进入到就绪状态。

  • rt_thread_delay(),rt_sem_take(), rt_mutex_take() 等函数使得运行状态的线程进入到挂起状态。

  • rt_thread_resume(), rt_sem_release() 等函数使得挂起状态的线程返回到就绪状态。

  • rt_thread_delete/detach()  函数将挂起状态的线程更改为关闭状态。

  • rt_thread_exit(),处于运行状态的线程,运行结束,在线程的最后部分调用此函数,将状态更改为关闭状态。

线程控制块

在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示。

线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等。

线程控制块也包含线程与线程之间连接用的链表结构,线程等待事件集合等。

详细定义如下,列出了关键结构成员,并加入了注释,了解即可:

struct rt_thread
{
  /* rt 对象 */
  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;  /* 用户数据 */
};
typedef struct rt_thread *rt_thread_t;

创建线程

RT-Thread 提供了先管理相关的系统函数:包含:创建 / 初始化线程、启动线程、运行线程、删除 / 脱离线程 等。

此处只讲解如何创建一个线程。

在 RT-Thread 中,要创建一个线程,并使得它能够被执行,需要两步:

  • 创建线程,此时一个新线程被创建,并处于初始状态。

  • 启动线程,此时线程由初始状态进入就绪状态,可以被调度器执行。

1. 创建线程

创建一个线程的函数 rt_thread_create() 定义如下:

rt_thread_t rt_thread_create(const char *name,
                             void (*entry)(void *parameter),
                             void       *parameter,
                             rt_uint32_t stack_size,
                             rt_uint8_t  priority,
                             rt_uint32_t tick)

调用这个函数时,系统会从动态内存堆中分配一个线程句柄(线程控制块),并按照参数中指定的栈大小从动态内存堆中分配相应的空间。

函数的各个参数解释如下:

参数描述
name线程的名称;线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分会被自动截掉
entry线程入口函数
parameter线程入口函数参数
stack_size线程栈大小,单位是字节
priority线程的优先级。优先级范围根据系统配置(rtconfig.h 中的RT_THREAD_PRIORITY_MAX 宏定义)
tick线程的时间片大小。单位是操作系统的时钟节拍

线程创建成功,返回线程的控制块指针,也可称为线程句柄。

创建失败,则返回 RT_NULL。

2. 线程启动

线程创建完成后,需要使其进入就绪状态,也就是启动线程。可以通过调用 rt_thread_startup() 函数来完成。其函数原型为:

rt_err_t rt_thread_startup(rt_thread_t thread)

调用此函数成功后,会将线程放到相应优先级队列中等待调度。如果新启动的线程优先级比当前优先级高,将立即切换到这个线程。

参数 thread ,线程句柄,即线程控制块的指针。

线程启动成功,返回 RT_EOK;启动失败,则返回 -RT_ERROR。

线程创建示例

此处用一个简单的例子,来演示一下 RT-Thread 线程创建。程序源码如下:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <rtthread.h>

#define THREAD_PRIORITY    25
#define THREAD_STACK_SIZE  512
#define THREAD_TIMESLICE    5

void thread_entry(void *parameter)
{
    rt_uint32_t count = 0;

    while(1)
    {
        /* 线程运行,打印计数 */
        rt_kprintf("thread run: %d\n", count ++);
        rt_thread_mdelay(500);
    }
}

int main(void)
{
    rt_thread_t tid = RT_NULL;

    /* 创建线程, 名称是 thread_test, 入口是 thread_entry*/
    tid = rt_thread_create("thread_test",
                            thread_entry, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);

    /* 线程创建成功,则启动线程 */
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
    }

    return 0;
}

编译运行,结果如下,线程创建成功,执行过程中,在打印计数信息:

图片

 

OK,今天先到这,下次继续。加油~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/783529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker部署Redis集群详解【主从复制 + 哨兵模式】

前言 注意&#xff1a;该文章不会讲解Redis集群搭建的原理&#xff0c;只讲述如何通过Docker容器快速部署搭建Redis主从 哨兵模式的集群 准备工作&#xff1a; 一台云服务器 or 本地虚拟机&#xff08;CentOS和Ubuntu都可以&#xff09;安装好Docker环境&#xff08;Docker、…

ROS框架——发布者功能包和订阅者功能包进行bool类型数据结构的topic通讯

ROS框架——发布者功能包和订阅者功能包进行bool类型数据结构的topic通讯 code review! 文章目录 ROS框架——发布者功能包和订阅者功能包进行bool类型数据结构的topic通讯零.同时运行两个功能包一.发布者功能包1.1.文件结构1.2.bool\_publisher\_node.cpp1.3.CMakeLists.txt…

SpringBoot实战(二十一)集成 TLog 日志

目录 一、简介二、Maven依赖三、启动类集成四、测试1.服务简介2.服务A代码DemoController.javaDemoFeignClient.java 3.服务B代码4.测试结果 补充一&#xff1a;自定义日志标签模板补充二&#xff1a;SpanId的生成规则补充三&#xff1a;业务标签1.打印入参2.指定连接符3.打印常…

【深度学习-卷积神经网络CNN-基础】

文章目录 发展历史卷积神经网络的应用领域卷积的原理和作用卷积和传统的神经网络的区别 卷积网络的整体架构输入层卷积层池化层全连接层卷积和池化叠加多层卷积可以处理什么类型的数据卷积的超参数卷积最大的优势 卷积的细节卷积的原理卷积的参数卷积的次数步长 卷积核尺寸 边缘…

成为UI设计高手:如何规划和创建UI设计组件库!

今天给大家分享一篇关于组件化设计的总结&#xff0c;希望可以带给大家更多设计思考。 什么是组件化 组件化化是构成界面的最基础元素和重复出现控件的集合体&#xff0c;也就是常说的组件库。通过对基础元素和控件的规范命名与排列组合&#xff0c;最终形成一个可快速调用与便…

每日一题——地下迷宫(迷宫问题II)

迷宫问题&#xff08;地下迷宫&#xff09;——II 题目链接 前言&#xff1a; 这题是在昨天迷宫问题——I的基础上进行的变形&#xff0c;因此&#xff0c;如果昨天的题目没看或者对迷宫问题不怎么了解&#xff0c;建议先看看昨天的解析。 迷宫问题——I源代码&#xff1a; …

安装redis,适配阿里云服务器,Liunx安装redis

下载redis以及编译安装 下载redis文件 wget http://download.redis.io/releases/redis-6.0.8.tar.gz #下载redis压缩文件 tar xzf redis-6.0.8.tar.gz #解压缩 cd redis-6.0.8 make 查看是否安装了gcc编译输入gcc --version如果没有…

AtcoderABC236场

A - chukodaiA - chukodai 题目大意 题目要求交换字符串S中第a个字符和第b个字符的位置&#xff0c;并输出结果。 思路分析 借用临时变量t&#xff0c;进行记录交换。 时间复杂度 O(∣S∣) 输出交换后的字符串的时间复杂度为O(∣S∣)&#xff0c;其中∣S∣表示字符串SS的…

autohotkey实战:窗口透明化

文章目录 实现方法代码讲解WinGet和WinSet 注 本文采用的是V1版本语法 实现方法 窗口透明不仅实用性强&#xff0c;关键是非常炫酷&#xff0c;如果用AHK实现一个实时调节窗口透明度的工具&#xff0c;那么就可以一边敲代码&#xff0c;一边透过半透明的IDE&#xff0c;愉快地…

vue 3.0 如何实现 文本框只能输入数字 避免文字和符号

<input v-model.number"form.payTime" :min"0" type"number" οninput"valuevalue.replace(/[^0-9.]/g,)" type"text" /> v-model 后面的.numer 一定要加上不然没效果 οninput"valuevalue.replace(/[^0-9.]/g…

Pdb蛋白质数据库网址!+30蛋白质数据库网站!

蛋白质数据库是指专门存储蛋白质相关信息的数据库。它们收集、整理和存储大量的蛋白质数据&#xff0c;包括蛋白质序列、结构、功能、互作关系、表达模式、疾病关联等信息。蛋白质数据库提供了对这些数据的检索、查询和分析功能&#xff0c;为科学研究人员、生物信息学家和药物…

为什么deferred probe将设备挂入延迟链表而不是将驱动挂入延迟链表

1. 代码流程(drivers/base/dd.c) 可以看到在probe失败的时候(驱动返回-EPROBE_DEFER)是把设备挂到deferred_probe_pending_list上面的。 这就带来了一个疑问: 我当前明明是驱动加载的过程(driver_attach()->bus_for_each_dev()), 为什么要将设备挂到pending list上面而不是…

Maven高级(继承与聚合+私服)

分模块设计和开发 为什么要分模块设计&#xff1f; 一个项目往往是分为好几个模块的 如果不同模块全写在一个项目里面 所有的程序员都要调用这有个项目就难以维护 比如我们之前设计的板块 就不太合理&#xff0c;现在我们把实体类和对应的工具类单独开出两个maven模块存储 然…

安装openai和简单使用

Anaconda的界面创建open ai环境&#xff0c;选择python10 控制台 #或者 conda info -e 注意不是anaconda命令开头 (base) C:\Users\su>conda env list # conda environments: # base * F:\anaconda3 openai F:\anaconda3\envs\opena…

TCP的拥塞控制、提高网络利用率的方法【TCP原理(笔记四)】

文章目录 拥塞控制慢启动 提高网络利用率的规范Nagle算法延迟确认应答捎带应答 拥塞控制 有了TCP的窗口控制&#xff0c;收发主机之间即使不再以一个数据段为单位发送确认应答&#xff0c;也能够连续发送大量数据包。然而&#xff0c;如果在通信刚开始时就发送大量数据&#x…

新能源汽车交流充电桩CP信号详解

随着新能源汽车的推广&#xff0c;交流充电桩迎来了巨大的市场需求&#xff0c;人们对车辆充电的便利性、安全性有着越来越高的要求。CP信号主要用于交流充电桩&#xff0c;充电桩和汽车之间只能通过CP信号进行通讯&#xff0c;判断、控制充电电流和状态。 汽车充电桩CP信号…

QT之自定义表格控件

继承QWidget来绘制的一款自定义控件&#xff0c;设计原因是因为Qt自带的QTableWidget的大批量操作很卡&#xff0c;特别是在嵌入式设备上时。 该控件特色功能&#xff1a; 1、支持拖动自适应。 2、支持各种颜色&#xff0c;字体&#xff0c;行列数设置。 代码如下&#xff1a; …

win10安装cuda11.4及cudnn

查看nvidia驱动版本 在windows终端键入nvidia-smi&#xff0c;查看nvidia显卡驱动。显卡驱动的版本决定了CUDA的版本下限。 如果出现上述的问题&#xff0c;则终端进入C:\Program Files\NVIDIA Corporation\NVSMI文件夹内&#xff0c;再键入nvidia-smi&#xff0c;可以看到我…

[MySQL]MySQL视图特性

[MySQL]MySQL视图特性 文章目录 [MySQL]MySQL视图特性1. 视图的概念2. 视图的基本操作创建视图删除视图 3. 视图规则和限制 1. 视图的概念 视图是一个虚拟表&#xff0c;其内容由查询定义&#xff0c;同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。基表是对…

Git标签管理(对版本打标签,起别名)

tag 理解标签创建标签git tag [name]git show [tagname] 操作标签删除标签git tag -d < tagname > 推送某个标签到远程git push origin < tagname > 理解标签 标签 tag &#xff0c;可以简单的理解为是对某次 commit 的⼀个标识&#xff0c;相当于起了⼀个别名。 …