生命周期¶
线程(thread) 是一个用于处理由于太复杂或耗时太长而不便于在 ISR 内完成的工作的内核对象。
概念¶
应用程序可以定义任意数量的线程,且可以使用线程创建时分配给该线程的 线程标识符(thread id) 来引用该线程。
线程的注关键属性包括:
- 栈区域:一段用于线程控制块(
struct k_thread
)和线程栈的内存区域。 栈空间的 大小 可以被裁剪,以适应线程处理的实际需求。 - 入口点函数:线程启动时调用的函数。该函数最多能接收 3 个参数。
- 调度优先级:指示内核的调度器如何给该线程分配 CPU 时间。(参考 调度。)
- 线程选项:允许内核在特定场景中对该线程做某种特殊处理。(参考 线程的选项。)
- 启动时延: 指定线程在启动前需要等待的时间。
线程的创建¶
线程必须先创建、再使用。创建线程时,内核将初始化线程栈区域的控制块区域以及栈的尾部。栈区域的其它部分通常都是未初始化的。
如果指定的启动时延是 K_NO_WAIT
,内核将立即启动线程。您也可以指定一个超时时间,让内核延迟启动该线程。例如,让线程需要使用的设备就绪后再启动线程。
如果延迟启动的线程还未启动,内核可以取消该线程。如果线程已经启动了,则内核在尝试取消它时不会有如何效果。如果延迟启动的线程被成功地取消了,它必须被再次创建后才能再次使用。
线程的正常结束¶
线程一旦启动,它通常会一直运行下去。不过,线程也可以从它的入口点函数中返回,从而同步结束执行。这种结束方式叫做 正常结束(terminaltion)。
正常结束的线程需要在返回前释放它所拥有的共享资源(例如互斥量、动态分配的内存)。内核 不会 自动回收这些资源。
注解
当前内核不会做任何补偿。应用程序可以重新创建正常结束的线程。
线程的异常终止¶
线程可以通过 异常终止 (aborting) 异步结束其执行。如果线程触发了一个致命错误(例如引用了空指针),内核将自动终止该线程。
其它线程(或线程自己)可以调用 k_thread_abort()
终止一个线程。不过,更优雅的做法是向线程发送一个信号,让该线程自己结束执行。
线程终止时,内核不会自动回收该线程拥有的共享资源。
注解
当前内核不会做任何补偿。应用程序可以重新创建异常终止的线程。
线程的挂起¶
如果一个线程被挂起,它将在一段不确定的时间内暂停执行。函数 k_thread_suspend()
可以用于挂起包括调用线程在内的所有线程。对已经挂起的线程再次挂起时不会产生任何效果。
线程一旦被挂起,它将一直不能被调度,除非另一个线程调用 k_thread_resume()
取消挂起。
注解
线程可以使用 k_sleep()
睡眠一段指定的时间。不过,这与挂起不同,睡眠线程在睡眠时间完成后会自动运行。
线程的选项¶
内核支持一系列 线程选项(thread options),以允许线程在特殊情况下被特殊对待。这些与线程关联的选项在线程创建时就被指定了。
不需要任何线程选项的线程的选项值是零。如果线程需要选项,您可以通过选项名指定。如果需要多个选项,使用符号 |
作为分隔符。(即按位或操作符)。
支持如下选项:
K_ESSENTIAL
该选项将线程标记为 必须线程(essential thread),表示当该线程正常结束或异常终止时,内核将认为产生了一个致命的系统错误。
默认情况下,一般线程都不是必须线程。
K_FP_REGS
and K_SSE_REGS
这两个选项是 x86 相关的选项,分别表示线程使用 CPU 的浮点寄存器和 SSE 寄存器,指示内核在调度线程进行时需要采取额外的步骤来保存/恢复这些寄存器的上下文。(更多信息请参考 浮点服务)。
默认情况下,内核在调度线程时不会保存/恢复这些寄存器的上下文。
实现¶
创建一个线程¶
您可以通过定义线程的栈区域并调用 k_thread_spawn()
创建一个线程。栈区域是一个由字节构成的数组,且其大小必须等于 K_THREAD_SIZEOF
加上线程栈大小之和。栈区域必须使用属性 __stack
进行定义,以确保被正确对齐。
创建线程的函数会返回该线程的标识符(ID),您可以使用该标识符来引用该线程。
下面的代码创建了一个立即启动的线程。
#define MY_STACK_SIZE 500
#define MY_PRIORITY 5
extern void my_entry_point(void *, void *, void *);
char __noinit __stack my_stack_area[MY_STACK_SIZE];
k_tid_t my_tid = k_thread_spawn(my_stack_area, MY_STACK_SIZE,
my_entry_point, NULL, NULL, NULL,
MY_PRIORITY, 0, K_NO_WAIT);
您也可以调用 K_THREAD_DEFINE
在编译时创建线程。需要注意的是,这个宏自动定义了一个栈区域以及一个线程标识符变量。
下面的代码与上面的代码具有系统的效果。
#define MY_STACK_SIZE 500
#define MY_PRIORITY 5
extern void my_entry_point(void *, void *, void *);
K_THREAD_DEFINE(my_tid, MY_STACK_SIZE,
my_entry_point, NULL, NULL, NULL,
MY_PRIORITY, 0, K_NO_WAIT);