4、线程管理基础:创建线程、线程入口函数、线程参数传递、线程删除与动态创建
在嵌入式实时操作系统(RTOS)中,线程(Thread)是任务调度的最小执行单元。Azure RTOS ThreadX 以其卓越的实时性和紧凑的内核设计,为开发者提供了高效且灵活的线程管理机制。本章将深入剖析 ThreadX 线程管理的四大核心操作:创建线程、编写线程入口函数、传递线程参数以及删除线程。我们将从内核数据结构出发,结合代码实例与工程实践,帮助您建立对 ThreadX 线程生命周期的完整认知,为后续的信号量、队列等同步机制打下坚实基础。
上图清晰地展示了 ThreadX 线程从创建到删除的完整生命周期。线程并非孤立存在,其创建、运行、参数传递与销毁均围绕内核中的线程控制块(TCB)展开。接下来,我们将逐一拆解每个环节的底层原理与编码实践。
4.1 核心原理:线程控制块(TCB)与栈空间
在 ThreadX 中,每个线程都由两部分核心资源构成:线程控制块(TCB)和线程栈。TCB 是一个结构体,由内核维护,包含了线程的状态、优先级、入口函数指针、栈指针、时间片、事件标志等关键信息。线程栈则是一块连续的内存区域,用于保存线程的上下文(CPU 寄存器值)、局部变量和函数调用信息。
创建线程的本质,就是向内核注册一个 TCB,并为其分配一块栈空间。ThreadX 的调度器通过遍历就绪线程列表,根据优先级和时间片算法,选择下一个要运行的线程,并将其上下文从栈中恢复。因此,TCB 和栈的初始化质量直接决定了系统的稳定性。
4.2 操作流程:创建线程与入口函数编写
创建线程的 API 为 tx_thread_create,其原型如下:
UINT tx_thread_create(
TX_THREAD *thread_ptr, // 指向 TCB 的指针
CHAR *name_ptr, // 线程名称(调试用)
VOID (*entry_function)(ULONG), // 入口函数指针
ULONG entry_input, // 传递给入口函数的参数
VOID *stack_start, // 栈起始地址
ULONG stack_size, // 栈大小(字节)
UINT priority, // 优先级(0-31,0最高)
UINT preempt_threshold, // 抢占阈值
ULONG time_slice, // 时间片(0表示不轮转)
UINT auto_start, // 是否自动启动(TX_AUTO_START / TX_DONT_START)
UINT *created_thread_count // 可选:指向线程计数器的指针
);
入口函数必须遵循固定格式:VOID my_thread_entry(ULONG initial_input)。该函数通常包含一个无限循环,用于执行周期性任务或事件等待。线程函数不应返回,否则会导致系统错误。
标准创建流程:
- 定义
TX_THREAD结构体变量和栈数组。 - 调用
tx_thread_create,传入参数。 - 检查返回值,若为
TX_SUCCESS则创建成功。 - 若
auto_start设为TX_DONT_START,后续需调用tx_thread_resume启动。
TX_THREAD my_thread;
UCHAR my_thread_stack[4096];
VOID my_thread_entry(ULONG input) {
while(1) {
// 任务逻辑
tx_thread_sleep(10); // 延时
}
}
UINT create_my_thread() {
UINT status = tx_thread_create(
&my_thread, "My Thread", my_thread_entry, 0,
my_thread_stack, sizeof(my_thread_stack),
15, 15, 0, TX_AUTO_START, TX_NULL);
return status;
}
4.3 案例拆解:线程参数传递的多种方式
线程入口函数仅接收一个 ULONG 类型的参数。这个参数是线程与外部世界交互的初始通道。虽然只有一个参数,但通过指针转换,可以传递任意复杂的数据结构。
| 传递方式 | 适用场景 | 示例 |
|---|---|---|
| 直接传值 | 传递简单标志、索引、计数器 | tx_thread_create(..., 5, ...) 表示线程 ID 为 5 |
| 传递结构体指针 | 传递配置参数、初始化数据 | 将 APP_CFG* 强制转换为 ULONG |
| 传递字符串指针 | 传递设备名称、文件路径 | (ULONG)"UART1" |
| 传递函数指针 | 回调机制 | 将回调函数地址作为参数传入 |
实战场景:假设我们需要创建两个 LED 闪烁线程,一个闪烁频率为 1Hz,另一个为 2Hz。我们可以定义一个结构体,包含 GPIO 端口号和延时时间,然后将结构体指针作为参数传入。
typedef struct {
UINT gpio_port;
UINT delay_ticks;
} LED_CFG;
LED_CFG led1_cfg = {1, 100}; // 100 ticks @ 10ms = 1s
LED_CFG led2_cfg = {2, 50}; // 50 ticks = 0.5s
VOID led_thread_entry(ULONG arg) {
LED_CFG *cfg = (LED_CFG*)arg;
while(1) {
// 翻转 GPIO
tx_thread_sleep(cfg->delay_ticks);
}
}
// 创建两个线程
tx_thread_create(&led1_tcb, "LED1", led_thread_entry, (ULONG)&led1_cfg, ...);
tx_thread_create(&led2_tcb, "LED2", led_thread_entry, (ULONG)&led2_cfg, ...);
4.4 线程删除与动态创建:资源回收与灵活部署
线程删除使用 tx_thread_delete API。该函数会终止线程的执行,并释放其 TCB 和栈空间(如果这些内存是由应用程序管理的)。但需要注意,线程不能删除自身,只能由其他线程或定时器上下文中删除。
删除流程:
- 确保目标线程已终止(即其入口函数已返回)或处于挂起状态。
- 调用
tx_thread_terminate强制终止(如果还在运行)。 - 调用
tx_thread_delete释放资源。
动态创建则是指在系统运行过程中,根据实际需求(如新连接接入、任务动态加载)创建线程。这通常与字节池(Byte Pool)配合使用,从池中分配 TCB 和栈内存,删除时再释放回池中,避免内存碎片。
TX_BYTE_POOL my_pool;
TX_THREAD *dynamic_thread_ptr;
UCHAR *stack_ptr;
// 初始化字节池
tx_byte_pool_create(&my_pool, "Dynamic Pool", pool_memory, POOL_SIZE);
// 动态创建线程
UINT create_dynamic_thread() {
// 从池中分配 TCB 和栈
tx_byte_allocate(&my_pool, (VOID**)&dynamic_thread_ptr, sizeof(TX_THREAD), TX_NO_WAIT);
tx_byte_allocate(&my_pool, (VOID**)&stack_ptr, STACK_SIZE, TX_NO_WAIT);
// 创建线程
UINT status = tx_thread_create(dynamic_thread_ptr, "Dyn", dyn_entry, 0,
stack_ptr, STACK_SIZE, 20, 20, 0, TX_AUTO_START, TX_NULL);
return status;
}
// 删除动态线程
UINT delete_dynamic_thread() {
tx_thread_terminate(dynamic_thread_ptr);
tx_thread_delete(dynamic_thread_ptr);
// 释放内存回池
tx_byte_release(dynamic_thread_ptr);
tx_byte_release(stack_ptr);
}
4.5 常见误区与总结复盘
在实际开发中,线程管理环节容易出现以下几类问题:
- 栈溢出:栈大小分配不足,导致内存踩踏。建议使用 ThreadX 提供的
tx_thread_stack_error_notify回调进行监控。 - 优先级反转:未合理设置抢占阈值或使用优先级继承,导致高优先级任务被低优先级任务阻塞。
- 删除未终止的线程:直接调用
tx_thread_delete而线程仍在运行,会导致系统崩溃。必须先终止。 - 参数指针失效:传递了指向局部变量的指针,线程启动后该变量已销毁。
- 动态内存泄漏:删除线程后未释放 TCB 和栈内存,长期运行导致内存耗尽。
tx_thread_create 或 tx_thread_delete 是不允许的。这些 API 只能在任务上下文中调用。若需在 ISR 中触发线程管理,应通过信号量或事件标志组通知一个高优先级线程来执行。
总结复盘:线程管理是 RTOS 应用的地基。创建线程时,要像“配置硬件外设”一样严谨:明确栈大小、优先级、时间片。参数传递时,要遵循“指针生命周期大于线程生命周期”的原则。动态创建与删除是嵌入式系统灵活性的体现,但必须以严谨的内存管理为前提。建议在项目初期就制定线程资源规划表,明确每个线程的 TCB、栈来源(静态或动态池),并建立统一的错误处理机制。
掌握本章内容后,您应能熟练地在 ThreadX 中创建、配置、传递参数并安全地销毁线程。下一阶段,我们将探讨线程之间的同步与通信机制,届时您将看到线程管理如何与信号量、互斥锁等内核对象协同工作,构建出健壮的实时应用系统。