Notes on learning RT-Thread——线程的管理方式

文章目录

  • 创建和删除线程
  • 初始化和脱离线程
  • 启动线程
  • 获得当前线程
  • 使线程让出处理器资源
  • 使线程睡眠
  • 挂起和恢复线程
  • 控制线程
  • 设置和删除空闲钩子
  • 设置调度器钩子

创建和删除线程

一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。可以通过如下的接口创建一个动态线程:

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

调用这个函数时,系统会从动态堆内存中分配一个线程句柄以及按照参数中指定的栈大小从动态堆内存中分配相应的空间。分配出来的栈空间是按照 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 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
返回 ——
thread 线程创建成功,返回线程句柄
RT_NULL 线程创建失败

对于一些使用 rt_thread_create() 创建出来的线程,当不需要使用,或者运行出错时,我们可以使用下面的函数接口来从系统中把线程完全删除掉:

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_USING_HEAP 宏定义已经定义了)。

初始化和脱离线程

线程的初始化可以使用下面的函数接口完成,来初始化静态线程对象:

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 线程栈大小,单位是字节
priority 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0~255,数值越小优先级越高,0 代表最高优先级
tick 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
返回 ——
RT_EOK 线程创建成功
RT_ERROR 线程创建失败

对于用 rt_thread_init() 初始化的线程,使用 rt_thread_detach() 将使线程对象在线程队列和内核对象管理器中被脱离。线程脱离函数如下:

rt_err_t rt_thread_detach (rt_thread_t thread);

线程脱离接口 rt_thread_detach() 的参数和返回值见下表:

参数 描述
thread 线程句柄,它应该是由 rt_thread_init 进行初始化的线程句柄。
返回 ——
RT_EOK 线程脱离成功
-RT_ERROR 线程脱离失败

这个函数接口是和 rt_thread_delete() 函数相对应的, rt_thread_delete() 函数操作的对象是 rt_thread_create() 创建的句柄,而 rt_thread_detach() 函数操作的对象是使用 rt_thread_init() 函数初始化的线程控制块。同样,线程本身不应调用这个接口脱离线程本身。

启动线程

创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化 / 创建成功后调用下面的函数接口让该线程进入就绪态:

rt_err_t rt_thread_startup(rt_thread_t thread);

当调用这个函数时,将把线程的状态更改为就绪状态,并放到相应优先级队列中等待调度。如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。线程启动接口 rt_thread_startup() 的参数和返回值见下表:

参数 描述
thread 线程句柄
返回 ——
RT_EOK 线程启动成功
-RT_ERROR 线程起动失败

获得当前线程

在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄:

rt_thread_t rt_thread_self(void);

该接口的返回值见下表:

返回 描述
thread 当前运行的线程句柄
RT_NULL 失败,调度器还未启动

使线程让出处理器资源

当前线程的时间片用完或者该线程主动要求让出处理器资源时,它将不再占有处理器,调度器会选择相同优先级的下一个线程执行。线程调用这个接口后,这个线程仍然在就绪队列中。线程让出处理器使用下面的函数接口:

rt_err_t rt_thread_yield(void);

调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。
rt_thread_yield() 函数和 rt_schedule() 函数比较相像,但在有相同优先级的其他就绪态线程存在时,系统的行为却完全不一样。执行 rt_thread_yield() 函数后,当前线程被换出,相同优先级的下一个就绪线程将被执行。而执行 rt_schedule() 函数后,当前线程并不一定被换出,即使被换出,也不会被放到就绪线程链表的尾部,而是在系统中选取就绪的优先级最高的线程执行(如果系统中没有比当前线程优先级更高的线程存在,那么执行完 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 的传入参数 tick 以 1 个 OS Tick 为单位 ;mdelay 的传入参数 ms 以 1ms 为单位;
返回 ——
RT_EOK 操作成功

挂起和恢复线程

当线程调用 rt_thread_delay() 时,线程将主动挂起;当调用 rt_sem_take(),rt_mb_recv() 等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。
线程挂起使用下面的函数接口:

rt_err_t rt_thread_suspend (rt_thread_t thread);

线程挂起接口 rt_thread_suspend() 的参数和返回值见下表:

参数 描述
thread 线程句柄
返回 — —
RT_EOK 线程挂起成功
-RT_ERROR 线程挂起失败,因为该线程的状态并不是就绪状态

注意事项

通常不应该使用这个函数来挂起线程本身,如果确实需要采用 rt_thread_suspend() 函数挂起当前任务,需要在调用 rt_thread_suspend() 函数后立刻调用 rt_schedule() 函数进行手动的线程上下文切换。用户只需要了解该接口的作用,不推荐使用该接口。

恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。线程恢复使用下面的函数接口:

rt_err_t rt_thread_resume (rt_thread_t thread);

线程恢复接口 rt_thread_resume() 的参数和返回值见下表:

参数 描述
thread 线程句柄
返回 ——
RT_EOK 线程恢复成功
-RT_ERROR 线程恢复失败,因为该个线程的状态并不是 RT_THREAD_SUSPEND 状态

控制线程

当需要对线程进行一些其他控制时,例如动态更改线程的优先级,可以调用如下函数接口:

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_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() 等可能会导致线程挂起的函数都不能使用。

设置调度器钩子

在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用:

void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));

设置调度器钩子函数的输入参数如下表所示:

函数参数 描述
hook 表示用户定义的钩子函数指针

钩子函数 hook() 的声明如下:

void hook(struct rt_thread* from, struct rt_thread* to);

调度器钩子函数 hook() 的输入参数如下表所示:

函数参数 描述
from 表示系统所要切换出的线程控制块指针
to 表示系统所要切换到的线程控制块指针

注意事项

请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。

本文转载网址

你可能感兴趣的:(RT-Thread,STM32)