原创 【RT-Thread学习笔记】11.线程的工作机制

2020-3-27 16:48 1424 4 5 分类: MCU/ 嵌入式 文集: RT-Thread学习笔记
线程控制块
在RT-Thread中,线程控制块由结构体struct rt_thread表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含了线程与线程之间连接用的链表结构,线程等待事件集合等,详细定义如下:
  1. /* 线程控制块 */
  2. struct rt_thread
  3. {
  4. /* rt 对象 */
  5. char name[RT_NAME_MAX]; /* 线程名称 */
  6. rt_uint8_t type; /* 对象类型 */
  7. rt_uint8_t flags; /* 标志位 */
  8. rt_list_t list; /* 对象列表 */
  9. rt_list_t tlist; /* 线程列表 */
  10. /* 栈指针与入口指针 */
  11. void *sp; /* 栈指针 */
  12. void *entry; /* 入口函数指针 */
  13. void *parameter; /* 参数 */
  14. void *stack_addr; /* 栈地址指针 */
  15. rt_uint32_t stack_size; /* 栈大小 */
  16. /* 错误代码 */
  17. rt_err_t error; /* 线程错误代码 */
  18. rt_uint8_t stat; /* 线程状态 */
  19. /* 优先级 */
  20. rt_uint8_t current_priority; /* 当前优先级 */
  21. rt_uint8_t init_priority; /* 初始优先级 */
  22. rt_uint32_t number_mask;
  23. ......
  24. rt_ubase_t init_tick; /* 线程初始化计数值 */
  25. rt_ubase_t remaining_tick; /* 线程剩余计数值 */
  26. struct rt_timer thread_timer; /* 内置线程定时器 */
  27. void (*cleanup)(struct rt_thread *tid); /* 线程退出清除函数 */
  28. rt_uint32_t user_data; /* 用户数据 */
  29. };
其中init_priority是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级)。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是线程的入口函数,它是线程实现预期功能的函数。线程的入口函数由用户设计实现,一般有以下两种代码形式:

----无限循环模式:

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

  1. void thread_entry(void* paramenter)
  2. {
  3. while (1)
  4. {
  5. /* 等待事件的发生 */
  6. /* 对事件进行服务、进行处理 */
  7. }
  8. }

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

----顺序执行或有限次循环模式:

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

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

--线程错误码

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

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

线程状态切换

RT-Thread提供一系列的操作系统调用接口,使得线程的状态在这五个状态之间来回切换。几种状态间的转换关系如下图所示:

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

注意:

RT-Thread中,实际上线程并不存在运行状态,就绪状态和运行状态是等同的。

系统线程

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

--空闲线程

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

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

空闲线程也提供了接口来运行用户设置的钩子函数,在空闲线程运行时会调用该钩子函数,适合钩入功耗管理、看门狗喂狗等工作。

--主线程

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

文章评论1条评论)

登录后参与讨论

ztj2000_532784782 2020-4-17 15:45

谢谢分享!
相关推荐阅读
xld0932 2020-05-29 11:27
【MM32应用笔记】02.使用Xmodem协议实现文件传输功能
1.Xmodem协议Xmodem协议是由Ward Chritensen于70年代提出并实现的,是一种被广泛使用的异步文件传输协议。Xmodem协议传输数据的单位为信息包,每个信息包都包含有一个字节的开...
xld0932 2020-05-25 13:22
【MM32应用笔记】01.使用GPIO端口模拟I2C从机通讯
1.I2C总线搜索复制从事嵌入开发的大多都了解I2C总线。I2C总线仅由两根信号线组成,一根为SCL时钟信号线,另一根为SDA数据信号线;双方通过I2C总线协议完成数据的通讯交互。在做这个章节的实验之...
xld0932 2020-05-17 08:38
【MM32学习笔记】18.定时器(TIM)之3路PWM移相输出
1.移相搜索复制MM32高级定时器TIM1的应用功能非常多,本节就输出比较模式下翻转模式的应用做一个移相的应用。简单的说,移相就是相对于之前输出的相位产生了偏差,这个偏差称之为相位差。MM32的TIM...
xld0932 2020-05-14 21:59
【MM32学习笔记】17.定时器(TIM)之输出相位差为180度的PWM
1.输出相位差为180度的PWM搜索复制这一章节我们来产生两路相位差为180度的PWM脉冲信号,占空比和频率都是相同的,只是相位不同; 在上一小节中,我们使用TIM1产生的3组互补带死区的PWM脉冲,...
xld0932 2020-05-14 21:22
【MM32学习笔记】16.定时器(TIM)之带死区控制的3路互补PWM输出功能
1.死区时间搜索复制死区时间是PWM输出时,为了使H桥或者半H桥上的上下晶体管不会因为开关速度的问题而发生同时导通的情况而设置的一个保护时间段,在这段时间内,上下晶体管都不会有输出。而PWM控制的上下...
xld0932 2020-05-12 21:23
【MM32学习笔记】15.定时器(TIM)之PWM输入捕获
1.PWM输入模式搜索复制MM32的高级控制定时器可以工作在输入捕获模式,PWM输入模式是输入捕获模式的一个特例,与输入捕获模式的异同点可以参考用户手册的11.3.7小节的介绍。但有一点需要注意的就是...
广告
我要评论
1
4
广告
关闭 热点推荐上一条 /3 下一条