线程管理(学习)

线程管理

在日常生活中,我们要完成一个大任务,一般会将它分解成多个简单、容易解决的小问题,小问题逐个被解决,大问题也就随之解决了。

在多线程操作系统中,也同样需要开发人员把一个复杂的应用分解成多个小的、可调度的、序列化的程序单元,当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求。

例如让嵌入式系统执行这样的任务,系统通过传感器采集数据,并通过显示屏将数据显示出来,在多线程实时系统中,可以将这个任务分解成两个子任务,如下图所示,一个子任务不间断地读取传感器数据,并将数据写到共享内存中,另外一个子任务周期性的从共享内存中读取数据,并将传感器数据输出到显示屏上。

在这里插入图片描述
在RT-Thread中,与上述子任务对应的程序实体就是线程,线程是实现任务的载体,它是最基本的调度单位,描述了一个任务执行的运行环境,也描述了这个任务所处的优先级,重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。

当线程运行时,它会认为自己是以独占CPU的方式在运行,线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。

线程管理的功能特点

RT-Thread线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程。

系统线程是由RT-Thread内核创建的线程,用户线程是由应用程序创建的线程。

这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除。
每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。
在这里插入图片描述
RT-Thread的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到CPU的使用权。

当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的 CPU 使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。

如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。

当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。

线程控制块

在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;                      /* 用户数据 */
}

其中init_priorit是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级)。
cleanup会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作。
最后的一个成员 user_data 可由用户挂接一些数据信息到线程控制块中,以提供一种类似线程私有数据的实现方式。

线程栈

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

线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。

对于线程第一次运行,可以以手工的方式构造这个上下文来设置一些初始的环境:入口函数(PC 寄存器)、入口参数(R0 寄存器)、返回位置(LR 寄存器)、当前机器运行状态(CPSR 寄存器)。

线程栈的增长方向是芯片构架密切相关的,RT-Thread 3.1.0 以前的版本,均只支持栈由高地址向低地址增长的方式,对于 ARM Cortex-M 架构,线程栈可构造如下图所示。

在这里插入图片描述
线程栈大小可以这样设定,对于资源相对较大的MCU,可以适当设计较大的线程栈。

也可以在初始时设置较大的栈,例如指定大小为1K或2K字节,然后在FinSH中用list_thread命令查看线程运行过程中使用的栈的大小,然后通过此命令,能够看到从线程启动运行时,到当前时刻点,线程使用的最大栈深度,而后加上适当的余量形成最终的线程栈大小,最后对栈空间大小加以修改。

线程状态

线程运行的过程中,同一时间只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态,如初始状态、挂起状态、就绪状态等。
在 RT-Thread 中,线程包含五种状态,操作系统会自动根据它运行的情况来动态调整它的状态。 RT-Thread 中线程的五种状态,如下表所示:

  • 初始状态:**当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。**此状态在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

线程优先级

RT-Thread线程的优先级是表示线程被调度的优先程序。每个线程都有优先级,线程越重要,赋予的优先级就应越高,线程被调度的可能才会越大。

RT-Thread最大支持256个线程优先级(0~255),数值越小的优先级越高,0为最高优先级。
在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;
对于ARM Cortex-M系列,普遍采用32个优先级。
最低优先级默认分配给空闲线程使用,用户一般不使用。
在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。

时间片

每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。
系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)。

假设有 2 个优先级相同的就绪态线程 A 与 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间来回切换执行,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长,如下图。
在这里插入图片描述

线程的入口函数

线程控制块中的entry是线程的入口函数,它是线程实现预期功能的函数。线程的入口函数由用户设计实现,一般有以下两种代码形式:

无限循环模式
在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务:

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

线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。
但是作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。
所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出CPU使用权的动作,如循环中调用延时函数或者主动挂起。用户设计这种无限循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。

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

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

线程错误码

一个线程就是一个执行场景,错误码是与执行环境密切相关的,所以每个线程配备了一个变量用于保存线程错误码。

#define RT_EOK           0 /* 无错误     */
#define RT_ERROR         1 /* 普通错误     */
#define RT_ETIMEOUT      2 /* 超时错误     */
#define RT_EFULL         3 /* 资源已满     */
#define RT_EEMPTY        4 /* 无资源     */
#define RT_ENOMEM        5 /* 无内存     */
#define RT_ENOSYS        6 /* 系统不支持     */
#define RT_EBUSY         7 /* 系统忙     */
#define RT_EIO           8 /* IO 错误       */
#define RT_EINTR         9 /* 中断系统调用   */
#define RT_EINVAL       10 /* 非法参数      */

线程状态切换

RT-Thread提供一系列的操作系统调用接口,使得线程的状态在这五个状态之间来回切换。
在这里插入图片描述

  • 线程通过调用函数 rt_thread_create/init() 进入到初始状态(RT_THREAD_INIT);
  • 初始状态的线程通过调用函数rt_thread_startup()进入到就绪状态
  • 就绪状态的线程被调度器调度后进入运行状态
  • 当处于运行状态的线程调用rt_thread_delay()、rt_sem_take()、rt_mutex_take()、rt_mb_recv()等函数或者获取不到资源时,将进入到挂起状态(RT_THREAD_SUSPEND);
  • 如果等待超时依然未能获得资源或由于其他线程释放了资源,那么它将返回到就绪状态
  • 挂起状态的线程,如果调用 rt_thread_delete/detach() 函数,将更改为关闭状态(RT_THREAD_CLOSE);
  • 而运行状态的线程,如果运行结束,就会在线程的最后部分执行 rt_thread_exit() 函数,将状态更改为关闭状态。

系统线程

系统线程是指由系统创建的线程,用户线程是由用户程序调用线程管理接口创建的线程,在 RT-Thread 内核中的系统线程有空闲线程和主线程。

空闲线程

空闲线程(idle)是系统创建的最低优先级的线程,线程状态永远为就绪态。
当系统中无其他就绪线程存在时,调度器将调度到空闲线程,它通常是一个死循环,且永远不能被挂起。
另外,空闲线程在RT-Thread也有着它的特殊用途:

若某线程运行完毕,系统将自动删除线程:自动执行rt_thread_exit()函数,将该线程从系统就绪队列中删除,再将该线程的状态更改为关闭状态,不再参与系统调度,然后挂入 rt_thread_defunct 僵尸队列(资源未回收、处于关闭状态的线程队列)中,最后空闲线程会回收被删除线程的资源。

空闲线程也提供了接口来运行用户设置的钩子函数,在空闲线程运行时会调用该钩子函数,适合处理功耗管理、看门狗喂狗等工作。空闲线程必须有得到执行的机会,即其他线程不允许一直while(1)死卡,必须调用具有阻塞性质的函数;否则例如线程删除、回收等操作将无法得到正确执行。

主线程

在系统启动时,系统会创建 main 线程,它的入口函数为 main_thread_entry(),用户的应用入口函数 main() 就是从这里真正开始的,系统调度器启动后,main 线程就开始运行,过程如下图,用户可以在 main() 函数里添加自己的应用程序初始化代码。

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

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

相关文章

imread函数_MATLAB图像处理:27:使用imtranslate函数平移图像

本示例说明如何使用imtranslate函数对图像执行平移操作。平移操作将图像在x或y方向或两者上移动指定数量的像素。将图像读入工作区。I imread(cameraman.tif);显示图像。图像的大小为256 x 256像素。默认情况下,imshow显示图像的左上角坐标为(0,0&#…

计算机网络 --- 网络层IP地址

全世界唯一的32位/4字节标识符&#xff0c;标识路由器主机的接口。 IP地址&#xff1a;&#xff1a; {<网络号><主机号>} IP编址的历史阶段 分类的IP地址子网的划分构成超网&#xff08;无分类编址方法&#xff09; 分类的IP地址 特殊IP地址 私有IP地址 网络地址…

3测试图片显示置信度_云上的移动性能测试平台

1. 功能决定现在&#xff0c;性能决定未来性能测试在移动测试领域一直是一个大难题&#xff0c;它最直观的表现是用户在前台使用 App 时的主观体验&#xff0c;然而决定体验优劣的背后&#xff0c;涉及到了许许多多的技术变迁。当我们习惯于诺基亚时&#xff0c;智能机出现了&a…

计算机网络 --- 网络层重要协议

动态IP分配协议 --- DHCP协议 主机如何获得IP地址&#xff1a; 静态配置 包括IP地址&#xff0c;子网掩码&#xff0c;默认网关 动态配置 由DHCP服务器来分配 DHCP协议&#xff1a; 动态主机配置协议DHCP是应用层协议&#xff0c;使用客户、服务器方式&#xff0c;客户端和…

java 并发_Java并发防范机制

1.背景并发程序开发不可避免地要涉及多线程、多线程协作、数据共享和线程安全等问题。在多线程并发场景下&#xff0c;由于采用数据共享的线程通信模型可能导致多个线程之间并发时相互干扰&#xff0c;影响到程序的正常逻辑、无法保证正常的结果。为了保证程序在并发环境的正确…

水晶底是什么材质_都是红酒杯,水晶的和玻璃的有什么区别?

除了看酒杯的形状外&#xff0c;材质也是我们选酒杯不可忽视的一环。酒杯材质可简单分为玻璃酒杯和水晶酒杯。那么&#xff0c;水晶酒杯和玻璃酒杯到底有何不同&#xff1f;普通玻璃杯二氧化硅是主要成分&#xff0c;价格低廉&#xff0c;容易清洗普通玻璃酒杯材质较厚&#xf…

计算机网络 --- 网络层

主要任务是把分组从源端传到目的端&#xff0c;为分组交换网上的不同主机提供通信服务。网络层的传输单位是数据报。&#xff08;分组是把数据报进行切割形成&#xff09; 网络层主要功能 路由选择与分组转发异构网络互联拥塞控制 如果所有结点都来不及接收分组&#xff0…

mac 串口调试工具_MACamp;串口调试

上一篇文章讲了Mac下进行stm32开发wlzz&#xff1a;Mac下stm32开发(clion)​zhuanlan.zhihu.com不可避免的遇到了串口调试的问题,发现mac下进行串口调试还真的不太容易。下了不少网上的软件,发现都不是特别好用,有很多已经不支持现在的macos系统版本了。于是在CLion下搜索了一下…

golang atomic load 性能_设计模式之Golang单例模式

今天给大家讲下什么是单例模式&#xff0c;以及在Go语言中如何用正确的姿势实现它。其实单例模式是一种在平时开发中经常用到的软件设计模式。在设计模式结构中&#xff0c;其核心是只包含一个被称为单例的特殊类。通过单例模式可以确保系统中一个类只有一个实例&#xff0c;且…

计算机网络 --- 传输层

传输层概述 传输层是只有主机才有的层次&#xff08;路由器没有&#xff09;&#xff0c;为应用层提供通信服务&#xff0c;使用网络层的服务 传输层的功能 传输层提供进程与进程之间的逻辑通信&#xff08;网络层提供主机之间的逻辑通信&#xff09;。复用和分用传输层对收…

手机相机里面的m_荣耀V30 PRO详细评测:Matrix Camera相机矩阵开启5G视频时代

【IT168 评测】随着手机摄像头的配置越来越高&#xff0c;手机上已经能实现不俗的拍照效果&#xff0c;甚至超越了数码相机(DC)成为了人们最常用的拍照工具。进入今年的下半年&#xff0c;随着5G的商用&#xff0c;手机厂商对于手机的摄像头功能优化已经不局限于拍照&#xff0…

计算机网络 --- 传输层UDP协议

UDP只在IP数据报服务之上增加了很少功能&#xff0c;即复用分用和差错检测功能。 UDP的主要特点 UDP是无连接的&#xff0c;减少开销和发送数据之前的时延。UDP使用最大努力交付&#xff0c;即不保证可靠交付。UDP是面向报文的&#xff0c;适合一次性传输少量数据的网络应用。…

怎么调整字段长短_【芝士蛋糕怎么总烤不熟呢?】

烘焙群一位同学发来图片问&#xff0c;不是说看蛋糕有没有烤熟&#xff0c;要用牙签检测吗&#xff1f;可我这个芝士蛋糕&#xff0c;怎么扎都会带出面糊&#xff0c;烤不熟怎么办&#xff1f;看到这个被扎成刺猬的舒芙蕾芝士&#xff0c;原谅Windy 不厚道的笑了。也是Windy 疏…

计算机网络 --- 传输层TCP协议

TCP协议的特点 TCP是面向连接&#xff08;虚连接&#xff09;的传输层协议。每一条TCP连接只能有两个端点&#xff0c;每一条TCP连接只能是点对点的。TCP提供可靠交付的服务&#xff0c;无差错、不丢失、不重复、按序到达。可靠有序&#xff0c;不丢不重。TCP提供全双工通信。…

java 委托_java 能不能自己写一个类叫 java.lang.System/String 正确答案

来自&#xff1a;一汪清水 | 责编&#xff1a;乐乐链接&#xff1a;blog.csdn.net/tang9140/article/details/42738433正文 最近学习了下java类加载相关的知识。然后看到网上有一道面试题是能不能自己写个类叫java.lang.System&#xff1f;网上提供的答案&#xff1a;通常不可…

中奖人js滚动效果_js使用transition效果实现无缝滚动

作者&#xff1a;李大雷出自&#xff1a;SegmentFault 思否原文&#xff1a;segmentfault.com/a/1190000023945464前言无缝轮播一直是面试的热门题目&#xff0c;而大部分答案都是复制第一张到最后。诚然&#xff0c;这种方法是非常标准&#xff0c;那么有没有另类一点的方法呢…

计算机网络 --- 应用层

应用层概述 应用层对应用程序的通信提供服务。 应用层协议定义&#xff1a; 应用进程交换的报文类型&#xff0c;请求还是响应各种报文类型的语法&#xff0c;如报文中的各个字段及其详细描述字段的语义&#xff0c;即包含在字段中的信息的含义进程何时&#xff0c;如何发送…

opencv机器学习线性回归_Python机器学习之?线性回归入门(二)

线性回归原理介绍文章目录机器学习分类线性回归原理线性回归定义线性回归背后矩阵运算机器学习分类机器学习的主要任务便是聚焦于两个问题&#xff1a;分类和回归分类相信大家都不会陌生&#xff0c;生活中会见到很多的应用&#xff0c;比如垃圾邮件识别、信用卡发放等等&#…

php 邮件发送是html 没样式_使用python发送邮件

发送邮件这个功能非常实用&#xff0c;比如你写了一个爬虫&#xff0c;可以通过邮件来接收程序异常或者任务完成的通知&#xff0c;再比如你搭了一个网站&#xff0c;别人只需留下他的邮箱&#xff0c;你就可以自动给他发送邮件。总之我觉得邮件通知非常适合那些处理批量、大量…