RTT小师弟 · 2021年04月20日

RT-Thread 内核学习笔记 - 理解defunct僵尸线程

RT-Thread 内核学习笔记 - 内核对象rt_object

RT-Thread 内核学习笔记 - 内核对象管理

RT-Thread 内核学习笔记 - 内核对象操作API

RT-Thread 内核学习笔记 - 内核对象初始化链表组织方式

RT-Thread 内核学习笔记 - 内核对象链表结构深入理解

RT-Thread 内核学习笔记 - 设备模型rt_device的理解

RT-Thread 内核学习笔记 - 理解defunct僵尸线程

前言

  • 目前大家偶尔会讨论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。

总结

  • 熟悉RT-Thread线程的初始化、创建、脱离(反初始化)、删除等操作。
  • 熟悉动态创建的线程,删除后,加入僵尸线程链表,内存资源回收流程。
  • 理解普通线程如main线程的退出流程。
  • 带着问题后续继续研究以下知识点:

    • 调度器的工作流程,在哪里执行?如何运作?
    • 线程定时器,每个线程都会创建一个,线程删除后,定时器资源的回收流程。
    rt_thread_delete /* 线程的定时器,使用detach方式,定时器资源回收流程 */
    
    rt_timer_detach(&(thread->thread_timer)); /* 定时器资源的回收流程 */
  • 创建线程时,我把线程的名字(name)改为RT_NULL,依旧可以正常的运行,但这样是否有影响?
  • rt_thread 结构体中:内核对象的链表用于加入内核对象容器,成员:tlist的工作流程。
  • 历史问题:rt_thread结构体:
/**
* Thread structure
*/
struct rt_thread
{
   /* rt object */
   char        name[RT_NAME_MAX];                      /**< the name of thread */
   rt_uint8_t  type;                                   /**< type of object */
   rt_uint8_t  flags;                                  /**< thread's flags */

#ifdef RT_USING_MODULE
   void       *module_id;                              /**< id of application module */
#endif

   rt_list_t   list;                                   /**< the object list */

是否可以改为如下:

/**
 * Thread structure
 */
struct rt_thread
{
    struct rt_object parent;                            /**< inherit from rt_object */
推荐阅读
关注数
8072
内容数
181
小而美的物联网操作系统,经过14年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过4亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息