目录
一、RTT线程的特点
二、RTT线程机制
2.1 线程的属性与线程控制块详解
2.2 线程的调度
2.3 线程的切换
2.5 系统线程
三、线程的调度机制
3.1 线程创建与删除
1、线程控制块
2、线程栈
3、入口函数
4、线程的创建方式
(1)静态线程初始化函数如下:
(2)动态创建函数如下:
(3)示例1:创建线程
(4)示例2:线程2的代码:
4、线程优先级和Tick
1、线程优先级
2、时间片
5、线程状态
6、空闲线程
四、RTT线程的使用
一、RTT线程的特点
RT Thread(RTT)是一个基于C语言的嵌入式实时操作系统内核,具有方便的API和可裁剪的内核特性。RTT线程管理主要包括线程的创建、销毁、挂起、恢复等功能。通过RTT内核提供的API,可以方便地对线程进行管理和调度,实现任务的实时性和高效性。
RTT线程管理的一些特点包括:
- 线程的创建和销毁:可以通过RTT提供的API创建新的线程,并在不需要时销毁线程,以释放系统资源。
- 线程的挂起和唤醒:可以通过RTT提供的API将线程挂起(暂停执行),并在需要时唤醒线程(继续执行)。
- 线程的优先级调度:RTT支持多种调度算法,可以根据线程的优先级来进行调度,确保高优先级任务优先执行。
- 线程同步和通信:通过RTT提供的信号量、邮箱等机制,可以实现线程间的同步和通信,确保数据的正确和实时传输。
- 线程的实时性能:RTT线程管理能够保证线程在规定的时间内响应和执行任务,实现实时性能。
总体来说,RTT线程管理通过简洁而高效的API,提供了对线程的灵活管理和调度,帮助开发者实现复杂的实时系统。
RTT线程的一些特点包括:
- 实时性能强:RTT线程具有快速响应和执行任务的能力,能够在规定的时间内完成任务。
- 高度可裁剪:RTT内核具有可裁剪的特性,可以根据应用需求选择需要的功能和特性,避免系统资源的浪费。
- 多任务支持:RTT支持多个任务同时运行,能够根据任务的优先级进行调度,确保高优先级任务先执行。
- 足够小巧:RTT内核具有较小的内存占用,适合运行在资源有限的嵌入式系统中。
- 灵活的API:RTT提供了简单易用的API,方便开发者进行线程管理和调度。
- 精简而高效:RTT线程管理具有高效的执行速度和低延迟,能够满足实时系统的需求。
二、RTT线程机制
2.1 线程的属性与线程控制块详解
以下是一个简单的RTT线程控制块数据结构的代码示例:
typedef struct rt_thread
{const char *name; // 线程名称rt_uint8_t priority; // 线程优先级rt_uint8_t state; // 线程状态void *stack_addr; // 线程堆栈地址void *entry; // 线程入口函数void *parameter; // 线程参数rt_ubase_t id; // 线程IDrt_tick_t init_tick; // 初始时间片rt_tick_t remaining_tick; // 剩余时间片rt_list_t tList; // 等待队列
} rt_thread_t;
- 线程名称:用于标识线程的名称。
- 线程优先级:指定线程的执行优先级。
- 线程状态:表示线程当前的状态,如运行、挂起、休眠等。
- 线程堆栈:保存线程运行时的栈空间。
- 线程入口函数:线程开始执行的入口函数。
- 线程参数:传递给线程入口函数的参数。
- 线程ID:唯一标识线程的ID。
- 时间片:指定线程的时间片大小,用于实现多任务调度。
- 等待队列:保存等待该线程执行完成的其他线程。
时间片在线程控制块中的作用是指定线程在多任务调度中可以占用的CPU执行时间。时间片值决定了线程执行的时间长度,当时间片用尽后,系统会将 CPU 时间切换到其他同等优先级的可执行的线程,以实现同等优先级多任务并行执行。通过时间片的设置,可以合理分配 CPU 资源,确保系统的实时性和效率。
RT Thread线程的状态通常包括以下几种:
- 运行状态:线程当前正在执行,处于运行状态。
- 就绪状态:线程已经准备好执行,但由于其他高优先级线程正在执行,暂时无法获得CPU时间片。
- 挂起状态:线程被挂起,暂时停止执行,直到恢复为止。
- 睡眠状态:线程主动挂起,等待某个条件的发生,如等待定时器超时、事件发生等。
- 结束状态:线程执行完毕或被销毁,进入结束状态。
通过这些状态,系统可以动态地管理和调度线程的执行顺序,保证系统的实时性和效率。开发者可以根据需求将线程设置为不同的状态,以实现多任务的并行执行和协同工作。
2.2 线程的调度
RT Thread线程的调度是指系统根据线程的优先级和时间片来决定线程的执行顺序和时长。
在RT Thread中,系统会维护一个线程就绪队列,根据线程的优先级来确定每次调度时执行的线程。
当系统启动时,会将优先级最高的就绪线程放到CPU上执行,直到线程执行完毕或时间片用尽,系统会选择下一个优先级最高的就绪线程继续执行。
如果当前线程的优先级低于其他线程的优先级,时间片执行完,系统会暂停当前线程的执行,将CPU时间片分配给更高优先级的线程。
时间片是指系统分配给每个线程连续执行的时间长度,当时间片用尽时,系统会进行线程切换,选择另一个就绪线程进行执行。通过动态调度和时间片的控制,系统可以实现多任务的并行执行,提高系统的响应速度和效率。
开发者可以通过设置线程的优先级、时间片等属性来调整线程的调度策略,以满足系统对实时性和性能的需求。合理的线程调度可以帮助系统充分利用CPU资源,提高系统的并发处理能力和稳定性。
2.3 线程的切换
在多任务系统中,线程切换是指从当前正在执行的线程切换到另一个线程的过程。线程切换的原因可能是当前线程时间片用尽、等待I/O操作、等待事件发生等。线程切换的过程包括保存当前线程的上下文信息(包括程序计数器、栈指针、寄存器等)、选择下一个要执行的线程,并将其上下文信息恢复,以便让其继续执行。
在RT Thread中,线程切换是由系统的调度器来控制和执行的。当当前线程的时间片用尽或有更高优先级的线程就绪时,系统会触发线程切换。在线程切换过程中,系统需要保证上下文信息的正确保存和恢复,以确保线程的执行状态能够无缝切换,不影响线程的正常执行。
线程切换是系统调度的关键环节,直接影响系统的响应速度和性能。合理的线程切换策略可以帮助系统高效地分配CPU资源,提高系统的并发处理能力和实时性。开发者可以通过调整线程的优先级、时间片等属性来影响线程切换的频率和方式,以满足系统的需求。
在RT Thread中,抢占式是指系统线程按照其优先级进行调度和执行的方式。当一个高优先级的线程准备就绪并且当前正在运行的线程时间片用尽或主动释放CPU时,系统会立即抢占当前线程的CPU资源,将CPU交给高优先级的线程执行。这种方式能够确保高优先级的任务及时得到执行,提高系统的实时性和响应能力。
在RT Thread中,抢占式调度是通过系统调度器实现的,调度器会根据线程的优先级和就绪状态来决定下一个要执行的线程。开发者可以通过设置线程的优先级来控制线程的调度顺序,确保关键任务能够及时响应和执行。
抢占式调度可以有效避免低优先级任务长时间占用CPU资源而导致系统响应缓慢的情况,提高系统的稳定性和实时性。开发者可以利用RT Thread提供的API接口和工具,合理设计和管理系统线程的优先级和调度策略,以达到系统性能的最优化。
2.5 系统线程
三、线程的调度机制
3.1 线程创建与删除
在RT-Thread中,线程是RT-Thread中最基本的调度单位,使用rt_thread结构体表示线程。
rt_thread描述了一个线程的运行环境,也描述了这个线程所处的优先级
系统中总共有两种线程,分别是系统线程和用户线程
- 系统线程:由RT-Thread内核自己创建
- 用户线程:由用户应用程序创建
这两类线程都会从内核对象容器中分配线程对象,如下图所示:
每个线程由三部分关键信息组成组成:线程控制块,线程栈和入口函数
1、线程控制块
线程控制块由结构体rt_thread表示,线程控制块是操作系统用于管理线程的一个数据结构
它存放一些现成的信息,例如线程优先级,线程名称,线程状态等,也包括线程与线程之间连接用的链表结构,线程等待事件集合等
它在rtdef.h中定义:
- 线程名称:用于标识线程的名称。
- 线程优先级:指定线程的执行优先级。
- 线程状态:表示线程当前的状态,如运行、挂起、休眠等。
- 线程堆栈:保存线程运行时的栈空间。
- 线程入口函数:线程开始执行的入口函数。
- 线程参数:传递给线程入口函数的参数。
- 线程ID:唯一标识线程的ID。
- 时间片:指定线程的时间片大小,用于实现多任务调度。
- 等待队列:保存等待该线程执行完成的其他线程。
2、线程栈
在裸机系统中,涉及局部变量、子函数调用或中断发生,就需要用到栈,
而在RTOS系统中,每个线程运行时,也是普通函数调用,也涉及局部变量、子函数调用、中断,也要用到栈。
但不同于裸机系统,RTOS存在多个线程,每个线程互不干扰的,因此需要为每个线程都分配独立的栈空间,这就是线程栈。
在RT Thread中,每个线程都有自己的栈空间用于存储局部变量、函数调用信息和临时数据。线程栈在 struct rt_thread 的成员中包含了 stack_addr 和 stack_size 两个属性,分别表示线程的栈地址和栈大小。
线程栈的大小是一个重要的考虑因素,它需要根据线程的任务需求和系统资源进行合理设置。如果线程执行过程中需要使用大量的局部变量或进行复杂的函数调用,那么需要分配足够大的栈空间来避免栈溢出的情况发生。
在创建线程时,开发者需要指定线程的 stack_addr 和 stack_size,系统会根据这两个参数来为线程分配合适的栈空间。通过合理设置线程栈的大小和地址,可以有效地优化系统内存的利用并避免因栈溢出而导致的系统崩溃问题。
总之,线程栈在RT Thread中扮演着重要的角色,通过合理管理线程栈的大小和地址,可以确保系统线程的正常运行和稳定性。
3、入口函数
入口函数是线程要运行的函数,由用户自行设计。
可分为无限循环模式和顺序执行模式
void thread_entry(void *parameter)
{
while(1)
{
/*等待事件发生*/
/*对事件进行服务,进行处理*/
}
}
使用这种模式时,需要注意,一个实时操作系统,不应该让一个线程一直处于最高优先级占用CPU,让其他线程得不到执行。
因此在这种模式下,需要调用延时函数或者主动挂起。这种无限循环模式设计的目的是让这个线程一直循环调度运行,而不结束。
顺序执行模式
static void thread_entry(void *parameter)
{
/*处理事务1*/
...
/*处理事务2*/
...
/*处理事务3*/
}
使用这种模式时线程不会一直循环,最后一定会执行完毕。
执行完毕之后,线程将被系统自动删除。
以上就是线程的三要素:线程控制块,线程栈,入口函数;
线程的五种状态:初始态,就绪态,运行态,挂起态,关闭态。
线程的三种基本形式:单次执行,周期执行,资源驱动(时间也是资源的一种)
4、线程的创建方式
RT-Thread提供两种线程的创建方式:
- 静态创建:使用rt_thread_init(void *parameter)
- 动态创建:使用rt_thread_creare(void *paremeter)
这两种方式的区别就是分配线程控制块和线程栈的分配方式是静态的还是动态的。
静态线程:是由使用该API的用户来分配栈空间和线程句柄的内存。
动态线程:是系统(API函数内部)自动从动态内存堆上分配栈线程的堆栈空间与线程句柄以及对应的线程控制块的内存。
(1)静态线程初始化函数如下:
(2)动态创建函数如下:
动态模式:不需要用户指定现场堆栈的空间地址。
创建线程后,还需要启动线程,才能让线程运行起来。
启动线程函数如下:
rt_err_t rt_thread_startup(rt_thread_t thread)
(3)启动线程
(3)示例1:创建线程
使用静态创建和动态创建两种方法分别创建两个线程
线程1的代码:
/*线程1的入口函数*/
static void thread1_entry(void *parameter)
{
const char *thread_name = "Thread1_run\r\n";
volatile rt_uint32_t cnt = 0;
/*线程1*/
while(1)
{
/*打印线程1的信息*/
rt_kprintf(thread_name);
/*延迟一会(比较简单粗暴)*/
for(cnt=0;cnt<100000;cnt++)
{
}
}
}
(4)示例2:线程2的代码:
/*线程2入口函数*/
static void thread_entry(void *parametr)
{
const char *thread_name = "Thread2 run";
volatile rt_uint32_t cnt = 0;
/*线程2*/
while(1)
{
/*打印线程2的信息*/
rt_kprintf(thread_name);
}
/*延迟一会,简单粗暴*/
for(cnt = 0;cnt<100000;cnt++)
{
}
}
静态创建线程:
rt_thread_init(&thread1, //线程句柄
"thread1" //线程名字
thread1_entry, //入口函数
RT_NULL, //入口函数参数
&thread1_stack[0], //线程栈起始地址
sizeof(thread1_stack), //栈大小
THREAD_PRIORITY, //线程优先级
THREAD_TIMESLICE //线程时间片大小
);
rt_thread_startup(&thread1);
动态创建线程:
thread2 = rt_thread_create("thread2", //线程名字
thread2_entry, //入口函数
RT_NULL, //入口函数参数
THREAD__STACK_SIZE, //栈大小
THREAD_PRIORITY, //线程优先级
THREAD_TIMESLICE //线程时间片
)
4、线程优先级和Tick
1、线程优先级
RT-Thread的线程优先级是指线程被调度的优先程度
每个线程都有优先级,线程的重要性越高,优先级应该设置的越高,被调度的可能才会更大。
由rtconfig.h中定义的RT_THREAD_PRIORITY宏指定优先级范围,RT-Thread最大支持256级优先级,数值越小的优先级越高,0为最高优先级。
在学习调度方法之前,只要粗略的知道,RT-Thread会确保优先级最高,可运行的线程,马上就能执行。
RT-Thread会确保优先级最高的,可运行的线程马上就能执行。
对于相同优先级的,可运行的线程,轮流执行。
举个例子,
厨房着火了,当然优先灭火
喂饭,回复信息同样重要,轮流做
2、时间片
RT-Thread中也有心跳,它使定时器产生固定间隔的中断,这叫Tick,滴答,比如每1ms发生一次时钟中断。
5、线程状态
简单分为运行态和非运行态,具体的非运行态又可以细分,
初始态(RT_THREAD_INIT):创建线程的时候会将线程的状态设置为初始态
就绪态(RT_THREAD_READY):该线程就在就绪列表中,就绪的线程已经具备执行的能力,只等待CPU
运行态(RT_THREAD_RUNNING):该线程正在运行,此时它正占用CPU
挂起态(RT_THREAD_SUSPEND):如果线程当前正在等待某个时序或外部中断,我们就说这个线程处于挂起状态,该线程不在就绪列表中,包含线程被挂起、线程被延时,线程正在等待信号量、读写队列或者等待读写事件
关闭态(RT_THREAD_CL):该线程运行结束,等待系统回收资源。
线程状态迁移
RT_Thread系统中的每一个线程都有多种运行状态,他们之间的转换关系如下图所示,从运行态变成阻塞态、或者从阻塞态变成就绪态,这些线程状态是怎么迁移的呢?
6、空闲线程
空闲线程是系统中一个比较特殊的线程,它具有最低的优先级,当系统中无其他线程可运行时,调度器将调度到空闲线程。空闲线程通常是一个死循环,永远不挂起。
RT_Thread实时操作系统为空闲线程提供了一个钩子函数(钩子函数:用户提供的一段代码,在系统运行的某一路径上设置一个钩子,当系统经过这个位置时,转而执行这个钩子函数,然后再返回到它的正常路径上) ,可以让系统在空闲的时候执行一些特定的任务,例如系统运行指示灯闪烁,电源管理等,除了调用钩子函数,RT_Thread也把线程清理(rt_thread_cleanup)函数,真正的线程删除动作放到了空闲线程中(在删除线程时,仅改变线程的状态为关闭状态不在参与系统调度)。
调度器相关接口
调度器初始化
在系统启动时需要执行调度器的初始化,以初始化系统调度器用到的一些全局变量。调度器初始化可以调用下面的接口:
void rt_system_schduler_init(void)
启动调度器
void rt_system_scheduler(void)
执行调度
void rt_schedule(void)
设置调度器钩子函数
在整个系统的运行时,系统都处于线程运行、中断触发-响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个响应的钩子函数。在系统线程切换时,这个钩子函数将被调用
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from,struct rt_thread* to))
四、RTT线程的使用
RTT线程管理提供了一系列API函数,用于线程的创建、销毁、挂起、唤醒等操作。
一些常用的线程相关API函数包括:
-
rt_thread_t rt_thread_create(const char* name, rt_thread_entry_t entry, void* parameter, rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick):
创建一个新的线程,指定线程的名称、入口函数、参数、栈空间大小、优先级和指定时间片长度等参数。 -
void rt_thread_delete(rt_thread_t thread):
销毁指定的线程,释放线程占用的资源。 -
rt_err_t rt_thread_startup(rt_thread_t thread):
启动指定的线程,使其开始执行任务。 -
void rt_thread_yield(void):
主动让出CPU执行时间,从当前线程切换到其他可执行的线程执行。 -
rt_err_t rt_thread_suspend(rt_thread_t thread):
挂起指定的线程,暂停其执行。 -
rt_err_t rt_thread_resume(rt_thread_t thread):
恢复指定的线程,使其继续执行。 -
rt_err_t rt_thread_sleep(rt_tick_t tick):
使当前线程休眠指定的时钟节拍数。 -
rt_err_t rt_thread_delay(rt_tick_t tick):
使当前线程延迟指定的时钟节拍数后执行。
以上是一些RTT线程相关的常用API函数,开发者可以根据需求选择合适的API函数来管理和调度线程。