PREEMPT_LAZY是一种位于PREEMPT_VOLUNTARY和PREEMPT之间的内核抢占模型。
PREEMPT_VOLUNTARY情况下,只能在进程a的时间片耗尽或者当前进程a在内核自愿放弃的情况下,让进程b抢占,此外则需要等待从内核态返回用户态的点,这些自愿被抢占的点一般标注为:
cond_resched();
而PREEMPT则可以在诸多抢占点上进行抢占,比如中断返回、任务唤醒、抢占开启、系统tick等,只有中断、软中断、preempt disable的区间(如spinlock区间)等少数区域不能抢占,但是在我们离开这些区域的点上面,抢占也可立即发生。
一些workload显然更加关心吞吐率,它们在PREEMPT_VOLUNTARY的情况下工作地更好。抢占的越少,本身耗费在上下文切换的时间会减小,另外,lock的contention也会显著减少(考虑到你拿到锁后很可能有机会运行到解锁,这样并发抢锁的机会少很多)。PREEMPT很可能对低延迟响应有好处,因为延迟敏感的任务可以及时抢,当然,它的坏处显然是上下文切换开销变多,以及任务频繁切换导致更多的机会大家碰撞抢锁。
PREEMPT_LAZY是一种位于PREEMPT_VOLUNTARY和PREEMPT之间的机制,它让normal/fair调度类的工作接近于PREEMPT_VOLUNTARY(但是不完全相同,下一个tick来的时候,normal/fair在内核态仍然可以抢占),让其他的实时调度类如RR/FIFO/DEADLINE仍然采用PREEMPT。
所以简单来说,对于normal/fair调度类任务:
PREEMPT_VOLUNTARY < PREEMPT_LAZY < PREEMPT
对于实时调度类RR/FIFO/DEADLINE任务,
PREEMPT_LAZY = PREEMPT
所以假设没有PREEMPT_LAZY的情况下(PREEMPT情况),fair调度类的task b本身可以在时刻1抢占task a,而随着PREEMPT_LAZY的引入,这个抢占需要延迟到下一个tick的到来。
所以现在的抢占延迟最大可达到一个tick,而平均下来是tick时间/2。所以它是一个折中:它会抢,它不是在内核里面不抢;它延迟抢,而不是老抢。既要抢,又不使劲抢,既要又不要,欲拒还迎的这个姿态,比较符合东方审美。
前者实际带来了一个好处,就是原先在PREEMPT_VOLUNTARY情况下的cond_resched()就不那么有价值了。比如一聪在kernel/dma/map_benchmark.c的这个提交:
commit 54624acf88433, Author: Yicong Yang
dma-mapping: benchmark: Don't starve others when doing the test
它其实就是为了防止PREEMPT_VOLUNTARY情况下,我们的map benchmark线程一直占着CPU跑,导致其他的task完全没机会跑。所以,之前的内核开发,程序员必须明确意识到我们的循环,可能你循环地很爽,但是别人一直得不到运行,所以时不时在循环里面调cond_resched()主动礼让给别人一些机会(尽管PREEMPT模型不需要这个)。现在有了下一个时钟tick的抢占,也许程序员就不必时刻在意这一点。
我现在很懒,一篇文章最多写20分钟。未写完的部分,后面再继续追加一篇吧。