RT-Thread学习笔记(7):线程管理

线程管理

  • 线程基本概念
  • 线程调度器
  • 线程状态
  • 线程创建的过程
        • 线程控制块的重要成员
        • 创建线程的过程
  • 线程挂起函数
  • 线程恢复函数
  • 线程设计要点
      • RT-Thread中程序运行的上下文包括:
        • 中断服务函数:
        • 线程:
        • 空闲线程:
      • 线程执行时间
  • API
  • 参考

线程基本概念

RT-Thread的线程可认为是一系列独立线程的集合。每个线程在自己的环境中运行。在任何时刻,只有一个线程得到运行,RT-Thread调度器决定运行哪个线程。调度器会不断启动、停止每一个线程,宏观看上去所有的线程都在同时在执行。作为线程,不需要对调度器的活动有所了解,在线程切入切出保存上下文环境(寄存器值、堆栈内容)是调度器主要的职责。为了实现这点,每个RT-Thread线程都需要有自己的堆栈。当线程切出时,它的执行环境会被保存在该线程的堆栈中,这样当线程再次运行时,就能从堆栈中正确的恢复上次的运行环境。

RT-Thread中的线程是抢占式调度机制,同时支持时间片轮转调度方式。高优先级的线程可打断低优先级线程,低优先级线程必须在高优先级线程阻塞或结束后才能得到调度。

线程调度器

RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权

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

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

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

线程状态

RT-Thread系统中的每一线程都有多种运行状态。系统初始化完成后,创建的线程就可以在系统中竞争一定的资源,由内核进行调度。
线程状态通常分为以下五种:

  • 初始态(RT_THREAD_INIT):创建线程的时候会将线程的状态设置为初始态。
  • 就绪态(RT_THREAD_READY):该线程在就绪列表中,就绪的线程已经具备执行的能力,只等待CPU。
  • 运行态(RT_THREAD_RUNNING):该线程正在执行,此时它占用处理器。
  • 挂起态(RT_THREAD_SUSPEND):如果线程当前正在等待某个时序或外部中断,我们就说这个线程处于挂起状态,该线程不在就绪列表中。包含线程被挂起、线程被延时、线程正在等待信号量、读写队列或者等待读写事件等。
  • 关闭态(RT_THREAD_CLOSE):该线程运行结束,等待系统回收资源。

RT-Thread学习笔记(7):线程管理_第1张图片
状态转化:

  • 初始态→就绪态:线程创建后进入初始态,在线程启动的时候(调用rt_thread_startup()函数)会将初始态转变为就绪态,表明线程已启动,线程可以进行调度。
  • 就绪态→运行态:发生线程切换时,就绪列表中最高优先级的线程被执行,从而进入运行态。
  • 运行态→挂起态:正在运行的线程发生阻塞(挂起、延时、读信号量等待)时,该线程会从就绪列表中删除,线程状态由运行态变成挂起态,然后发生线程切换,运行就绪列表中最高优先级线程。
  • 挂起态→就绪态:阻塞的线程被恢复后(线程恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的线程会被加入就绪列表,从而由挂起态变成就绪态;此时如果被恢复线程的优先级高于正在运行线程的优先级,则会发生线程切换,将该线程由就绪态变成运行态。
  • 就绪态→挂起态:线程也有可能在就绪态时被挂起,此时线程状态会由就绪态转变为挂起态,该线程从就绪列表中删除,不会参与线程调度,直到该线程被恢复。
  • 运行态→就绪态:有更高优先级线程创建或者恢复后,会发生线程调度,此刻就绪列表中最高优先级线程变为运行态,那么原先运行的线程由运行态变为就绪态,依然在就绪列表中。
  • 挂起态→关闭态:处于挂起的线程被调用删除接口,线程状态由挂起态变为关闭态。
  • 运行态→关闭态:运行状态的线程,如果运行结束会在线程最后部分执行rt_thread_exit()函数而更改为关闭状态。

RT-Thread学习笔记(7):线程管理_第2张图片

线程创建的过程

线程控制块的重要成员
  • thread->entry:函数指针
  • thread->parameter:函数参数
  • thread->stack_addr:栈的起始地址
  • thread->stack_size:栈大小
  • thread->sp:栈顶
  • thread->init_priority:初始优先级
  • thread->current_priority:当前优先级
  • thread->init_tick:一次能运行多少个tick
  • thread->remaining_tick:当次运行还剩多少个tick
创建线程的过程
rt_thread_create
	// 1. 分配线程结构体
	thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,name);
	// 2. 分配栈
	stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
	// 3. 初始化栈,即构造栈的内容
	_rt_thread_init
		// 3.1 具体操作
		thread->sp = (void *)rt_hw_stack_init

线程挂起函数

线程挂起可以由多种方法实现:线程调用rt_thread_delay()、rt_thread_suspend()等函数接口可以使得线程主动挂起,放弃CPU使用权,当线程调用rt_sem_take(),rt_mb_recv()等函数时,资源不可使用也会导致调用线程被动挂起。
RT-Thread学习笔记(7):线程管理_第3张图片

  • 代码清单 17-1(1):判断线程是否有效,如果是没被创建的线程,那么无法挂起。
  • 代码清单 17-1(2):判断要挂起线程的状态,如果是已经挂起了,会返回错误码,用户可以在恢复线程后再挂起。
  • 代码清单 17-1(3):将线程的状态变为挂起态。
  • 代码清单 17-1(4):停止线程计时器。

注:通常不应该使用这个函数来挂起线程本身,如果确实需要采用rt_thread_suspend函数挂起当前线程,需要在调用rt_thread_suspend()函数后立刻调用rt_schedule()函数进行手动的线程上下文切换

线程恢复函数

RT-Thread学习笔记(7):线程管理_第4张图片

  • 代码清单 17-3(1):判断线程是否有效,如果是没被创建的线程,那么无法恢复。并且检查当前线程是否已经挂起,要恢复的线程当然是必须是挂起态的,如果不是挂起态的根本不需要进行恢复。
  • 代码清单 17-3(2):将线程从挂起列表中删除。
  • 代码清单 17-3(3):将恢复的线程加入就绪列表,但是此时线程能不能立即运行是根据其优先级决定的,如果该线程的优先级在就绪列表中最高,那么是可以立即运行的。

线程设计要点

RT-Thread中程序运行的上下文包括:

中断服务函数:

中断服务函数是一种需要特别注意的上下文环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(也被称作特权模式)),在这个上下文环境中不能使用挂起当前线程的操作不允许调用任何会阻塞运行的API函数接口。另外需要注意的是,中断服务程序最好保持精简短小,快进快出,一般在中断服务函数中只做标记事件的发生,让对应线程去执行相关处理,因为中断服务函数的优先级高于任何优先级的线程,如果中断处理时间过长,将会导致整个系统的线程无法正常运行。所以在设计的时候必须考虑中断的频率、中断的处理时间等重要因素,以便配合对应中断处理线程的工作。

线程:

线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是做为一个优先级明确的实时系统,如果一个线程中的程序出现了死循环操作(此处的死循环是指没有不带阻塞机制的线程循环体),那么比这个线程优先级低的线程都将无法执行,当然也包括了空闲线程,因为死循环的时候,线程不会主动让出CPU,低优先级的线程是不可能得到CPU的使用权的,而高优先级的线程就可以抢占CPU。这个情况在实时操作系统中是必须注意的一点,所以在线程中不允许出现死循环。如果一个线程只有就绪态而无阻塞态,势必会影响到其他低优先级线程的执行,所以在进行线程设计时,就应该保证线程在不活跃的时候,线程可以进入阻塞态以交出CPU使用权,这就需要我们自己明确知道什么情况下让线程进入阻塞态,保证低优先级线程可以正常运行。在实际设计中,一般会将紧急的处理事件的线程优先级设置得高一些。

空闲线程:

空闲线程(idle线程)是RT-Thread系统中没有其他工作进行时自动进入的系统线程。用户可以通过空闲线程钩子方式,在空闲线程上钩入自己的功能函数。通常这个空闲线程钩子能够完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等。除了空闲线程钩子,RT-Thread系统还把空闲线程用于一些其他的功能,比如当系统删除一个线程或一个动态线程运行结束时,会先行更改线程状态为非调度状态,然后挂入一个待回收队列中,真正的系统资源回收工作在空闲线程完成,空闲线程是唯一不允许出现阻塞情况的线程,因为RT-Thread需要保证系统用于都有一个可运行的线程
对于空闲线程钩子上挂接的空闲钩子函数,它应该满足以下的条件:
-不会挂起空闲线程;
-不应该陷入死循环,需要留出部分时间用于系统处理系统资源回收。

线程执行时间

线程的执行时间一般是指两个方面,一是线程从开始到结束的时间,二是线程的周期

在系统设计的时候这两个时间候我们都需要考虑,例如,对于事件A对应的服务线程Ta,系统要求的实时响应指标是10ms,而Ta的最大运行时间是1ms,那么10ms就是线程Ta的周期了,1ms则是线程的运行时间,简单来说线程Ta在10ms内完成对事件A的响应即可。此时,系统中还存在着以50ms为周期的另一线程Tb,它每次运行的最大时间长度是100us。在这种情况下,即使把线程Tb的优先级抬到比Ta更高的位置,对系统的实时性指标也没什么影响,因为即使在Ta的运行过程中,Tb抢占了Ta的资源,等到Tb执行完毕,消耗的时间也只不过是100us,还是在事件A规定的响应时间内(10ms),Ta能够安全完成对事件A的响应。但是假如系统中还存在线程Tc,其运行时间为20ms,假如将Tc的优先级设置比Ta更高,那么在Ta运行的时候,突然间被Tc打断,等到Tc执行完毕,那Ta已经错过对事件A(10ms)的响应了,这是不允许的。所以在我们设计的时候,必须考虑线程的时间,一般来说处理时间更短的线程优先级应设置更高一些。

API

RT-Thread学习笔记(7):线程管理_第5张图片

详细API可查询RT-Thread官网手册。

参考

本节笔记参考于:野火-《RT-Thread内核实现与应用开发实战》
以及RT-Thread官网:线程管理

你可能感兴趣的:(RTThread笔记,学习,单片机,stm32)