1、引言
本文基于Cotex-M内核处理器分析讨论RT-Thread中线程从创建到消亡的整个详细过程。
-
线程的载体-控制块
RT-Thread中是用线程控制块来描述线程实体的,在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程 的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之 间连接用的链表结构,线程等待事件集合等 。所有对线程的操作归根结底都是对线程控制块中以上属性所对应变量的操作。
-
线程的诞生
线程不会自己产出,需要人为的去创建,RT-Thread提供了两种方式:使用 rt_thread_create() 创建一个动态线程,使用 rt_thread_init() 初始化一个静态线程,动态线程与 静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能 使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄 。
-
线程的消亡
线程的消亡分为人为删除和自然消亡。
自然消亡:线程结束(线程入口函数返回)后,当前线程也会随之消亡。
人为删除又分为两种情况:
情况一:对于一些使用 rt_thread_create() 创建出来的线程,当不需要使用,或者运行出错时,我们可以使用
rt_thread_delete()从系统中把线程完全删除掉。
情况二:对于用 rt_thread_init() 初始化的线程,使用 rt_thread_detach() 将使线程对象在线程队列和内核对
象管理器中被脱离(删除)。
2、线程删除细节
RT-Thread中线程删除过程分为两个步骤:
第一、先将该线程从系统就绪队列中删除,再将该线程的状态更改为关闭状态,不再参与系统调度,然后挂入rt_thread_defunct 僵尸队列(资源未回收、处于关闭状态的线程队列)中。
第二、空闲线程会回收被删除线程的资源。
上诉第一步的处理过程的本质是,直接(直接删除)或者间接调用rt_schedule_remove_thread()来完成上诉操作。所谓直接是指人为的调用 rt_thread_delete()或者rt_thread_detach()函数,这两个函数函数内部都调用了rt_schedule_remove_thread()函数。所谓间(自然消亡)接是指线程结束(线程入口函数返回)后,系统空闲线程 自动执行 rt_thread_exit() ,该函数中也调用了rt_schedule_remove_thread()函数。
直接删除操作比较好理解,我们重点对自然消亡的线程删除过程进行分析。通过上面描述我们只知道线程运行结束时会自动执行 rt_thread_exit() 函数,对于如何调用rt_thread_exit()的细则一开始是一头雾水。下面经过分析源码我们将拨开这层神秘的面纱。
无论是调用rt_thread_create()创建的动态或是调用rt_thread_init()创建的静态线程,内部都是通过调用_rt_thread_init()函数来完成线程创建工作的。下面我们对 _rt_thread_init()函数进行分析
static 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)
{
……
//省略部分
/*初始化线程控制坏*/
thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
(void *)((char *)thread->stack_addr + thread->stack_size - 4),
(void *)rt_thread_exit);
……
//省略部分
}
_rt_thread_init() 函数内部调用了rt_hw_stack_init()函数对线程控制块进行初始化,这个也是线程创建的本质。
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter,
rt_uint8_t *stack_addr,
void *texit)
{
……
//省略部分
/*设置函数返回地址*/
stack_frame->exception_stack_frame.lr = (unsigned long)texit; /* lr */
……
//省略部分
}
对于rt_hw_stack_init函数我们重点stack_frame->exception_stack_frame.lr = (unsigned long)texit
;这句代码将内部arm内部lr寄存器的值赋予了函数指针参数texit。而texit传递的是rt_thread_exit函数。由此可见rt_hw_stack_init函数返回后会执行rt_thread_exit函数。
连接寄存器 R14
R14 是连接寄存器(LR)。在一个汇编程序中,你可以把它写作 both LR 和 R14。 LR 用于 在调用子程序时存储返回地址。例如,当你在使用 BL(分支并连接, Branch and Link)指令时, 就自动填充 LR 的值。 AR
Cortex-M 内核下,c函数结束后会通过BX LR 指令自动跳转到LR寄出去中的地址中去执行指令,因此RTT中rt_hw_stack_init()函数返回后会跳转到rt_thread_exit()函数去执行,在rt_thread_exit()函数中调用rt_schedule_remove_thread()来执行上诉线程删除的第一个步骤。第一步执行完成后,系统空闲时候会进入空闲线程,空闲线程的入口函数为rt_thread_idle_entry(),此函中调用rt_thread_idle_excute()对僵尸线程资源进行回收,从而完成线程删除的最终工作。
static void rt_thread_idle_entry(void *parameter)
{
#ifdef RT_USING_IDLE_HOOK
rt_size_t i;
#endif
while (1)
{
#ifdef RT_USING_IDLE_HOOK
for (i = 0; i < RT_IDEL_HOOK_LIST_SIZE; i++)
{
if (idle_hook_list[i] != RT_NULL)
{
idle_hook_list[i]();
}
}
#endif
rt_thread_idle_excute();//释放僵尸线程所占资源
}
}