没志青年
发布于 2025-07-28 / 18 阅读
0

RT-Thread 线程管理

线程控制块 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 调度机制区别:

FreeRTOS

RT-Thread

任务优先级

数字越大优先级越高

数字越小优先级越高

抢占式调度

虽然不是默认开启的,但这是项目的必选项。

源码中写死,默认开启。

时间片调度

默认不开启,可在配置文件中启用。

源码中写死,默认开启。

线程工作状态

状态

说明

初始态

线程刚开始创建但还没开始运行。

就绪态

线程准备好了,可以执行了。

(线程按照优先级排队执行,一旦当前线程让出 CPU,内核调度器会寻找最高优先级的就绪线程执行。)

运行态

处于运行状态的线程是当前被 CPU 执行的线程。只有一个线程可以在一个 CPU 核心上处于运行状态。

挂起态

也称阻塞态。

(1)线程主动延时。

(2)线程因等待某些事件或资源(如信号量、互斥锁、消息队列等)而阻塞。

终止态

线程正常结束执行或被强制终止,不再参与线程调度。

线程状态转换图:

线程操作

线程入口函数:

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));