前言
目前大家偶尔会讨论RT-Thread线程退出的问题,如main线程return后,怎么处理的?占用的内存RAM资源是否得到释放。
最近在看线程相关的内核源码,基于内核对象rt_object管理方法,梳理一下线程退出后的处理流程。
rt_thread_defunct,僵尸线程的链表结构,为什么叫【僵尸】,我查的字典,作用就是回收删除的线程的内存(堆)资源。
线程初始化与创建
rt_thread_init:静态初始化一个线程,线程结构体、线程栈,都是全局的变量。rt_thread_detach后,这个线程的内核对象从内核容器链表里移除,【但】线程结构体、线程栈,因为是静态全局的,无法释放。
若下次再想初始化并使用这个线程,依旧可以使用这个detach后的现有的线程结构体、线程栈进行初始化。
静态线程的特点:初始化后,内存的占用就不会改变。
rt_thread_create:动态创建一个线程。需要使能:
RT_USING_HEAP,堆管理。创建的线程【结构体】与【线程栈】都是动态申请出来的。删除这个线程时,需要调用rt_thread_delete。删除后,线程的【结构式】与【线程栈】占用的内存(堆)空间,可以释放。删除后这个线程不存在了,想再次开启使用需要重新创建。
动态线程的特点:删除后,内存资源可以释放。
线程的资源回收
RT-Thread 只回收rt_thread_create的线程内存资源。
静态初始化或动态创建线程时,会注册:rt_thread_exit,线程退出后,会调用rt_thread_exit。
rt_thread_exit中,第一步:把线程从调度链表移除。第二步:静态的线程,会调用:rt_object_detach,从内核对象容器里移除线程内核对象;动态线程,会把线程的结构体指针(操作句柄),加入rt_thread_defunct僵尸线程链表中,而不是立即释放线程占用的内存。
僵尸线程的操作,是在idle线程中执行。第三步:执行线程调度,切换线程。在idle线程执行,应该是保证这个线程删除后,立即调度切换线程,线程的资源回收不需要太高的优先级。
idle 线程中: rt_thread_idle_excute 负责查看rt_thread_defunct僵尸线程链表是否为空,如果不为空,则执行内存释放的操作。从线程链表移除、释放线程栈、释放内核结构体。注意,只有动态创建的线程,执行此操作。
/* 来自:rt_thread_idle_excute 片段 /
/ remove defunct thread /
rt_list_remove(&(thread->tlist));
/ release thread's stack /
RT_KERNEL_FREE(thread->stack_addr);
/ delete thread object */
rt_object_delete((rt_object_t)thread);
main线程退出
上面梳理了线程的退出,僵尸线程的处理流程,main线程的退出,基本上也可以梳理流程了
静态的main线程,未使能:RT_USING_HEAP,退出后,内存资源不会释放。
动态的main线程,使能了:RT_USING_HEAP后,退出后,内存资源得到释放。
实际对比内存资源的释放大小,发现,动态申请,会额外占用一点内存资源,如12字节,这部分在后面内存管理后再深入的梳理。
动态线程的资源回收:
RT_USING_HEAP
main存在时:线程栈大小为2048
msh >free
total memory: 89568
used memory : 10656
maximum allocated memory: 10656
main不存在时:
msh >free
total memory: 89568
used memory : 8456
maximum allocated memory: 10656
main的线程栈,return后的资源:2200 Bytes空间
2048 栈空间 + 12Byte(rt_malloc管理占用)
128Byte rt_thread结构体大小,sizeof(struct rt_thread) + 12Byte(rt_malloc管理占用,内核对象)。
合计:2048+12+128+12 = 2200。
注意事项
动态创建的线程,线程里动态创建的对象,在删除线程前,需要先手动删除创建的对象,释放内存
如动态线程里,内部申请了一块内存,退出线程前,需要释放掉。
如动态线程里,内部使用全局静态的消息队列,退出线程前,需要detach掉。
总结
熟悉RT-Thread线程的初始化、创建、脱离(反初始化)、删除等操作。
熟悉动态创建的线程,删除后,加入僵尸线程链表,内存资源回收流程。
理解普通线程如main线程的退出流程。
带着问题后续继续研究以下知识点:
调度器的工作流程,在哪里执行?如何运作?
线程定时器,每个线程都会创建一个,线程删除后,定时器资源的回收流程。
rt_thread_delete /* 线程的定时器,使用detach方式,定时器资源回收流程 /
rt_timer_detach(&(thread->thread_timer)); / 定时器资源的回收流程 */
创建线程时,我把线程的名字(name)改为RT_NULL,依旧可以正常的运行,但这样是否有影响?
rt_thread 结构体中:内核对象的链表用于加入内核对象容器,成员:tlist的工作流程。
历史问题:rt_thread结构体:
rt_uint8_t flags; /< thread’s flags */
ifdef RT_USING_MODULE
void *module_id;
endif
rt_list_t list;
是否可以改为如下:
原作者:张世争