linux抢占式调度

为什么会发生调度?
 
因为cpu是有限的,而操作系统上的进程很多,所以操作系统需要平衡各个进程的运行时间
比如说有的进程运行时间已经很长了,已经占用了cpu很长时间了,这个时候操作系统要公平
就会换下一个需要运行的进程。
 
举个例子
 
公司只有一个饮水机用来接水,有很多人排队,某个人接完了一杯水,又接下一杯水,一连接了好多杯水,这个时候公司的人事就要过来把这个人赶走
换下一个人接水,然而这个时候老板过来接水了,这个时候下一个人就是老板来接水,而不是后面排队的人,因为什么呢?因为他是老板,就这么强悍
 
这里面的饮水机就是cpu
人事就是操作系统
等接水的一个个员工就是进程
 
老板就对应操作系统里的实时进程
普通员工就对应操作系统的普通进程
 
在操作系统里面实时进程的优先级比普通进程的优先级要高,
所以操作系统在选择下个进程的时候会优先选择实时进程队列里面的进程
除非实时进程里面的进程没有了,这个时候才轮到普通进程
 
 
操作系统就像人事一样,需要公平的调度和分配资源,当然公司里面有很多老板啊,总经理啊,领导啊
这些人拥有特权,所有分配资源的时候要优先考虑这些老板领导们,这也很正常,谁让人家是老板呢
 
 
 
下面来说说抢占式调度
 
什么情况下会发生抢占式调度呢?
最常见的现象是你这个进程运行时间太长了,是时候切换到另一个进程了
 
然而操作系统怎么去统计运行时间呢?
 
计算机有个时钟的概念,每过一段时间,计算机会通知操作系统,告诉操作系统,又过去了一段时间,你去看看,当前运行的进程运行时间是不是过长了,这个时候操作系统就会去搞这个进程了。
 
在操作系统中对于每个进程有一个理想运行时间的变量,然后对应还有一个虚拟运行时间和实际运行时间和权重(优先级)。
 
这三者有什么关系呢?
 
虚拟运行时间 vruntime += 实际运行时间 delta_exec * NICE_0_LOAD/ 权重(优先级)
 
通过这个公式可以看出来,给高优先级的进程的虚拟运行时间算少了,给低优先级的进程的虚拟运行时间算多了,但是当操作系统选择下一个进程的时候还是选择虚拟运行时间最少的进程,所以说优先级在这里面就体现出来了。
 
当某个进程在运行的时候,这个进程的虚拟运行时间会增加,当进程不运行的时候,虚拟时间不增加
其实这里又会涉及到调度器的概念。
 
这里我们就只说针对普通进程的绝对公平调度策略
 
这种策略在选取下个进程的时候,是怎么选取的呢?
他是选取当前所有普通进程中运行时间最少的进程,这个应该很好理解吧,因为你在cpu上占用的时间最少,所以为了公平,就要选取你这个进程在cpu上运行。
 
在操作系统中维护了一个红黑树,红黑树就是一颗平衡二叉树,也就是说红黑树上面挂了好多进程,最左边的进程就是运行时间最少的进程,所有操作系统在,选取下一个进程就会选取这个红黑树上最左侧的进程。
 
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
unsigned long ideal_runtime, delta_exec;
struct sched_entity *se;
s64 delta;
 
 
ideal_runtime = sched_slice(cfs_rq, curr);
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime) {
resched_curr(rq_of(cfs_rq));
return;
}
......
se = __pick_first_entity(cfs_rq);
delta = curr->vruntime - se->vruntime;
if (delta < 0)
return;
if (delta > ideal_runtime)
resched_curr(rq_of(cfs_rq));
}
 
上面代码是Linux源码
 
在操作系统中还有个时间的概念,就是在一个调度周期中,这个进程应该运行的实际时间(ideal_runtime)
sum_exec_runtime指进程总共执行的实际时间, prev_sum_exec_runtime指上次该进程被调度时已经占用的实际时间。
 
每次在调度一个新的进程时都会把它的se->prev_sum_exec_runtime=se->sum_exec_runtime,所以sum_exec_runtime-prev_sum_exec_runtime
就是这次调度占用实际时间。如果这个时间大于ideal_runtime,则应该被抢占了。
 
除了这个条件外,还会通过_pick_first_entity取出红黑树中最小的进程。如果当前进程的vruntime(虚拟运行时间)大于红黑树中最小的进程的vruntime,且差值大于ideal_time,也应该
被抢占了
 
当发现这个进程应该被抢占了,不能直接把他踢下去,而是在这个进程上打一个标签TIF_NEED_RESCHED,标示这个进程可以被抢占了
 
还有一个可能发生抢占的场景,就是当一个休眠的进程被唤醒的时候
这个时候如果这个被唤醒的进程比当前运行的进程的优先级高,则也应该被抢占了,也是在当前运行的进程上打一个标签TIF_NEED_RESCHED
 
抢占的时机
 
1.用户态的抢占时机
当该进程进行系统调用从内核态返回到用户态的时候,判断如果该进程有TIF_NEED_RESCHED标签,则进行抢占。
 
2.内核态的抢占时机
对内核态的执行中,被抢占的时机一般发生在preempt_enable()中。
preempt_disable()关闭抢占
在内核态的执行中,有的操作是不能被中断的,所有在进行这些操作之前,总是先调用preempt_disable()关闭抢占,当再次打开的时候,也就是调用preempt_enable()的时候
就是一次内核态代码被抢占的机会。
 
在内核态也会遇到中断的情况,当中断返回的时候,返回的仍然是内核态度。这个时候也是一个执行抢占的时机。
 
大家可以看看这张图理解
linux抢占式调度
上一篇:ASM:《X86汇编语言-从实模式到保护模式》第17章:保护模式下中断和异常的处理与抢占式多任务


下一篇:非抢占式RCU中的一些概念