Skip to content

[kernel][src] fix: patch for the time slice scheduler - #6645

Open
blta wants to merge 1 commit into
RT-Thread:masterfrom
blta:fix/time_slice_patch
Open

blta wants to merge 1 commit into
RT-Thread:masterfrom
blta:fix/time_slice_patch

Conversation

@blta

@blta blta commented Nov 23, 2022 •

Copy link
Copy Markdown
Contributor

[
最近再次研究了之前提交的PR #6232 ,发现存在不少问题和新的bug, 打个patch, 同时提供一下utest
具体信息,参考 时间片调度算法issue解决后续及utest测试

已在stm32f407disc1测试, SMP 正在研究怎么测试。

]

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 代码是高质量的 Code in this PR is of high quality
  • 已经使用formatting等源码格式化工具确保格式符合RT-Thread代码规范 This PR complies with RT-Thread code specification

@blta
blta force-pushed the fix/time_slice_patch branch from cc15710 to 7c4b539 Compare November 23, 2022 23:55
@Guozhanxin Guozhanxin added the Status: waiting for test Waiting test for this PR. label Dec 7, 2022
@supperthomas supperthomas changed the title fix: patch for the time slice scheduler [kernel][src] fix: patch for the time slice scheduler May 4, 2023
@supperthomas supperthomas added the Kernel PR has src relate code label May 4, 2023
@Yunlk

Yunlk commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

这个 PR 挂在 Status: waiting for test 上三年了,补一些当前 master 上的核对结果,希望能帮到推进。

1. 补丁的目标文件已经不存在了

本 PR 改的是 src/scheduler.c,但当前 master 上这个文件已经拆分:

src/scheduler_comm.c 线程/优先级公共逻辑 src/scheduler_up.c 单核 rt_schedule / rt_sched_insert_thread src/scheduler_mp.c 多核 rt_schedule / _sched_insert_thread_locked

所以本 PR 现在无法直接应用。对应关系大致是:

本 PR 的位置 当前 master
rt_schedule() 里两处 stat &= ~RT_THREAD_STAT_YIELD_MASK src/scheduler_up.c:338-341(切走时清除)、scheduler_mp.c 对应处
rt_schedule_insert_thread() 单核分支 rt_sched_insert_thread()(scheduler_up.c:491)
rt_schedule_insert_thread() 绑核分支 _sched_insert_thread_locked()(scheduler_mp.c:280)

2. 核心判别式与 PR #11907 完全一致

本 PR 的关键判断是:

c if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING) { if (thread->stat & RT_THREAD_STAT_YIELD_MASK) -> 队尾 /* 时间片用尽 */ else -> 队首 /* 时间片未用尽被抢占 */ } else -> 队尾 /* 挂起后恢复 / 新启动 */

我最近提的 PR #11907 独立得出了同一个判别式((stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING),两边在不知情的情况下收敛到相同结论,这本身算是这个方向有效性的旁证。

3. 一处值得决策的差异:YIELD 在哪里清除

  • 本 PR:删掉 rt_schedule() 里的两处清除,也删掉 insert 开头 thread->stat = RT_THREAD_READY | ... 的覆盖,改为在 insert 末尾统一 thread->stat &= ~RT_THREAD_STAT_YIELD_MASK。因为如果保留那行覆盖,判别式读到的状态就已经被抹掉了。
  • [kernel][scheduler] fix: keep FIFO order for same-priority threads #11907:保留覆盖,但先把它存进局部量 stat_before 再判断;rt_schedule() 里的清除位置不动。

两者的分支结果等价,差别在于 YIELD 生命周期的语义。这里有个实测现象可以参照:当前代码在切走线程时就清 YIELD,所以一个线程 yield 之后再阻塞到信号量、被唤醒时该位已经是 0——我在 QEMU vexpress-a9 上测过,这种场景的结果是顺序仍被反转(BA)。这也是为什么必须用 RUNNING 而不是单靠 YIELD 位来判断。

4. 我可以帮忙验证

我本地已有可用的测试环境(QEMU vexpress-a9 + xpack arm-none-eabi-gcc 15.2.1,以及一组同优先级线程的顺序/轮转测试:信号量唤醒顺序、启动顺序、tick 轮转公平性),已经把 #11907 的修复前/后都跑过。

如果需要,我可以把这个补丁按当前文件结构 port 一份,在 QEMU 上跑同样的用例给出对照数据(单核 + SMP 都可以)。作者当年提到"SMP 正在研究怎么测试",这部分我可以补。

要我先做 port + 测试吗?还是维护者认为 #11907 已经覆盖了同等语义、本 PR 可以直接关闭,也请明示——这样至少不让它继续无声挂着。

@Yunlk

Yunlk commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

按上一条评论里说的,我把这个补丁 port 到当前文件结构并跑了单核测试。顺序问题确实修好了,但引入了一个可复现的重复释放。

Port 方式

只改了 src/scheduler_up.c(单核),严格按原补丁的逻辑:

  1. 删掉 insert 开头的 stat = RT_THREAD_READY | ...
  2. 用 (stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING 分支:YIELD 置位→队尾,否则→队首;非 RUNNING→队尾
  3. 插入后按原补丁顺序 stat &= ~RT_THREAD_STAT_YIELD_MASK; 再 stat = RT_THREAD_READY | ...
  4. rt_schedule() 里原来那处 YIELD 清除去掉(改由 insert 内部完成)

结果(QEMU vexpress-a9,UP)

项目 master 本补丁 port PR #11907
信号量唤醒顺序 BA(错) AB ✅ AB ✅
启动顺序 DC(错) CD ✅ CD ✅
时间片轮转(3 线程,切片 5 tick) 1.09x,OK 1.07x,OK 1.05x,OK
调度组 utest 1 failed 崩溃 7/7 passed

顺序和轮转都对,说明判别式本身是对的。但跑 utest 时稳定触发:

(small_mem != RT_NULL) assertion failed at function:rt_smem_free, line number:516
backtrace:
  60077d78 60078b80 60077a50 60070714 600708f8

addr2line 还原:

rt_assert_handler     kservice.c:1554
rt_smem_free          mem.c:517
rt_free               kservice.c:1251
rt_defunct_execute    defunct.c:133
idle_thread_entry     idle.c:134

也就是 idle 线程在回收已结束线程时,对同一个 TCB 做了第二次释放。连续两次运行都在同一位置复现(每次都是 utest 跑到 core.scheduler_ready_order 的 _sem_wakeup_order 单元时触发)。

隔离实验:不是 YIELD 清除位置的问题

我做了个对照:保留 insert 侧的改动,但把 rt_schedule() 里原来那处 YIELD 清除加回去。崩溃照旧复现。

所以问题不在"YIELD 清除从 rt_schedule 搬到 insert 末尾"这一半,而在 insert 侧延后写 READY这一半:插入期间线程的 stat 仍是 RT_THREAD_RUNNING,插入完成后才置 READY。

作为对照,PR #11907 用的是同一个判别式,但保留"先写 READY、再判断",判别式改用进入函数时保存的 stat_before——调度组 7 个用例全过,没有这个断言。

我没有做完的部分

  • 没有定位到重复释放的具体根因。目前只能确定它跟随"延后写 READY"这个设计选择,并且在 UP 下稳定复现。
  • SMP 没有测。考虑到 UP 已经暴露内存安全问题,再在 SMP 上验证同一套已经出错的逻辑收益不大;如果维护者认为仍需要 SMP 数据,我可以接着 port scheduler_mp.c 那两处并测(本机 vexpress-a9 支持 SMP 配置)。

建议

判别式(== RT_THREAD_RUNNING)值得保留,但不要延后写 READY——那个改动看起来是让判别式能读到旧状态的手段,但代价是插入期间线程状态与实际不一致,至少在我这套测试下会引出重复释放。PR #11907 用局部变量保存旧状态,规避了这一点。

如果这个方向维护者认可,建议以 #11907 为基础推进,本 PR 可以关闭;如果认为 #6645 的写法更合适,需要先查清上面那个重复释放。

@Yunlk

Yunlk commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

补上 SMP 数据(上一条里我说"SMP 没测",现在测了)。同时更正一条我自己的误判。

SMP 结果(QEMU vexpress-a9,-smp cpus=2,RT_USING_SMP + RT_CPUS_NR=2)

port 了 scheduler_mp.c 的 _sched_insert_thread_locked() 两处分支,逻辑与 UP 侧一致。

项目 本补丁 port PR #11907
信号量唤醒顺序 AB ✅ AB ✅
启动顺序 CD ✅ CD ✅
时间片轮转(3 线程轮转,切片 5 tick) 34/33/32%,1.07x,OK 35/33/31%,1.10x,OK
调度组 utest passed passed

SMP 下两边都能修好顺序,轮转也没问题。

更正:那条 rt_thread_delete 断言是我的测试台问题

SMP 跑完后我的临时轮转脚本清理时打印了:

(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread) assertion failed
  at function:rt_thread_delete, line number:614

我一开始怀疑是 TCB 被破坏,于是在同样的 SMP 配置下又跑了 PR #11907——完全相同位置同样报错。所以这是我那段临时脚本的缺陷(SMP 下直接 rt_thread_delete 仍在其他核上运行的线程),与这两个补丁都无关。它在测量之后才触发,不影响上表的顺序/轮转数据。

UP 与 SMP 的差异值得注意

  • SMP:本补丁 port 跑调度组 utest 通过,没有出现内存断言。
  • UP:稳定出现 (small_mem != RT_NULL) 断言,backtrace 是 rt_free ← rt_defunct_execute ← idle_thread_entry,即 idle 回收已结束线程时二次释放;两次运行都在同一点复现。

也就是说这个重复释放目前只在单核路径观察到,跟着 scheduler_up.c 里"延后写 READY"的实现走;多核路径没复现出来(可能只是没触发到,不代表没问题)。

综合结论

  1. 判别式 (stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING 是对的——UP/SMP 都验证了它能把顺序修正过来,两个实现都做到了。
  2. 分歧在实现手段:延后写 READY(本补丁)在单核路径引出了重复释放;保存 stat_before 局部量([kernel][scheduler] fix: keep FIFO order for same-priority threads #11907)没有。隔离实验确认这与 YIELD 清除位置无关,问题出在插入期间线程状态与实际不一致。
  3. 我仍然没有定位到那个重复释放的根因,只能说它跟随该设计选择出现。

如果维护者希望以 #6645 为基础,需要先查清 UP 下这个二次释放;若以 #11907 为基础,判别式一致,且目前 UP/SMP 都没有出现内存问题。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Kernel PR has src relate code Status: waiting for test Waiting test for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants