RT-Thread / rt-thread

RT-Thread is an open source IoT Real-Time Operating System (RTOS).
https://www.rt-thread.io
Apache License 2.0
10.44k stars 5k forks source link

ready list插入顺序问题 #8459

Open daguobayern opened 9 months ago

daguobayern commented 9 months ago

版本:master分支最新commit 背景:结合两次修复历史(https://github.com/RT-Thread/rt-thread/pull/6232https://github.com/RT-Thread/rt-thread/issues/8050) 都是针对ready list add 成员时做出的修复,其中rt_schedule_insert_thread函数中代码如下: / there is no time slices left(YIELD), inserting thread before ready list/ if((thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0) { rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]), &(thread->tlist)); } / there are some time slices left, inserting thread after ready list to schedule it firstly at next time/ else { rt_list_insert_after(&(rt_thread_priority_table[thread->current_priority]), &(thread->tlist)); } 我们的本意应该是如果时间片没用完被调度切换到更高优先级的线程时,那么在下次切回时应优先执行未使用完时间片的线程,例如A,B,C三线程,优先级值为A=B>C,在A执行时,C由于优先级最高被调度执行(假设被中断触发信号量),那么在C执行完时,应该先执行A,待A时间片轮完再执行B,以此交替。总结起来就是同优先级的线程必须按顺序依次执行完各自的时间片,中途可被中断或更高优先级打断,但总顺序不变,这样可以保证同优先级线程最大的公平性。

问题1: 假设线程ABCD,D优先级最高,ABC优先级相同,线程A,B,C依次获取信号量s从而挂起(s的flag为fifo),那么在插入s的挂起线程链表时调用rt_list_insert_before函数,其挂起线程链表中顺序为A->B->C,在某个时刻释放信号量时,依次调用函数_ipc_list_resume,rt_thread_resume,rt_schedule_insert_thread,此时将A取出插入相应优先级的ready list中,由于没有RT_THREAD_STAT_YIELD,那么是插队插入ready链表头中,调度时由于A优先级低,D继续执行,而后又释放了一次信号量,相同的步骤将B插队插入ready list中,此时ready list中链表顺序为B->A,若D此时释放CPU,那么调度先执行B而不是A,这显然违背了我们的意图。

最终目的: 插队插入链表头只能是线程已运行且时间片未用完的情况或者线程还未运行又要被切换(#6232)的情况,其余大部分情况应该都是顺序插入链表尾,包括线程的主动挂起(rt_thread_yield)。

zhzhqian commented 9 months ago

如A,B,C三线程,优先级值为A=B>C,在A执行时,C由于优先级最高被调度执行(假设被中断触发信号量),那么在C执行完时,应该先执行A,待A时间片轮完再执行B 而后又释放了一次信号量,相同的步骤将B插队插入ready list中,此时ready list中链表顺序为B->A,若D此时释放CPU,那么调度先执行B而不是A 这里没太理解,假如有两个同优先级的线程A,B时间片均未用完,A,B先后让出CPU(阻塞在锁或者信号量),那么当高优先级线程先后唤醒A,B(或者同时唤醒)并让出CPU,应该先执行哪一个线程呢?如果是最近被让出的线程,也就是B,那么我理解这个插入ready list的逻辑就不存在问题,如果是A,那么我想问为什么?选择A而不是B对系统公平性或者性能有什么正向影响吗?

daguobayern commented 9 months ago

感谢回复,是这个意思,如果A,B是先后获取信号量而挂起的,而这个信号量的flag是RT_IPC_FLAG_PRIO,表示在有信号量时按挂起顺序来执行线程,即先执行A后再执行B。 在高优先级线程C中释放信号量时,rt_sem_release/_ipc_list_resume函数取出挂起的线程插入到ready list中,由于是先A后B获取的信号量,挂起线程中的链表顺序是A->B,那么第一次取出的线程是A,插入到ready list的是使用rt_list_insert_after,即插入到头部。而后发生调度,此时执行D,D再次释放信号量,同样的流程,这次从挂起线程链表中取出的线程是B,插入到ready list的也是使用rt_list_insert_after,那么会插入到A的前面,即此时ready list的链表顺序是B->A,再次调度时则会先执行B再执行A,这就违背FIFO属性了。