Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread一般讨论
系统优化
rt-thread 僵尸线程销毁流程优化分析篇
发布于 2021-11-15 10:45:10 浏览:2337
订阅该版
[tocm] ## 前言 虽然前边写了两篇文章,提到了两个问题,一个是 idle 线程省去了一组常规关中断操作;一个是 `_thread_exit` 函数和 idle 线程有功能性重复行为。但写得虎头蛇尾的,没细说明白个中缘由。 今天就扒拉扒拉 `_thread_exit` 和 `rt_defunct_execute` 这两个函数的每一行代码,以及优化的理论基础分析。 ### 先从线程销毁说起 销毁一个线程,需要完成的工作包括但不限于: 1. 用户指定的销毁任务; 2. 如果启用了 `RT_USING_MODULE` 销毁注册到线程的模块; 3. 如果启用了 `RT_USING_SIGNALS` 释放线程的信号; 4. 从任务调度器上分离线程,至此,线程变成游离状态; 5. 修改线程状态为关闭态; 6. 把线程内置定时器从定时器列表上卸载,至此,这个定时器失去定时功能; 7. 如果目标线程是**静态**内核对象,将此对象从内核中剥离; 8. 如果目标线程是**动态**内核对象,不仅仅是剥离,更要把内核对象占用的堆内存释放回收。 以上也是目前 `_thread_exit` 的实现,但是第 7 条的描述修改成:暂时把线程放到线程回收站(僵尸线程列表)里。 再细看 `rt_defunct_execute` 函数的实现,先从线程回收站里获取到线程句柄,然后实现了以上 7 条说明中除了 4 5 两条之外的其它所有工作。 可以把 `_thread_exit` 和 `rt_defunct_execute` 两个函数的工作合并简化呢? ### 功能合并简化初步分析 **静态**线程对象和**动态**线程对象的区别就在需不需要释放对象本身占用的内存。其它的流程是完全一致的。这一特点,更是今天我们进行优化的理论基础。 #### `_thread_exit` 简化可行性 假如先不考虑**静态**线程对象和**动态**线程对象的区别,无论是**静态**线程对象和**动态**线程对象都执行如下流程: 1. 从任务调度器上分离线程,至此,线程变成游离状态; 2. 修改线程状态为关闭态; 3. 把线程对象放到线程回收站(僵尸线程列表)里。 做最少最紧要的工作,把线程变成游离状态,保证线程不再被任务调度器调度。剩下的工作交给 idle 线程去完成。这样可以做到吗? 有几个问题需要考虑, 1. 模块、信号、用户定义的销毁工作等等可以交给 idle 线程完成吗? 2. **静态**线程对象也可以放到线程回收站里吗? 3. 线程内置定时器从定时器列表上卸载的工作可以延迟吗? 下面一一进行说明: 1. 也是目前 idle 里的做法; 2. 前边也说过,**静态**线程对象和**动态**线程对象的区别就在需不需要释放对象本身占用的内存。假设我们不区分**静态**和**动态**都放到线程回收站,交由 idle 线程里去区别两种不同对象的不同操作。也是可行的; 3. 线程内置定时器被延迟卸载的理论基础是什么?执行 `_thread_exit` 函数的时候,这个定时器没有被启动!因为只有线程被阻塞的时候(延时或等待资源)才启动它,既然目标线程能走到 `_thread_exit` 函数肯定不是这两种阻塞状态。目前看,这个也可以放到 idle 线程。 #### idle 线程的关中断简化理由 为什么把 idle 线程的优化放到这里讲?因为跟僵尸线程回收息息相关。这里的所作所为都跟上面说的是有机整体。 一、我们看看下面的代码。 ``` rt_thread_t rt_thread_defunct_dequeue(void) { rt_thread_t thread = RT_NULL; rt_list_t *l = &_rt_thread_defunct; if (l->next != l) { thread = rt_list_entry(l->next, struct rt_thread, tlist); rt_list_remove(&(thread->tlist)); } return thread; } ... /* disable interrupt */ lock = rt_hw_interrupt_disable(); thread = rt_thread_defunct_dequeue(); if (!thread) { rt_hw_interrupt_enable(lock); break; } ... ``` 以上的关中断操作,是因为 `rt_thread_defunct_dequeue` 中有全局变量 `_rt_thread_defunct` 。 `_rt_thread_defunct` 是一个全局变量,用于存放僵尸线程的容器,只有两个地方引用了这个变量,其中,`rt_thread_defunct_enqueue` 函数往容器中加入一个元素,`rt_thread_defunct_dequeue` 提取一个元素,也就是一个增加元素写,一个减少元素写。前一种肯定是非 idle 线程里执行的,后一种肯定是 idle 线程里执行,也就是肯定是俩不同线程的写访问。 进一步拆解我们会发现, `rt_thread_defunct_enqueue` 函数只有一个写访问; `rt_thread_defunct_dequeue` 函数里,一个指针判断是读访问,一个查询也是读访问,还有一个删除元素是写访问。能看到这一点儿,笔者也有胆量说,只有**两个“写访问”需要中断的保护,两个“读访问”,不需要中断保护!** 无论 `l->next` 指针有没有被中断修改,都不影响 `if` 判断的执行流程。无论中断返回后, `l->next` 的实际值变成多少,缓存的指针值要么是修改前的 `_rt_thread_defunct` 地址,要么是修改后的其它线程的(tlist)地址,不会变成其他无意义的地址值。 如果 `if` 判断结果为真,`rt_list_entry` 这句更不用担心 `l->next` 被修改,因为根本不可能。 `l->next` 只有可能被下一句 `rt_list_remove(&(thread->tlist));` 修改。 **总之,只有 `rt_thread_defunct_enqueue` 函数和 `rt_list_remove(&(thread->tlist));` 这一句需要关中断保护。** 没想到这里写了这么多,也不知道有没有写清楚,缓口气继续说下面的。 二、`_idle_has_defunct_thread` 函数存在的意义 目前,只有启用 `RT_USING_MODULE` 模块后 `_idle_has_defunct_thread` 函数才会被定义。但是,它的存在的意义也只是获取到僵尸线程容器中的僵尸线程句柄。如果没有僵尸线程,它返回 RT_NULL。这一点儿和 `rt_thread_defunct_dequeue` 函数的一致的! **用 `rt_thread_defunct_dequeue` 函数代替 `_idle_has_defunct_thread` 函数也是理论有支撑,实际上行得通的。** 三、卸载模块、释放信号需要在关中断保护吗? 这个问题目前笔者还不好说,但是,可以说的是,**但凡线程变成僵尸线程的,任何给此线程发信号的行为都是不明智的。** 四、**静态**线程对象 detach 过程,**动态**线程对象 delete 都不需要关中断保护。 #### 分析范围扩展到 `rt_thread_detach` `rt_thread_delete` 两个函数 同向对比 `_thread_exit` `rt_thread_detach` `rt_thread_delete` 三个函数也有很多类似的影子,不同的是: 1. 后面俩函数肯定是在其它线程执行的; 2. 原则上 `rt_thread_detach` 只能应用于**静态**线程对象,`rt_thread_delete` 只能用于**动态**线程对象。 第二条没啥特别的,因为 `_thread_exit` 以及经受住两种不同对象的考验。关键是第一条的不同,需要我们对前边的新流程进程重新考量了。 假如某个线程 Y 销毁线程 X,执行 `rt_thread_detach` `rt_thread_delete` 函数后,未及时切换到 idle 线程,也就是模块、信号、定时器、用户定义的清理任务等还未执行,切换到其他线程或者有资源可用了,会不会引起继续使用 X 的尴尬? 笔者唯一能想的起来的就是,X 线程因为延时或者资源不可用处于挂起挂起状态,线程内置定时器处于启动定时中, `rt_thread_detach` `rt_thread_delete` 函数执行结束,恰好定时器 timeout 执行了 `rt_thread_timeout` 。这是完全要杜绝的。 因此,“线程内置定时器从定时器列表上卸载的工作”不能延迟到 idle 线程,要么保证执行 `rt_thread_detach` `rt_thread_delete` 前线程内置定时器处于 stop 状态。后面这种很难做到。分析到这里, `_thread_exit` `rt_thread_detach` `rt_thread_delete` 三个函数至少的任务有: 1. 从任务调度器上分离线程,至此,线程变成游离状态; 2. 修改线程状态为关闭态; 3. 把线程内置定时器从定时器列表上卸载; 4. 把线程对象放到线程回收站(僵尸线程列表)里。 ### 销毁模块和释放信号时机的再思考 有了上面线程内置定时器的思考,销毁模块和释放信号的时机有没有类似的问题呢?是不是也必须归还 `_thread_exit` `rt_thread_detach` `rt_thread_delete` 三个函数?笔者还是那句话,**但凡线程变成僵尸线程的,任何给此线程发信号的行为都是不明智的。**~~脑子不够用,理顺不清楚了~~ ## 总结 `_thread_exit` `rt_thread_detach` `rt_thread_delete` 三个函数的主要工作可统一为: 1. 从任务调度器上分离线程,至此,线程变成游离状态; 2. 修改线程状态为关闭态; 3. 把线程内置定时器从定时器列表上卸载; 4. 把线程对象(无论**静态**线程对象还是**动态**线程对象)放到线程回收站(僵尸线程列表)里。 再次强调,如果能保证执行这三个函数前线程内置定时器已经被 stop 了,那么这里的第 3 条也可以挪到 idle 线程里。 idle 线程的常态行为只剩下判断是否有僵尸线程。如果没有僵尸线程不需要一次关中断操作;如果有僵尸线程,只有从容器里删除线程指针的时候需要关中断一次,其余的针对线程的操作均不需要关中断。
6
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
出出啊
恃人不如自恃,人之为己者不如己之自为也
文章
43
回答
1519
被采纳
343
关注TA
发私信
相关文章
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
BSP
flash
freemodbus
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
keil_MDK
ulog
SFUD
msh
C++_cpp
MicroPython
本月问答贡献
三世执戟
7
个答案
1
次被采纳
KunYi
5
个答案
1
次被采纳
RTT_逍遥
4
个答案
1
次被采纳
xiaorui
1
个答案
1
次被采纳
JonasWen
1
个答案
1
次被采纳
本月文章贡献
出出啊
1
篇文章
3
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
3
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部