线程控制块 PCB,记录线程的数据。
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_ubase_t init_tick; /* 线程初始化计数值 */
rt_ubase_t remaining_tick; /* 线程剩余计数值 */
struct rt_timer thread_timer; /* 内置线程定时器 */
void (*cleanup)(struct rt_thread *tid); /* 线程退出清理函数。在线程退出的时候,被idle线程回调,执行用户设置的清理现场等工作。 */
rt_uint32_t user_data; /* 用户私有数据*/
};
typedef struct rt_thread *rt_thread_t;RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中, 当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
线程调度机制
RT-Thread 使用抢占式调度和时间片调度。
(1)不同优先级,抢占
高优先级线程可以抢占低优先级线程。
由 rtconfig.h 中定义的 RT_THREAD_PRIORITY_MAX 宏指定优先级范围。
最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。
空闲线程使用最低优先级,用户不要使用。
(2)相同优先级,时间片
对于最高优先级的多个任务,采用时间片轮转调度。
线程能运行多久,取决于线程的时间片参数设置,时间单位为系统节拍。
FreeRTOS 与 RT-Thread 调度机制区别:
线程工作状态
线程状态转换图:

线程操作
线程入口函数:
void thread_entry(void* paramenter)
{
while (1)
{
}
}线程中必须有让出 CPU 的动作,否则低优先级的线程无法执行。
若不是循环的,执行结束后,线程会被系统自动删除或脱离。
动态创建与删除
必须使能 RT_USING_HEAP
// 创建线程,返回 RT_NULL 表示创建失败
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); // 时间片调度时运行时长
// 删除线程,返回 RT_EOK 成功,返回 RT_ERROR 失败
rt_err_t rt_thread_delete(rt_thread_t thread);真正的删除操作在空闲线程中。
注意:线程不能删除自己。
示例:
#include <rtthread.h>
#define THREAD_1_PRIORITY 25
#define THREAD_1_STACK_SIZE 512
#define THREAD_1_TIMESLICE 5
static rt_thread_t tid1 = RT_NULL;
/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter)
{
rt_uint32_t count = 0;
for (count = 0; count < 10 ; count++)
{
/* 线程 1 采用低优先级运行 */
rt_kprintf("thread1 count: %d\n", count);
rt_thread_mdelay(500);
}
rt_kprintf("thread1 exit\n");
/* 线程 1 运行结束后也将自动被系统脱离 */
}
#if defined(RT_VERSION_CHECK) && (RTTHREAD_VERSION >= RT_VERSION_CHECK(5, 0, 1))
rt_align(RT_ALIGN_SIZE)
#else
ALIGN(RT_ALIGN_SIZE)
#endif
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程 2 入口 */
static void thread2_entry(void *param)
{
rt_uint32_t count = 0;
/* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */
for (count = 0; count < 10 ; count++)
{
/* 线程 2 打印计数值 */
rt_kprintf("thread2 count: %d\n", count);
}
rt_kprintf("thread2 exit\n");
/* 线程 2 运行结束后也将自动被系统脱离 */
}静态初始化与脱离
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);脱离:从调度器中注销这个线程,但是它的内存资源(栈、线程控制块)必须用户手动释放,否则就变成了僵尸线程。
rt_err_t rt_thread_detach(rt_thread_t thread);注意:线程不能脱离自己。
启动线程
线程创建或初始化后为初始态,启动线程让它进入就绪态。
rt_err_t rt_thread_startup(rt_thread_t thread);挂起和恢复线程
只有线程自己能挂起自己,并且挂起后需要立刻调用 rt_schedule() 函数进行手动的线程上下文切换。
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_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);线程让出处理器资源
rt_err_t rt_thread_yield(void);让出后,调度器会选择相同优先级的就绪线程,因此,让出后低优先级的线程仍不能执行。
线程睡眠
高优先级的任务进入阻塞态,低优先级的线程才能执行。
睡眠是让线程进入阻塞态的一种方法。
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick); // 和 rt_thread_sleep 完全一样,只是为了兼容不同开发者的习惯。
rt_err_t rt_thread_mdelay(rt_int32_t ms);获取当前线程
同一个线程入口函数,可能被多个线程使用。
线程入口函数中可获取当前的线程,然后执行对应操作。
rt_thread_t rt_thread_self(void);调度器钩子函数
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));from:系统切出的 PCB 指针
to:系统切入的 PCB 指针
注意:
不要调用内核 API
不能阻塞
钩子函数影响着系统的实时性,务必认真编写,否则会导致系统运行异常。
系统线程
主线程 main
如果系统中开启了创建主线程,系统调度器启动后,main 线程开始运行。

空闲线程 IDLE
优先级最低
永远为就绪态
作用:
释放 t_thread_defunct 僵尸队列(资源未回收、处于关闭状态的线程队列)中的线程的资源。
调用钩子函数:用户自定义,比如系统指示灯、功耗管理、看门狗喂狗等。
注意,系统架构时,必须保证空闲线程有机会执行。
设置和删除空闲线程钩子函数:
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));