在日常生活中,我们通常通过将一个复杂的任务拆解为多个简单的小问题来逐步解决,最终完成整个任务。在多线程操作系统中,这一思路同样适用。为了实现复杂应用的实时性和高效性,开发人员需要将大任务拆分成多个独立、可调度的、可并发执行的子任务。这些子任务通过合理的协调与调度,最终确保整个系统满足实时性要求和时间约束。
以嵌入式系统中的数据采集和显示为例,我们可以将一个完整的任务分解成两个或多个子任务,从而实现更高效、更灵活的处理方式。例如,假设系统需要从传感器采集数据并将数据通过显示屏呈现给用户。在多线程实时操作系统中,可以采用以下方式:
-
数据采集任务:这是一个高频率、低延迟的任务,其目标是持续从传感器读取数据,并将其存储到共享内存中。这个任务可能会采用一个循环,持续检查并读取传感器的数据,同时避免长时间阻塞,确保系统的高效响应。
-
数据显示任务:这个任务则是周期性地读取共享内存中的数据,并将其更新到显示屏上。与数据采集任务相比,显示任务的周期较长,可以稍微放宽实时性要求,但依然需要保证在一定时间内完成显示更新。
在这个多线程实时系统中,任务的分解并不是单纯的工作拆分,而是通过合理的同步机制(如信号量、互斥锁等),让各个任务能够高效地协调工作。共享内存作为任务间通信的媒介,可以通过适当的同步手段来保证数据的一致性和完整性。任务的调度则根据各自的优先级、周期和资源需求进行合理分配,确保系统在多任务并发执行时仍能保持实时性和稳定性。
在 RT-Thread 中,与上述子任务对应的程序实体就是线程,线程是实现任务的载体,它是 RT-Thread 中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级,重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。
当线程运行时,它会认为自己是以独占 CPU 的方式在运行,线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。
线程管理的功能和特点
RT-Thread 线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除,如下图所示,每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。
RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。
当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的 CPU 使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。
如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。
当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。
1. 系统线程和用户线程
-
系统线程:由 RT-Thread 内核自动创建并管理,通常用于系统级别的任务或内部处理。系统线程在系统启动时或操作系统初始化过程中由内核创建。它们的生命周期由操作系统控制,主要负责系统的初始化、底层任务处理、调度器等。
-
用户线程:由应用程序创建,通常用于执行具体的应用逻辑。用户线程的生命周期由应用程序控制,应用程序可以根据需要创建、启动和删除这些线程。它们依赖于操作系统的调度机制,但由用户定义具体的任务和行为。
2. 线程管理和调度
线程管理的核心功能包括:
-
线程创建:无论是系统线程还是用户线程,都会通过线程创建函数(例如
rt_thread_create
)从内核的线程池中分配资源,包括线程控制块(TCB)、栈空间和线程的调度信息。 -
线程调度:RT-Thread 使用优先级调度算法管理线程,支持时间片轮转、优先级抢占等调度方式。当有多个线程需要执行时,RT-Thread 根据线程的优先级和状态选择合适的线程执行。
-
线程删除:当线程不再需要时,操作系统会释放线程所占用的资源,删除线程并将其从线程管理列表中移除。用户线程通过
rt_thread_delete
删除,系统线程的删除通常由内核自行管理。
3. 线程的属性
每个线程都拥有若干重要的属性,确保系统能够有效地管理和调度它们:
-
线程控制块(TCB):每个线程都有一个控制块,用于保存线程的状态信息和调度信息。TCB 包括线程的堆栈指针、线程的优先级、状态、入口函数、栈空间等。线程控制块是 RT-Thread 内核对线程的核心管理结构。
-
线程栈:每个线程都有一个独立的栈空间,用于存储线程的局部变量、函数调用信息等。栈的大小由线程创建时指定,并且必须满足该线程执行的函数调用需求。栈溢出会导致程序崩溃,因此栈空间的合理分配至关重要。
-
线程入口函数:每个线程都有一个入口函数(即线程启动时执行的函数)。该函数由用户在创建线程时指定,是线程的主要执行内容。
-
线程优先级:RT-Thread 支持优先级调度,每个线程都可以设置优先级。线程的优先级决定了线程在调度时的执行顺序,优先级较高的线程会优先获得 CPU 时间片。优先级可以动态调整,以适应不同的任务需求。
-
线程状态:线程状态可以包括 就绪、运行、阻塞 等不同的状态。线程的状态决定了它在调度中的行为和生命周期。RT-Thread 会根据线程的状态来决定是否将其加入就绪队列或进行调度。
4. 线程的生命周期
一个线程的生命周期包括以下几个主要阶段:
-
创建阶段:当应用程序调用
rt_thread_create
创建线程时,操作系统为线程分配资源并初始化线程的控制块和栈空间。此时线程处于 就绪 状态,等待 CPU 时间片。 -
执行阶段:线程进入 运行 状态,执行其入口函数中的任务。此时,操作系统的调度器会根据线程的优先级来决定哪个线程将运行。
-
阻塞阶段:线程可能会因为等待某些事件(如信号量、消息队列等)而进入 阻塞 状态。在此期间,操作系统会将该线程从就绪队列中移除,直到它满足条件才能恢复到就绪状态。
-
删除阶段:当线程的任务完成或被不再需要时,它会进入 删除 状态,操作系统释放线程的所有资源,并将其从线程管理列表中移除。
5. 线程对象容器
在 RT-Thread 中,所有的线程对象都会存储在内核对象容器中。该容器用于管理系统中所有的线程对象,确保在系统运行期间能够对线程进行正确的管理和调度。当线程被删除时,它会从线程对象容器中移除,释放相关资源。
#include <rtthread.h>
#define THREAD_STACK_SIZE 1024
#define THREAD_PRIORITY 25
static rt_thread_t tid;
static rt_uint8_t stack[THREAD_STACK_SIZE];
void thread_entry(void *parameter)
{
while (1)
{
rt_kprintf("This is a user thread.\n");
rt_thread_mdelay(500); // 每500ms打印一次
}
}
int main(void)
{
// 创建一个用户线程
tid = rt_thread_create("user_thread", thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, 10);
if (tid != RT_NULL)
{
rt_thread_startup(tid); // 启动线程
}
return 0;
}