Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
相同优先级时间片轮转调度
时间片调度算法issue解决后续及utest测试
10.00
发布于 2022-11-23 23:29:35 浏览:1629
订阅该版
[tocm] 之前针对时间片调度算法,写过一篇文章[关于时间片调度算法issue的分析与解决](https://club.rt-thread.org/ask/article/b3b36a52556382b2.html) 最近又仔细研究发现考虑不全,依然存在bug, 现进行修复并针对性设计一下utest测试实例。 ## 存在部分任务不调度的情况 前一笔提交https://github.com/RT-Thread/rt-thread/pull/6232, 已经合并到了主线分支。 后来有社区伙伴,使用该最新分支出现了不调度的情况。看了一下原因:  是YIELD 状态位的清除过早了,导致后面 rt_schedule_insert_thread的根据YIELD 状态位来判断时间片是否用完就存在问题了。 当时临时给出的方案如下,社区伙伴测试后,他们的问题不复现,解决了。  从本质上来看,这是修改不完整导致。是我的问题,当时认为方案二很简单,修改不多,大意了。但内心还是有疑问的,问什么我测试的时候没发现问题呢。加上当时内部小组讨论是重新理一下时间片的流程,看下还有什么漏洞,暂未提交该PR。最近搬完家,生活工作正常后,再次研究一下。 ### 测试时为什么没发现  当时测试的情况,thread2,thread3 同优先级,低于 thread1。 结合上图和代码,仔细研究一下它们的调度过程: 1)A1时间点,rt_current_thread = thread2, 其时间片用完,置位 YIELD状态,进入一次调度。但该状态在是调用 rt_schedule_insert_thread 前被清零了。  那么按照更改后的rt_schedule_insert_thread代码就会调用rt_list_insert_after把 thread2错误地插入到 其优先级ready_list的后面,即 list-> thread2-> thread3, 理论上应该是 list->thread3->thread2。  从rt_schedule_insert_thread 出来后,调用rt_schedule_remove_thread(to_thread);  这个时候 to_thraed = thread3, 把它从ready_list移除,置为运行态,开始调度。结果ready_list变成了 list-> thread2 2)A2时间点,高优先级thread1打断了 thread3的运行,再次调用rt_list_insert_after把 thread3插入其优先级ready_list的后面,即list->thread3->thread2, 这次是正确的,后面确实继续调用了thread3。 3)A3时间点,thread3时间片用完,置位 YIELD状态,再次进入一次调度。同理的结果就是虽然错误的插入,list->thread3->thread2,然后是list->thread3。 **虽然从结果上是对的,但是不符合理论,然后在未覆盖的使用实例中(2个以上同等级线程)就出现了问题。** ### 复现不调度问题 新增一个同等级线程4,测试  该线程反转STM32F4Disc1的LED6 ``` void led4_thread_entry( void *p_arg ) { for( ;; ) { HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_SET); delay( 300 ); HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_RESET); delay( 300 ); } } ``` 当前在stm32f407disc1上测试环境如下 | thread | priority | time slice | operation | | ------- | -------- | ---------- | -------------------------------------------- | | thread1 | 6 | 5 | rt_thread_delay( 5 ), 5s 翻转一次LED4(PD12) | | thread2 | 11 | 5 | delay( 300 ),自定义阻塞延时翻转LED3(PD13) | | thread3 | 11 | 2 | delay( 300 ),自定义阻塞延时翻转LED5(PD14) | | thread4 | 11 | 2 | delay( 300 ),自定义阻塞延时翻转LED6(PD15) | 运行波形如下,很明显thread2没有调度。  按照当时的补丁,处理后  ## 一波又起(顺序问题) ### 启动顺序问题 反复测试中,本能地抓了一下复位后的波形。  晕,启动顺序不对了,按照main中startup的顺序,调度顺序应该是 thread2 -> thread3 -> thread4,完全反调了。 ``` int main(void) { rt_thread_init( &rt_led1_thread, "LED1", led1_thread_entry, RT_NULL, &rt_led1_thread_stack[0], sizeof(rt_led1_thread_stack), 6, 1); rt_thread_startup(&rt_led1_thread); rt_thread_init( &rt_led2_thread, "LED2", led2_thread_entry, RT_NULL, &rt_led2_thread_stack[0], sizeof(rt_led2_thread_stack), 11, 5); rt_thread_startup(&rt_led2_thread); rt_thread_init( &rt_led3_thread, "LED3", led3_thread_entry, RT_NULL, &rt_led3_thread_stack[0], sizeof(rt_led3_thread_stack), 11, 2); rt_thread_startup(&rt_led3_thread); rt_thread_init( &rt_led4_thread, "LED3", led4_thread_entry, RT_NULL, &rt_led4_thread_stack[0], sizeof(rt_led4_thread_stack), 11, 2); rt_thread_startup(&rt_led4_thread); while (1) { rt_thread_mdelay(1000); } return RT_EOK; } ``` 发现了问题,其实很容易知道也是rt_schedule_insert_thread 修改后插入导致的问题,毕竟主要改了这个地方  rt_schedule_insert_thread 按照名字很好理解就算把 ready task 插入其优先级对应的ready_list,主要发生在线程状态发生变化(就绪,或者礼让)后。全局搜索一下  逐步查找,整理如下 | Call condition | Callee | Note | | ----------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------ | | thread ready后 或者Yield礼让
产生的调度内 | rt_schedule->rt_schedule_insert_thread | to_thread != rt_current_thread,才把当前线程插入ready_list | | 等待资源或Deley超时 | _thread_timeout->rt_schedule_insert_thread | 超时后,重新把thread 插入其ready_list 最后,然后调用rt_schedule | | thread 改变优先级,导致的优先级readylist的变动 | rt_thread_control->rt_schedule_insert_thread | 控制类接口 | | 资源就绪, 主动把thread从suspend 恢复到ready状态 | rt_thread_resume->rt_schedule_insert_thread | 这个调用后,一般紧接着会调用rt_schedule | thread的启动最终调用了rt_schedule_insert_thread ``` rt_thread_startup ->rt_thread_resume ->rt_schedule_insert_thread ```  在插入前该thread的stat为2(RT_THREAD_SUSPEND),我猜是为了保持和正常resume的情况保持一致。  同时,YIELD状态位默认为0,即不礼让。也就是这个导致在rt_schedule_insert_thread 根据YIELD状态位判断出了问题: > 1. 新的线程启动时,YIELD状态位为0,应该调用rt_list_insert_before,把它插入其ready_list的前面,按顺序最后调用 > 2. 但时间片调度要求,YIELD状态位为0(高优先级打断,时间片未用完),应该rt_list_insert_after把它插入到其ready_list的后面,下一次继续调用它 > 3. 当前满足了时间片的公平,使用rt_list_insert_after,却导致了正常线程的调度顺序发生反转。 ### 更糟糕的情况 如果参考freertos的调度,启动顺序相反来看,倒也没什么。但是从刚刚的表格知道,rt_schedule_insert_thread更多时用在资源就绪,超时等动态的ready task插入。这个就存在很大的问题: > 1. 同一优先级有多个任务在排队等待调度 > 2. 某一时刻thread1运行期间,因为等待资源或延时,suspended > 3. 资源就绪或者超时后,回来的时候,YIELD状态位为0, 不能使用rt_list_insert_after把它直接插入到ready_list的后面,下一次就调用它 这就比如大家一起去服务中心排队办事,排到的某个人处理了一半,中间有事离开了。再回来的时候,不能插队到最前面,需要重新排! 稍微修改一下新建的thread4, 使用系统delay延时来测试: ``` void led4_thread_entry( void *p_arg ) { uint32_t i; for( ;; ) { for(i=0 ;i < 27; i++ ) { HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_SET); delay( 300 ); HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_RESET); delay( 300 ); } rt_thread_delay( 1); for(i=0 ;i < 27; i++ ) { HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_SET); delay( 300 ); HAL_GPIO_WritePin(GPIOD, LD6_Pin, GPIO_PIN_RESET); delay( 300 ); } } } ``` 再看下波形,会发现thread4已经不按照 thread2->thread3->thread4原始的顺序运行了。原因就是在A1时刻,1ms超时后,thread4被错误地插入到ready_list 头部,A2时刻直接运行了。  > B1,B2时刻thread3时间减少的原因,一开始认为也是 thread4乱插队导致的,最后发现是碰巧了,刚切换就碰上了tick中断发生,看上去就运行了一个tick。 ### 解决办法 #### 方案一 resume线程后,也置位YIELD状态位,新ready的线程本来也就是插入到最后面的。这个理论上应该可以,但是改动点太多,风险过高;另外还要改变RT_THREAD_STAT_YIELD的定义,不太好。 ``` #define RT_THREAD_STAT_YIELD 0x08 /**< indicate whether remaining_tick has been reloaded since last schedule */ #define RT_THREAD_STAT_YIELD_MASK RT_THREAD_STAT_YIELD ``` #### 方案二 区分正常线程的和时间片的插入。这个折腾了很久,一开始一直有冲突  后来单步调试时,多次观察thread->stat后, 豁然开朗: > 1. 对于时间片用完 YIELD或者被高优先级打断的thread(有时间片剩余),那么该线程一定是当前正在运行的,其插入前状态位是 RT_THREAD_RUNNING > > 这就比如: **在车上,只有占到座了,才有让不让的问题** > > 2. 其他线程的插入,不管是刚启动的,还是刚刚资源就绪或者超时的,插入前状态位肯定不是RT_THREAD_RUNNING,一般应该是RT_THREAD_SUSPEND 然后我们只要在rt_schedule_insert_thread稍加调整就可以满足要求  > 1. if 内是时间片调度的插入处理 > 2. else内 是原有的插入处理 > 3. theead->stat 的 READY置位操作需要从if 前移到后面,保证RT_THREAD_RUNNING的状态用于判断。 ##### 静态顺序验证 thread4使用阻塞延时,按启动顺序调度  ##### 动态顺序验证 thread4使用delay延时  虽然看上去基本按照thread2->thread3->thread4在顺序执行,但是感觉还是有些不太对劲,放大时序  果然,thread4第一次运行时,中间1ms的延时,没起作用。如果参考A1时刻,5ms时基和tick中断的偏移,8ms前的A2时刻应该也有一次tick中断。就算是1ms延时碰巧很快就到了,也是往后边排队,为何又继续运行了呢? 下面根据debug 单步调试,追踪看下到底什么原因  一旦在rt_thread_sleep中打开中断,程序会立即跳转到熟悉的tick 中断里,说明刚延时启动定时器,还没来得及调度,就碰上了tick中断  然后时间片减1,有剩余,跳到rt_check 继续执行  刚设置的1ms 延时,直接超时了,调用了timeout callback。  由于太突然rt_current 还是thread4,而且带着suspend的状态进入了 rt_schedule_insert_thread,然后判定特殊情况,直接切到RT_THREAD_RUNNING,继续运行。退出systick 中断,回到 rt_thread_delay 还是继续运行。  原因找到了,和改动的插入没啥关系,逻辑上也不能说是错的。 > 如果让rt_thread_sleep完成调度,再开中断响应systick,rt_current_thread已改变,会导致time slice少减一次,后面会继续运行,对其他线程不太友好。 > > 至于最后直接置位RT_THREAD_RUNNING,是否合适,看下blame 提交历史的描述 > >  > > 和我们遇到的情况一样,提交认为对于立即resume的情况,应该继续持有调度,RUNNING > > 1. 对于资源阻塞任务,切到一半,资源就绪了(比如串口中断发生了信号量),这种快速响应很合适。有同优先级排队的话,都不知道你要让给他。 > > **就像前面柜台办事的人,少了一个文件资源,准备走了,突然有找到了,那就继续呗,后面排队的也没啥意见。** > > 2. 对于自延时1tick导致的立即resume,还没延时呢,理论上后响应合适些,不过问题也不大。 > > **这就提示我们谨慎使用1 tick 延时,刚好碰上tick中断,就是无效的** ## 极端情况 ### 实例 上面有惊无险,但是也提示我们考虑极端情况: **同样是刚延时,未来得及调度被systick打断,碰巧时间片又用完了,会有什么情况** 再次修改thread4, 把它的时间片改为1,测试一下  好家伙,A2时刻thread4直接罢工了。 ### 分析 继续dubug找原因,同样是带着suspend状态进入tick中断,这次thred4时间片使用完,置位RT_THREAD_STAT_YIELD,进入rt_schedule调度时 > + rt_current_thread = thred4 > + rt_current_thread->stat = RT_THREAD_STAT_YIELD |RT_THREAD_SUSPEND (先suspend然后 yield)  然后if 语句未执行,need_insert_from_thread 未被置1,thread4带着RT_THREAD_STAT_YIELD |RT_THREAD_SUSPEND 奇怪的状态离开调度  然后同样的流程 ``` rt_timer_check ->_thread_timeout ->rt_schedule_insert_thread ```  结果是插入的顺序是对的,但状态是 RT_THREAD_STAT_YIELD|RT_THREAD_READY。 目前为止也算不上啥大问题,再次运行时,正好被高优先级的thread1打断,错误的RT_THREAD_STAT_YIELD就导致系统认为thread4时间片用完了,让出调度,少了一次运行。  > A2时刻的调度顺序如下: > > 1. systic中断产生,thread3先礼让切换到thread4 > 2. 紧接者timer check 发现高优先级的thread1延时超时,从thread4 切到 thread1, 同时误认为thread4要礼让 ,把它插入到ready_list前面 > 3. thread1 切换到 thread2 当前只是延时碰巧导致的极端情况,ipc等资源阻塞也有同样的问题(本质上也是起一个定时器延时,流程基本一致) 总结一下这个极端情况: > 1. 延时或者资源阻塞时,碰到了systick中断 > 2. 当设置好定时器和suspend状态,开中断后,准备发起一次调度,却被tick中断抢占 > 3. tick中断里,如果碰巧又遇到当前thread的time slice使用完,会同时保留RT_THREAD_STAT_YIELD |RT_THREAD_SUSPEND进入礼让调度 > 4. 礼让调度里切换成功了,但thread的状态依然是RT_THREAD_STAT_YIELD |RT_THREAD_SUSPEND > 5. 然后在timer check发现超时(**不一定在这次中断里**)或者 资源就绪唤醒thread ,均会导致插入后状态为RT_THREAD_STAT_YIELD |RT_THREAD_READY > 6. 再次运行时,一旦被高优先级任务打断,就会误认为在礼让,在时间片未用完的情况下会提前退出调度,严重时,直接缺少一次调度。 需要同时满足 1,3,6 才会出现,算是比较极端,但还是有概率存在的, 是一直存在的一个bug,不管之前的还是现在的,只能保证最多错误礼让一次,然后被修正! ### 解决 设计上,RT_THREAD_STAT_YIELD明显是后来加的,不在一个MASK下,是或的关系,导致thread status兼容性不太好。 对极端流程分析来看,如果从开始的suspend状态解决,需要考虑很多。在5上更正状态是最合适,风险最小,改起来也比较简单: 直接在插入后,清除RT_THREAD_STAT_YIELD ,保证插入后均是RT_THREAD_READY状态,无复合态。稍微合并如下:  > 也就是确保插入后的thread 只处于一种RT_THREAD_READY状态,也应该是这种状态,合理简单的结果往往是正确的。 再测试一下,就正常了。  > A1时刻由于刚好碰上thread4中间的延时,切到其他thread了 # SMP处理 SMP多核的也需参考处理一下,具体参考PR, 不在赘述。 到此,我这边测出的时间片的问题,基本都解决了。 # Utest 时间片这个问题,一波几折,归根到底还是测试的不全面,未覆盖一些特殊的情况。没发现的问题才是最可怕的,尽可能全面的测试实例可以减少出错的概率。 现结合改动过程,写一下time slice 的utest 用例。 ## 测试内容 时间片测试应包含如下内容 1. **达到设置的时间片,是否礼让** 2. **给定时间内,相同时间片,运行的时间是否相同(或者测试时间片比例)** 3. **同优先级的任务数>=3** 4. **无系统延时或资源阻塞的任务,相对调度顺序不变,静态顺序测试** 5. **有系统延时或资源阻塞的任务,相对调度顺序改变,动态顺序测试** > 稍微合并一下: > > 1. 运行时间测试:包含测试项1,2,3;主要通过任务里的变量的累加,最后比较结果。 > > 2. 调度顺序测试:包好3,4,5;计数一致,调度顺序不一定对(比如前半段一直运行A,后半段一直运行B),要保证轮询调度。 ### 运行时间测试 参考已有的时间片测试,吃了线程不够的亏,再增加2个线程,一共4个同优先级测试(稍后会有线程用于延时,动态插入)  新增线程  新增assert  测试结果,pass  > 允许部分误差 ### 调度顺序测试 #### 静态测试 测试调用顺序,打算借助rt_scheduler_hook实现 ``` void timer_slice_hook(struct rt_thread *from, struct rt_thread *to) { if(__current_thread->current_priority + 2 == to->current_priority) { if(&(to->tlist) != timeslice_list.next) { if( (from->current_priority < __current_thread->current_priority + 2) && (to->tlist.next == timeslice_list.next) ) { /* high priority had interrupted thread to*/ } else { timeslice_error++; } } else { timeslice_list.next = to->tlist.next; } } } ``` > 1. 定义一个timeslice_list 初始化为时间片优先级ready_list,记录该优先级下一个该调用的thread > > ``` > rt_scheduler_sethook(timer_slice_hook); > timeslice_list = rt_thread_priority_table[__current_thread->current_priority + 2]; > ``` > > 2. 每次rt_schedule调用rt_scheduler_hook时,检查to thread是否正确 > > * 如果to thread == timeslice_list->next对应的thread, 更新timeslice_list.next = to->tlist.next > * 如果to thread != timeslice_list->next对应的thread, 检查一下是不是有高优先级打断了当前的thread,且下一个thread未发生变动、 > > 则认为正常,不更新timeslice_list,否则timeslice_error++。 新增assert  utest run  #### 动态测试 修改,thread4 新增一个1ms的延时, 不统计它计数,观察对其他thread的影响  utest run  ## 最终测试用例 最后的动态测试,基本和下面的测试一样,包含了全部的测试内容,所以最终只使用这一个测试用例即可。  > A1时刻由于刚好碰上thread4中间的延时,切到其他thread了,属于正常,这个不影响 hook的判断。 ## 反向测试 目前的utest看上去挺顺利的,是否真的有效,把最新的scheduler.c拉下来,替换工程里的,再测试一下 ### master 测试 可以看出B1几乎没有调度,调度顺序错误了44次  ### 打上第一个patch 虽然所有thread都调度了,但调度顺序错误了依旧严重  ### 区分正常线程和礼让 调度顺序未发现问题  > 对于下面的极端情况,A2时刻刚调度被高优先级打断,因为YIELD提前置位错误礼让,理论上它也调度了,当前测试实例测试不出来。 > >  PR: https://github.com/RT-Thread/rt-thread/pull/6645
9
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
blta
这家伙很懒,什么也没写!
文章
12
回答
9
被采纳
2
关注TA
发私信
相关文章
1
关于时间片轮询中优先级高低导致轮询出错的问题
2
时间片轮转调度,,,,,,,,
3
时间片轮转调度,,,,,,
4
线程创建函数create最后的tick的作用?
5
跑timeslice_sample的示例,运行结果与文档说明的结果不一样
6
RT-Thread 时间片示例问题
7
时间片例程 结果和pdf给出的结果不同
8
新手问题:刚开始学,看到优先级和时间片,有个问题问一下
9
没有相同优先级线程时,线程时间片的问题
10
关于互斥量与线程时间片的问题
推荐文章
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
rt-smart
FAL
I2C_IIC
UART
ESP8266
cubemx
WIZnet_W5500
ota在线升级
PWM
BSP
flash
freemodbus
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
keil_MDK
ulog
SFUD
msh
C++_cpp
MicroPython
本月问答贡献
RTT_逍遥
8
个答案
2
次被采纳
KunYi
8
个答案
1
次被采纳
三世执戟
7
个答案
1
次被采纳
winfeng
2
个答案
1
次被采纳
chenyaxing
2
个答案
1
次被采纳
本月文章贡献
catcatbing
2
篇文章
5
次点赞
swet123
1
篇文章
3
次点赞
YZRD
1
篇文章
2
次点赞
Days
1
篇文章
2
次点赞
阳光的掌控者
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部