【RT-Thread】线程的基本知识
什么是线程?
人们在生活中处理复杂问题时,惯用的方法就是分而治之,即把一个大问题分解成多个相对简单、比较容易解决的小问题,小问题逐个被解决了,大问题也就随之解决了。同样,在设计一个较为复杂的应用程序时,也通常把一个大型任务分解成多个小任务,然后通过运行这些小任务,最终达到完成大任务的目的。
在裸机系统中, 系统的主体就是 main 函数里面顺序执行的无限循环,这个无限循环里面 CPU 按照顺序完成各种事情。在多线程系统中,我们根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这个函数我们称为线程。
线程由哪些部分组成?
RT-Thread 中的线程由三部分组成:线程代码(函数)、线程控制块、线程堆栈。
线程栈
在一个裸机系统中, 如果有全局变量,有子函数调用,有中断发生。那么系统在运行的时候,全局变量放在哪里,子函数调用时,局部变量放在哪里, 中断发生时,函数返回地址发哪里。
如果只是单纯的裸机编程,它们放哪里我们不用管,但是如果要写一个 RTOS,这些种种环境参数,我们必须弄清楚他们是如何存储的。
在裸机系统中,他们统统放在一个叫栈的地方,栈是单片机 RAM 里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里面指定, 最后由 C 库函数_main 进行初始化。
但是, 在多线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组, 也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。 如:
static rt_uint8_t led_stack[512];
线程栈其实就是一个预先定义好的全局数据,数据类型为rt_uint8_t,大小我们设置为 512。 在 RT-Thread 中,凡是涉及到数据类型的地方, RTThread 都会将标准的 C 数据类型用 typedef 重新取一个类型名, 以“rt”前缀开头。这些经过重定义的数据类型放在 rtdef.h ,如:

线程控制块
在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等,详细定义如下(在rtdef.h中定义):

为led线程定义一个线程控制块:
static struct rt_thread led_thread;
线程函数
线程控制块中的 entry 是线程的入口函数,它是线程实现预期功能的函数。线程的入口函数由用户设
计实现,一般有以下两种代码形式:
无限循环模式:
在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务:

顺序执行或有限次循环模式:
如简单的顺序语句、 do whlie() 或 for() 循环等,此类线程不会循环或不会永久循环,可谓是 “一次性”线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。

动态线程与静态线程
我们的用户线程有两种创建方式,一种是静态线程,另一种是动态线程。
创建静态线程的函数:

返回值为错误代码。
创建动态线程的函数:

返回值为线程控制块 。
线程创建实例
创建一个静态线程
1、确定线程栈
2、定义线程控制块
3、创建线程函数。
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
/* 静态线程相关宏定义 */
#define THREAD_PRIORITY 25 /* 优先级 */
#define STACK_SIZE 512 /* 栈大小 */
#define TIMESLICE 5 /* 时间片 */
/* 线程三要素 */
static rt_uint8_t static_thread_stack[STACK_SIZE]; /* 线程栈 */
static struct rt_thread static_thread; /* 线程控制块 */
static void static_thread_entry(void* parameter); /* 线程入口函数 */
/* 静态线程入口函数 */
static void static_thread_entry(void* parameter)
{
rt_uint32_t i = 0;
rt_kprintf("This is static thread!\n");
/* 无限循环*/
while (1)
{
rt_kprintf("static thread count:%d \r\n", ++i);
/* 等待0.5s,让出cpu权限,切换到其他线程 */
rt_thread_delay(500);
}
}
/* 主函数 */
int main(void)
{
rt_err_t result;
/* 创建静态线程 : 优先级 25 ,时间片 5个系统滴答,线程栈512字节 */
result = rt_thread_init(&static_thread,
"static_thread",
static_thread_entry,
RT_NULL,
(rt_uint8_t*)&static_thread_stack[0],
STACK_SIZE,
THREAD_PRIORITY,
TIMESLICE);
/* 创建成功则启动静态线程 */
if (result == RT_EOK)
{
rt_thread_startup(&static_thread);
}
}
运行结果为:

可见,在T-Thread中创建一个线程需要线程栈、线程控制块与线程函数这三要素。除此之外,需要设置一个线程优先级,因为RT-Thread的调度器是基于优先级的抢占式调度算法。还需要设置一个时间片参数,这个用于多个线程具有同等优先级的情况下,采用时间片的轮转调度算法进行调度,这个值与时间节拍有关,每一秒的节拍数可在rtconfig.h里进行设置:

在这里我们只创建一个线程,所以时间片我们没有用到,但也需要传递一个时间片的值给rt_thread_init函数。最后,在主函数里调用相关接口创建一个静态线程,创建成功则启动该线程。
创建一个动态线程
创建动态线程与创建静态线程类似:
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
/* 动态线程相关宏定义 */
#define THREAD_PRIORITY 25 /* 优先级 */
#define STACK_SIZE 512 /* 栈大小 */
#define TIMESLICE 5 /* 时间片 */
/* 线程三要素 */
static rt_uint8_t dynamic_thread_stack[STACK_SIZE]; /* 线程栈 */
static struct rt_thread dynamic_thread; /* 线程控制块 */
static void dynamic_thread_entry(void* parameter); /* 线程入口函数 */
/* 动态线程入口函数 */
static void dynamic_thread_entry(void* parameter)
{
rt_uint32_t i;
/* 无限循环*/
while (1)
{
for (i = 0; i < 5; i++)
{
rt_kprintf("dynamic thread count:%d \r\n", i);
/* 等待1s,让出cpu权限,切换到其他线程 */
rt_thread_delay(500);
}
}
}
/* 主函数 */
int main(void)
{
rt_thread_t tid; // 动态线程句柄
/* 创建动态线程 : 优先级 25 ,时间片 5个系统滴答,线程栈512字节 */
tid = rt_thread_create("dynamic_thread",
dynamic_thread_entry,
RT_NULL,
STACK_SIZE,
THREAD_PRIORITY,
TIMESLICE);
/* 创建成功则启动动态线程 */
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
}
运行结果:

静态线程VS动态线程
上例中,从运行结果上看,是没有任何差别的!那么,我们在实际中如何抉择?
使用静态线程时,必须先定义静态的线程控制块,并且定义好栈空间,然后调用rt_thread_init()函数来完成线程的初始化工作。采用这种方式,线程控制块和堆栈占用的内存会放在 RW/ZI 段,这段空间在编译时就已经确定,它不是可以动态分配的,所以不能被释放,而只能使用 rt_thread_detach()函数将该线程控制块从对象管理器中脱离。
使用动态定义方式 rt_thread_create()时, RT-Thread 会动态申请线程控制块和堆栈空间。在编译时,编译器是不会感知到这段空间的,只有在程序运行时, RT-Thread 才会从系统堆中申请分配这段内存空间,当不需要使用该线程时,调用 rt_thread_delete()函数就会将这段申请的内存空间重新释放到内存堆中。
这两种方式各有利弊,静态定义方式会占用 RW/ZI 空间,但是不需要动态分配内存,运行时效率较高,实时性较好。动态方式不会占用额外的 RW/ZI 空间,占用空间小,但是运行时需要动态分配内存,效率没有静态方式高。
总的来说,这两种方式就是空间和时间效率的平衡,可以根据实际环境需求选择采用具体的分配方式。就像C编程中,何时使用动态空间,何时使用静态空间,也需要根据实际情况平衡选择。
我的个人博客:https://zhengnianli.github.io/
我的微信公众号:嵌入式大杂烩
【RT-Thread】线程的基本知识的更多相关文章
- Thread线程的基础知识及常见疑惑点
引言 相信各位道友在平时工作中已经很少直接用到Thread线程类了,现在大多是通过线程池或者一些多线程框架来操作线程任务,但我觉得还是有必要了解清楚Thread线程类中各种方法的含义,了解了底层才能更 ...
- java线程方面的知识
java中单继承,多实现的: 若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承. 为什么是多实现呢? 通过实现接口拓展了类的功能,若实现的多个接口中有重复的 ...
- 学习接水系统(java+thread线程)
(一)项目框架分析 对于学生并发接水项目,根据面向对象的思想,需要创建两个对象,即学生和水龙头. 接下来主要讲解不排队接水和排队接水两张情况. 项目的目录文件如下: (二)不排队接水 假设有四个学生小 ...
- QT5 Thread线程
QT5 Thread线程继承QThread方式 一.首先分析一下 QTimer Class与 Sleep()函数之间的秘密 QTimer *t = new QTimer(*parent); //创建Q ...
- Thread线程join方法自我理解
Thread线程join方法自我理解 thread.join():等待thread线程运行终止,指的是main-thread(main线程)必须等待thread线程运行结束,才能继续thread.jo ...
- java 并发(三)---Thread 线程
Thread 的状态 线程共有五种状态.分别是: (1)新建 (2)就绪 (3)运行 (4)阻塞 (5)死亡 ,下面列列举的状态需要结合状态示意图更好理解. 新建状态(New): 新创建了一个线程对 ...
- c++11中关于`std::thread`线程传参的思考
关于std::thread线程传参的思考 最重要要记住的一点是:参数要拷贝到线程独立内存中,不管是普通类型.还是引用类型. 对于传递参数是引用类型,需要注意: 1.当指向动态变量的指针(char *) ...
- Thread 线程池
Thread 线程池: 当使用多个较短存活期的线程有利时,运用线程池技术可以发挥作用.运用这一技术时,不是为每个任务创建一个全新的线程,而可以从线程池中抽出线程,并分配给任务.当线程完成任务后,再把它 ...
- Thread线程框架学习
原文:https://www.cnblogs.com/wangkeqin/p/9351299.html Thread线程框架 线程定义:线程可以理解为一个特立独行的函数.其存在的意义,就是并行,避免了 ...
- RT Thread 通过ENV来配置SFUD,操作SPI Flash
本实验基于正点原子stm32f4探索者板子 请移步我的RT Thread论坛帖子. https://www.rt-thread.org/qa/forum.php?mod=viewthread& ...
随机推荐
- Alodi:为了保密我开发了一个系统
每天都在愉快的造轮子,这次可以一键创建测试环境 咖啡君维护了几十个不同类型项目,其中有相当一部分项目是对保密性有很高要求的,也就是说下个版本要上线的内容是不能提前泄露的,就像苹果新产品的介绍网站决不允 ...
- [币严区块链]数字货币交易所之瑞波(XRP)钱包对接
对接Ripple(XRP),不需要本地部署钱包,直接访问Ripple API,本文包括访问Ripple API及如何免费获取测试的XRP. 对接流程 安装Ripple API Ripple API 接 ...
- jsp作用域总结
我们在定义每一个变量,每一个属性的时候,都会考虑这个变量.属性的作用范围,也就是作用域. JSP的四大作用域 作用域对象 作用域范围 page 只在当前页面有效 request 一次请求的生命周期内有 ...
- jmeter linux压测报错:Error in NonGUIDriver java.lang.IllegalArgumentException: Problem loading XML from:'/home/server/ptest/disk_out.jmx'.
1.linux环境jmeter与win环境编写脚本的jmeter版本不一致,版本改为一致 2.脚本中存在中文,去除中文 3.脚本中存在类似于jp@gc - Active Threads Over Ti ...
- Day 22 进程管理2之系统的平均负载
1.管理进程状态 当程序运行为进程后,如果希望停止进程,怎么办呢? 那么此时我们可以使用linux的kill命令对进程发送关闭信号.当然除了kill.还有killall,pkill 1.使用kill ...
- Salesforce学习之路-developer篇(一)利用VS Code结合Git开发Salesforce
Part 1: 从Git中克隆代码到本地 git clone https://github.com/git/git Part 2: 在VS Code中安装Salesforce和Git插件 在VS Co ...
- 多事之秋-最近在阿里云上遇到的问题:负载均衡失灵、服务器 CPU 100%、被 DDoS 攻击
昨天 22:00~22:30 左右与 23:30~00:30 左右,有1台服役多年的阿里云负载均衡突然失灵,造成通过这台负载均衡访问博客站点的用户遭遇 502, 503, 504 ,由此给您带来麻烦, ...
- 在linux服务器上装svn版本管理,自动部署代码到web项目
在linux服务器上装svn版本管理,自动部署代码到项目 1.安装svn服务器端 yum install subversion 从镜像下载安装svn服务器端 中间会提示是否ok,输入y,确认 ...
- css/js禁止点击元素
css禁止点击页面元素,只需一句代码即可解决: pointer-events: none; 如果用js来控制的话那就是: $('#test').click(function(){ return fal ...
- Java连载35-类总结、空指针异常
一.总结 1.栈内存中主要存储的是方法体中的局部变量 2.对象内部有实例变量,实例变量存储在堆内存中 3.变量分类:局部变量(方法体中声明):成员变量(方法体外声明) 实例变量(前边修饰符没有stat ...