线程管理
在日常生活中,我们通常将大任务分解成多个易于处理的小问题,逐个击破,从而解决整个大问题。同样,在多线程操作系统中,开发者需要将复杂的应用程序分解成多个小的、可调度的、顺序执行的程序单元。合理地划分任务并正确地调度执行,可以使系统满足实时性的性能和时间要求。例如,一个嵌入式系统需要通过传感器采集数据并在显示屏上显示。在多线程实时系统中,可以将此任务分解为两个子任务:一个子任务持续读取传感器数据并写入共享内存;另一个子任务周期性地从共享内存读取数据并显示在屏幕上。
在 RT-Thread 实时操作系统中,线程是任务的具体执行实体,也是系统调度的基本单位。每个线程都描述了一个任务执行的上下文环境,同时也指定了该任务的优先级。系统可以根据线程的优先级来决定其执行顺序,重要的任务可以被赋予较高的优先级,从而获得优先执行的机会;不太重要的任务可以被赋予较低的优先级;多个任务也可以被赋予相同的优先级,从而轮流执行。
当一个线程运行时,它会认为自己独占了 CPU 的使用权。线程执行时的运行环境被称为上下文 (Context),具体包括各种变量和数据,例如 CPU 的所有寄存器变量、堆栈数据以及内存信息等。
本章将分为 5 节内容,详细介绍 RT-Thread 的线程管理机制。阅读完本章后,读者将会对 RT-Thread 的线程管理机制有一个比较深入的了解,例如:线程有哪些状态?如何创建一个线程?为什么需要空闲线程?等等,这些问题都将得到解答。
线程管理的功能特点
RT-Thread 的线程管理负责对系统中的所有线程进行管理和调度。这些线程主要分为两类:一类是由 RT-Thread 内核创建的系统线程,另一类是由应用程序创建的用户线程。它们都从内核对象容器中获取线程对象,并在线程结束时将其归还。每个线程都具备一些关键属性,包括用于管理线程的控制块、用于函数调用和局部变量存储的栈空间,以及线程执行的入口函数。
RT-Thread 的线程调度器采用抢占式调度策略。其主要任务是从就绪线程列表中找出优先级最高的线程,并保证该线程能够获得 CPU 的使用权。这意味着,只要有更高优先级的线程进入就绪状态,当前正在运行的线程就会立即被抢占,即让出 CPU 使用权,以便高优先级的线程能够立即执行。
在中断处理过程中,如果中断服务程序使得一个比当前运行线程优先级更高的线程具备了运行条件(例如,释放了一个被高优先级线程等待的信号量),那么当中断处理完成后,系统并不会立即返回到被中断的线程继续执行,而是会进行一次线程切换,将 CPU 的使用权交给优先级更高的线程。
当发生线程切换时,线程调度器会负责保存当前线程的上下文信息(例如寄存器、程序计数器、堆栈指针等),以便该线程下次被调度时能够从中断处恢复执行。当调度器再次切换回该线程时,会恢复之前保存的上下文信息,使线程能够无缝地继续运行。
线程的工作机制
线程控制块
在 RT-Thread 中,线程控制块 (Thread Control Block, TCB) 是一个名为 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_priority
是线程创建时指定的优先级,通常在线程运行过程中保持不变。cleanup
函数指针用于在线程退出时执行用户自定义的清理操作。user_data
成员可用于存储用户自定义的线程私有数据。
线程重要属性
线程栈
在 RT-Thread 里,每个线程都有自己的专属空间,叫做线程栈。这个空间就像草稿纸,用来存放线程运行的临时数据。当线程切换的时候,会把当前线程的“工作进度”(上下文)记录到栈里,等下次再运行这个线程的时候,就能从栈里恢复进度了。
线程栈也用来存放函数内部的变量。当一个函数被调用的时候,它的变量会占用一部分栈空间。在 ARM 架构下,这些变量一开始可能会放在寄存器里,但是如果这个函数又调用了其他函数,这些变量就会被放到栈里暂存。
对于一个新线程第一次运行,我们需要手动设置它的初始工作状态(上下文),包括从哪里开始执行(入口函数)、需要处理的数据(入口参数)、执行完后返回哪里(返回地址)以及当前的运行状态等。
线程栈的增长方向和芯片结构有关。在 RT-Thread 3.1.0 之前的版本,线程栈都是从上往下增长的(从高地址到低地址)。
线程栈的大小需要根据实际情况设置。如果 MCU 资源比较多,可以给线程栈分配更大的空间。一个比较实用的方法是:先给栈分配一个比较大的空间(比如 1KB 或 2KB),然后用 list_thread
命令看看线程实际用了多少栈空间。这个命令会显示线程最多用过多少栈空间。根据这个数值再加上一些余量,就能确定最终的栈大小了。
线程状态
RT-Thread 的线程在运行过程中会经历五种状态,由操作系统根据线程的运行情况进行动态调整。下表对这五种状态进行了详细描述:
状态 | 描述 | 宏定义 |
---|---|---|
初始状态 | 线程刚创建但尚未运行,不参与调度。 | RT_THREAD_INIT |
就绪状态 | 线程等待被调度执行,系统会根据优先级选择就绪状态的线程运行。 | RT_THREAD_READY |
运行状态 | 线程正在处理器上运行。在单核系统中,同一时刻只有一个线程处于运行状态;多核系统中,则可以有多个线程同时处于运行状态。 | RT_THREAD_RUNNING |
挂起状态 | 也称阻塞状态,线程因等待资源或主动延时而暂停运行,不参与调度。 | RT_THREAD_SUSPEND |
关闭状态 | 线程运行结束,不参与调度。 | RT_THREAD_CLOSE |
线程优先级
在 RT-Thread 中,每个线程都被赋予一个优先级,用于表示该线程被调度的优先程度。优先级数值越小,则优先级越高,0 代表最高优先级。
RT-Thread 最大支持 256 个优先级(范围为 0 到 255),用户可以根据实际需要为线程分配不同的优先级。对于资源受限的系统,可以配置成只支持 8 个或 32 个优先级,以减少系统开销。例如,在 ARM Cortex-M 系列 MCU 上,通常采用 32 个优先级的配置。最低优先级通常保留给空闲线程使用,用户应用程序一般不使用最低优先级。在系统运行过程中,如果有一个比当前运行线程优先级更高的线程进入就绪状态,那么当前线程会被立即抢占,高优先级线程会获得 CPU 的使用权。
时间片
在 RT-Thread 中,每个线程都拥有一个时间片参数。但需要注意的是,时间片只对优先级相同的就绪态线程有效。当系统调度优先级相同的就绪态线程时,采用时间片轮转的调度方式。时间片的作用是限制每个线程单次运行的最长时间,其单位是一个系统节拍(OS Tick)。关于系统节拍的详细内容,请参考文档的《时钟管理》章节。
举例说明:假设有两个优先级相同的就绪态线程 A 和 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5。如果系统中没有比 A 和 B 优先级更高的就绪态线程,那么系统会在 A 和 B 线程之间进行切换。每次切换到 A 线程时,A 线程会运行 10 个系统节拍的时长;每次切换到 B 线程时,B 线程会运行 5 个系统节拍的时长。
线程入口函数
RT-Thread 线程的入口函数,由 struct rt_thread
结构体中的 entry
成员指定,定义了线程的具体行为。线程入口函数通常有两种编写模式:
无线循环模式
这种模式常用于实时系统中,线程以被动方式等待事件发生并进行处理。代码结构通常如下:
void thread_entry(void* parameter)
{while (1){/* 等待事件发生 *//* 处理事件 */}
}
需要注意的是,为了避免线程陷入死循环导致低优先级线程无法执行,无限循环中必须包含让出 CPU 使用权的操作,例如调用延时函数 (rt_thread_mdelay
、rt_thread_delay
) 或主动挂起线程 (rt_thread_suspend
)。这种模式设计的线程会一直运行,不会被系统自动删除。
顺序执行或有限次循环模式
这种模式下,线程执行完一系列操作后便会结束。代码结构可以是简单的顺序语句,也可以包含 do while
或 for
循环,但循环次数是有限的。线程执行完毕后,系统会自动将其删除。示例如下:
static void thread_entry(void* parameter)
{/* 处理事务 #1 */// .../* 处理事务 #2 */// .../* 处理事务 #3 */// ...
}
两种模式的对比如下:
特性 | 无限循环模式 | 顺序执行/有限次循环模式 |
---|---|---|
执行方式 | 持续运行,通常用于处理周期性任务或事件响应 | 执行完毕后自动结束,通常用于执行一次性任务 |
CPU 使用权 | 需要主动让出 CPU 使用权,例如调用延时函数或主动挂起线程 | 自动释放 CPU 使用权 |
线程生命周期 | 线程会一直运行,不会被系统自动删除 | 线程执行完毕后会被系统自动删除 |
代码示例 | while (1) { ... } | // 处理事务 1 ...; // 处理事务 2 ...; // 处理事务 3 ... |
线程错误码
在 RT-Thread 中,每个线程都维护一个错误码,用于指示线程运行过程中遇到的错误状态。这些错误码由预定义的宏表示,方便开发者识别和处理。线程的错误码有以下几种:
#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 /* 非法参数 */
下表详细列出了各种线程的错误码:
0错误码 | 描述 |
---|---|
RT_EOK | 无错误 |
RT_ERROR | 普通错误 |
RT_ETIMEOUT | 超时错误 |
RT_EFULL | 资源已满 |
RT_EEMPTY | 无资源 |
RT_ENOMEM | 无内存 |
RT_ENOSYS | 系统不支持 |
RT_EBUSY | 系统忙 |
RT_EIO | IO 错误 |
RT_EINTR | 中断系统调用 |
RT_EINVAL | 非法参数 |
线程状态切换
RT-Thread 线程在其生命周期内,会在五种状态之间进行转换,这些转换由 RT-Thread 提供的系统调用接口触发。具体转换过程如下:
- 创建: 通过 rt_thread_create/init 函数创建线程后,线程进入初始状态 (RT_THREAD_INIT)。
- 启动: 调用 rt_thread_startup 函数可以将初始状态的线程启动,使其进入就绪状态 (RT_THREAD_READY),等待被调度。
- 运行: 调度器选择优先级最高的线程执行,线程进入运行状态 (RT_THREAD_RUNNING)。
- 挂起: 运行状态的线程因多种原因可能进入挂起状态 (RT_THREAD_SUSPEND),例如:
- 主动调用延时函数 (rt_thread_delay);
- 尝试获取信号量、互斥量等资源失败 (rt_sem_take、rt_mutex_take 等);
- 等待邮箱或消息队列 (rt_mb_recv)。
- 恢复就绪: 挂起的线程在等待超时或获取到所需资源后,会重新回到就绪状态 (RT_THREAD_READY)。
- 关闭: 线程可以通过以下两种方式进入关闭状态 (RT_THREAD_CLOSE):
- 在挂起状态下被 rt_thread_delete/detach 函数删除或脱离;
- 运行状态的线程运行结束,自动调用 rt_thread_exit 函数。
下表详细列出了各种状态转换的触发条件和相关函数:
状态转换 | 触发条件 | 相关函数 |
---|---|---|
初始 -> 就绪 | 调用 rt_thread_startup() | rt_thread_startup() |
就绪 -> 运行 | 被调度器选中 | 调度器 |
运行 -> 挂起 | 调用延时函数、尝试获取资源失败、等待邮箱或消息队列等 | rt_thread_delay() 等 |
挂起 -> 就绪 | 超时、获取到所需资源 | 系统调度 |
挂起 -> 关闭 | 调用 rt_thread_delete() 或 rt_thread_detach() | rt_thread_delete/detach() |
运行 -> 关闭 | 线程运行结束,自动调用 rt_thread_exit() 或被其他线程调用 rt_thread_delete | rt_thread_exit() , rt_thread_delete() |
系统线程
前文已提到,系统线程是指由系统创建的线程,用户线程是由用户程序调用线程管理接口创建的线程,在 RT-Thread 内核中的系统线程有空闲线程和主线程
空闲线程
在 RT-Thread 操作系统中,空闲线程 (idle thread) 是一个由系统创建的、具有最低优先级的特殊线程。该线程始终处于就绪状态,当系统中没有其他就绪线程时,调度器会将 CPU 资源分配给空闲线程执行。空闲线程的主体通常是一个无限循环,并且永远不会被挂起。
空闲线程在 RT-Thread 中扮演着重要的角色,主要体现在以下几个方面:
-
资源回收: 当一个线程执行完毕后,系统会自动删除该线程。删除过程包括:首先,线程被从就绪队列中移除;其次,线程状态被标记为关闭,使其不再参与系统调度;最后,该线程被加入到 rt_thread_defunct 僵尸队列中(该队列用于存放资源未回收、状态为关闭的线程)。空闲线程负责回收这些僵尸线程所占用的系统资源,例如栈空间等。
-
用户钩子函数: RT-Thread 允许用户为空闲线程设置钩子函数。当空闲线程运行时,系统会自动调用这些钩子函数。这使得用户可以在空闲时间执行特定的操作,例如系统功耗管理、看门狗喂狗等任务。
-
系统保障: 为了保证系统的正常运行,空闲线程必须有执行的机会。这意味着其他线程不应长时间占用 CPU 资源而不释放,例如执行
while(1)
死循环。其他线程必须调用具有阻塞特性的函数,才能使空闲线程获得调度执行的机会。否则,例如线程删除、资源回收等系统操作将无法正确完成。
主线程
RT-Thread 系统启动时,会自动创建一个名为 “main” 的主线程。主线程的入口函数为 main_thread_entry()
,用户的应用程序入口函数 main()
实际上是在 main_thread_entry()
函数中被调用的。当系统调度器启动后,主线程开始运行,用户可以在 main()
函数中添加应用程序的初始化代码和主要逻辑。
线程的管理方式
前两节对 RT-Thread 线程的概念和工作机制进行了阐述,本节将深入探讨 RT-Thread 提供的线程相关接口,并通过部分源码分析,帮助读者在代码层面理解线程的操作原理。
下图概括了 RT-Thread 中线程的生命周期和主要操作,包括:创建/初始化、启动、运行和删除/脱离。
RT-Thread 支持两种类型的线程创建方式:动态线程和静态线程。动态线程使用 rt_thread_create()
函数创建,系统会从动态内存堆中分配线程栈空间和线程控制块。静态线程则使用 rt_thread_init()
函数初始化,线程栈空间和线程控制块由用户预先分配。动态线程依赖于系统动态堆 (heap) 的初始化。
创建和删除线程
创建动态线程
可以使用 rt_thread_create()
函数创建一个动态线程:
rt_thread_t rt_thread_create(const char* name,void (*entry)(void* parameter),void* parameter,rt_uint32_t stack_size,rt_uint8_t priority,rt_uint32_t tick);
调用此函数时,系统将从动态堆内存中分配一个线程控制块,并按照 stack_size
参数指定的字节数分配栈空间。分配的栈空间会按照 rtconfig.h
中定义的 RT_ALIGN_SIZE
进行内存对齐。rt_thread_create()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
name | 线程的名称。线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,超出部分会被截断。 |
entry | 线程的入口函数。 |
parameter | 传递给线程入口函数的参数。 |
stack_size | 线程栈大小,以字节为单位。 |
priority | 线程的优先级。优先级数值越小,优先级越高。优先级范围由 rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义决定。例如,如果系统支持 256 级优先级,则范围为 0 ~ 255,0 代表最高优先级。 |
tick | 线程的时间片大小。时间片单位为操作系统时钟节拍。当系统中存在相同优先级的就绪线程时,此参数决定线程一次调度可运行的最大时间长度。时间片耗尽时,调度器会选择下一个就绪的同优先级线程执行。 |
返回值 | 描述 |
thread | 线程创建成功,返回线程句柄。 |
RT_NULL | 线程创建失败。 |
删除动态线程
对于使用 rt_thread_create()
创建的动态线程,可以使用 rt_thread_delete()
函数从系统中将其删除:
rt_err_t rt_thread_delete(rt_thread_t thread);
调用此函数后,线程对象将被移出线程队列,并从内核对象管理器中删除。线程占用的堆栈空间也会被释放,回收的空间将重新用于其他内存分配。实际上, rt_thread_delete()
函数并非立即释放线程资源,而是将线程状态更改为 RT_THREAD_CLOSE
,并将线程放入 rt_thread_defunct
僵尸队列。真正的删除操作(释放线程控制块和线程栈)将在下一次执行空闲线程时完成。rt_thread_delete()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 要删除的线程句柄。 |
返回值 | 描述 |
RT_EOK | 删除线程成功。 |
-RT_ERROR | 删除线程失败。 |
注意:
rt_thread_create()
和rt_thread_delete()
函数只有在使能系统动态堆时才有效(即定义了宏RT_USING_HEAP
)。
初始化和脱离线程
初始化静态线程
对于静态线程,可以使用 rt_thread_init()
函数进行初始化:
rt_err_t rt_thread_init(struct rt_thread* thread,const char* name,void (*entry)(void* parameter), void* parameter,void* stack_start, rt_uint32_t stack_size,rt_uint8_t priority, rt_uint32_t tick);
静态线程的线程句柄和栈空间由用户提供。静态线程是指线程控制块和线程栈空间通常被定义为全局变量,在编译时即被确定和分配,内核不负责动态分配内存空间。用户提供的栈首地址需要按照系统要求进行对齐(例如,在 ARM 架构上需要进行 4 字节对齐)。rt_thread_init()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 线程句柄。此指针由用户提供,指向对应的线程控制块内存地址。 |
name | 线程的名称。线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,超出部分会被截断。 |
entry | 线程的入口函数。 |
parameter | 传递给线程入口函数的参数。 |
stack_start | 线程栈起始地址。 |
stack_size | 线程栈大小,以字节为单位。在大多数系统中,需要对栈空间地址进行对齐(例如,在 ARM 架构中,需要按 4 字节地址对齐)。 |
priority | 线程的优先级。优先级数值越小,优先级越高。优先级范围由 rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义决定。例如,如果系统支持 256 级优先级,则范围为 0 ~ 255,0 代表最高优先级。 |
tick | 线程的时间片大小。时间片单位为操作系统时钟节拍。当系统中存在相同优先级的就绪线程时,此参数决定线程一次调度可运行的最大时间长度。时间片耗尽时,调度器会选择下一个就绪的同优先级线程执行。 |
返回值 | 描述 |
RT_EOK | 线程初始化成功。 |
-RT_ERROR | 线程初始化失败。 |
脱离静态线程
对于使用 rt_thread_init()
初始化的线程,可以使用 rt_thread_detach()
函数将其从线程队列和内核对象管理器中脱离:
rt_err_t rt_thread_detach (rt_thread_t thread);
rt_thread_detach()
函数与 rt_thread_delete()
函数对应。 rt_thread_delete()
函数操作由 rt_thread_create()
创建的线程句柄,而 rt_thread_detach()
函数操作由 rt_thread_init()
初始化的线程控制块。 线程本身不应调用此接口脱离自身。rt_thread_detach()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 线程句柄。此句柄应由 rt_thread_init() 初始化。 |
返回值 | 描述 |
RT_EOK | 线程脱离成功。 |
-RT_ERROR | 线程脱离失败。 |
线程的启动
无论是动态创建还是静态初始化的线程,在创建/初始化完成后,都需要调用 rt_thread_startup()
函数使其进入就绪态:
rt_err_t rt_thread_startup(rt_thread_t thread);
调用此函数后,线程状态将被更改为就绪状态,并被放入相应优先级的就绪队列中等待调度。如果新启动的线程优先级高于当前线程的优先级,则会立即切换到该线程执行。rt_thread_startup()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 线程句柄。 |
返回值 | 描述 |
RT_EOK | 线程启动成功。 |
-RT_ERROR | 线程启动失败。 |
获取当前线程
在程序运行过程中,同一段代码可能被多个线程执行。可以使用 rt_thread_self()
函数获取当前正在执行的线程的线程句柄:
rt_thread_t rt_thread_self(void);
rt_thread_self()
函数的返回值说明如下:
返回值 | 描述 |
---|---|
thread | 当前运行的线程句柄。 |
RT_NULL | 失败,调度器尚未启动。 |
线程让出处理器资源
当线程的时间片耗尽或主动要求让出处理器资源时,可以使用 rt_thread_yield()
函数让出处理器资源:
rt_err_t rt_thread_yield(void);
调用此函数后,当前线程会被从其所在优先级的就绪队列中移除,并添加到该优先级队列的末尾,然后调度器会进行线程上下文切换(如果当前优先级只有一个线程,则线程继续执行,不进行上下文切换)。
rt_thread_yield()
和 rt_schedule()
函数类似,但在有相同优先级的其他就绪线程存在时,行为却截然不同。执行 rt_thread_yield()
函数后,当前线程会被换出,而下一个相同优先级的就绪线程将会被执行。而执行 rt_schedule()
函数后,当前线程不一定被换出,即使被换出,也不会被放到就绪线程链表的末尾,而是在系统中选取优先级最高的就绪线程执行(如果没有优先级更高的线程存在,则继续执行当前线程)。
线程睡眠
线程可以使用以下三个函数接口进行睡眠:
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
这三个函数接口的作用相同,调用它们会使当前线程挂起一段时间,待指定时间到达后,线程会被唤醒并再次进入就绪状态。 rt_thread_sleep/delay/mdelay()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
tick /ms | 线程睡眠时间。sleep /delay 的单位为 OS Tick, mdelay 的单位为 ms。 |
返回值 | 描述 |
RT_EOK | 操作成功。 |
线程的挂起与恢复
当线程调用 rt_thread_delay()
函数时,线程会主动挂起;当调用 rt_sem_take()
、rt_mb_recv()
等函数时,若资源不可用也会导致线程挂起。处于挂起状态的线程,如果等待资源超时(超过设定的等待时间),该线程将不再等待资源,并返回就绪状态;或者当其他线程释放了该线程所等待的资源时,该线程也会返回就绪状态。
挂起线程
可以使用 rt_thread_suspend()
函数挂起线程:
rt_err_t rt_thread_suspend (rt_thread_t thread);
rt_thread_suspend()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 线程句柄。 |
返回值 | 描述 |
RT_EOK | 线程挂起成功。 |
-RT_ERROR | 线程挂起失败,因为线程状态不是就绪态。 |
注意:
rt_thread_suspend()
函数只能用于挂起当前线程(即自己挂起自己),不能在线程 A 中尝试挂起线程 B。并且,在挂起线程自身后,需要立即调用rt_schedule()
函数进行手动线程上下文切换。 否则,可能会导致系统不稳定。
恢复线程
可以使用 rt_thread_resume()
函数使挂起的线程重新进入就绪态:
rt_err_t rt_thread_resume (rt_thread_t thread);
rt_thread_resume()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 线程句柄。 |
返回值 | 描述 |
RT_EOK | 线程恢复成功。 |
-RT_ERROR | 线程恢复失败,因为线程状态不是挂起状态 (RT_THREAD_SUSPEND )。 |
线程控制
rt_thread_control()
函数可以用于对线程进行动态控制,例如修改线程的优先级:
rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);
rt_thread_control()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
thread | 线程句柄。 |
cmd | 控制命令。 |
arg | 控制参数。 |
返回值 | 描述 |
RT_EOK | 控制执行成功。 |
-RT_ERROR | 控制执行失败。 |
当前 cmd
参数支持的命令包括:
RT_THREAD_CTRL_CHANGE_PRIORITY
: 动态更改线程的优先级。RT_THREAD_CTRL_STARTUP
: 启动线程,等同于rt_thread_startup()
函数调用。RT_THREAD_CTRL_CLOSE
: 关闭线程,等同于rt_thread_delete()
或rt_thread_detach()
函数调用。
空闲线程钩子
空闲线程钩子函数是指在空闲线程执行时自动执行的函数。可以使用 rt_thread_idle_sethook()
函数设置空闲线程钩子,使用 rt_thread_idle_delhook()
函数删除空闲线程钩子:
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));
rt_thread_idle_sethook()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
hook | 设置的钩子函数。 |
返回值 | 描述 |
RT_EOK | 设置成功。 |
-RT_EFULL | 设置失败。 |
rt_thread_idle_delhook()
函数的参数和返回值说明如下:
参数 | 描述 |
---|---|
hook | 删除的钩子函数。 |
返回值 | 描述 |
RT_EOK | 删除成功。 |
-RT_ENOSYS | 删除失败。 |
注意:
空闲线程是始终处于就绪态的线程。设置的钩子函数必须保证在任何时刻都不会处于挂起状态,例如不能使用
rt_thread_delay()
、rt_sem_take()
等可能导致线程挂起的函数。并且,由于malloc
、free
等内存管理函数内部使用信号量进行临界区保护,在钩子函数内部也不允许调用此类函数。
调度器钩子
系统上下文切换是系统中最为普遍的事件。可以使用 rt_scheduler_sethook()
函数设置一个调度器钩子函数,在每次线程切换时会被调用:
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));
rt_scheduler_sethook()
函数的参数说明如下:
参数 | 描述 |
---|---|
hook | 用户自定义的钩子函数指针。 |
用户自定义的钩子函数 hook()
的声明如下:
void hook(struct rt_thread* from, struct rt_thread* to);
hook()
函数的参数说明如下:
参数 | 描述 |
---|---|
from | 要切换出的线程控制块指针。 |
to | 要切换到的线程控制块指针。 |
注意:
请谨慎编写钩子函数,任何不当的操作都可能导致系统运行异常。在调度器钩子函数中,通常不允许调用任何系统 API,更不允许导致当前运行的上下文挂起。
线程的应用示例
以韦东山老师经常讲的小明妈妈的例子演示
#include <rtthread.h>
#include <stdio.h>#define THREAD_STACK_SIZE 1024
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 10 //增加时间片/* 线程入口函数声明 */
void feeding_thread_entry(void *parameter);
void working_thread_entry(void *parameter);/* 创建线程句柄 */
static rt_thread_t feeding_thread = RT_NULL;
static rt_thread_t working_thread = RT_NULL;/* 主函数 */
int main(void)
{/* 创建喂饭线程 */feeding_thread = rt_thread_create("feeding",feeding_thread_entry,RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY,THREAD_TIMESLICE);if (feeding_thread == RT_NULL){rt_kprintf("Failed to create feeding thread.\n");return -1;}rt_thread_startup(feeding_thread);/* 创建工作线程 */working_thread = rt_thread_create("working",working_thread_entry,RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY,THREAD_TIMESLICE);if (working_thread == RT_NULL){rt_kprintf("Failed to create working thread.\n");return -1;}rt_thread_startup(working_thread);return 0;
}/* 喂饭线程入口函数 */
void feeding_thread_entry(void *parameter)
{while (1){rt_kprintf("小明妈妈:喂小明吃一口菜\n");rt_thread_mdelay(250);rt_kprintf("小明妈妈:喂小明吃一口饭\n");rt_thread_mdelay(250);}
}/* 工作线程入口函数 */
void working_thread_entry(void *parameter)
{while (1){rt_kprintf("小明妈妈:开始读取工作邮件\n");rt_thread_mdelay(250);rt_kprintf("小明妈妈:回复工作邮件\n");rt_thread_mdelay(250);}
}