linux内核进程调度

1.调度策略

进程分为普通进程和实时进程。任何时候,实时进程的优先级都高于普通进程,前者适用SCHED_NORMAL调度策略,后者可选SCHED_FIFO或SCHED_RR调度策略。进程的调度策略存储在表示进程结构体 tast_struct 的 policy 成员中。

struct task_struct {
...
int prio, static_prio, normal_prio; //优先级
unsigned int rt_priority;
struct list_head run_list;
const struct sched_class *sched_class; //调度类
struct sched_entity se;  //CFS 调度器实体结构
unsigned int policy;  //调度策略
cpumask_t cpus_allowed;
unsigned int time_slice;
...
}

2.静态优先级,动态优先级,实时优先级

静态优先级:进程结构体中static_prio表示静态优先级,在 Linux 中普通进程依赖称之为 nice 值进行进程的优先级描述。nice 值的范围是 [-20, 19]。映射到内核100-139范围,越低的 nice 值,代表着越高的优先级,反之,越高的 nice 值代表着越低的优先级。
在这里插入图片描述

动态优先级:非实时进程静态优先级与动态优先级相等,实时进程静态优先级与动态优先级不相等,内核中优先级的考虑都以动态优先级为准。
实时优先级:默认情况下的范围是 0~99,与nice 值相反,越高的实时优先级数值代表着越高的优先级。与此同时,任何实时进程的优先级都高于普通进程的优先级。

3.普通进程调度算法

使用的是 CFS 的调度算法,即完全公平调度器。对于一个普通进程,CFS 调度器调度它执行(SCHED_NORMAL),需要考虑两个方面维度:

1.如何挑选哪一个进程进入运行状态?

在 CFS 中,给每一个进程安排了一个虚拟运行时间 vruntime,调度器总是选择 vruntime 最小的进程,让其投入执行。他们被维护到一个以 vruntime 为顺序的红黑树 rbtree 中,每次去取最小的 vruntime 的进程来投入运行。

当一个进程被调度器调度到并投入运行,它的虚拟时间就会增加,但是低优先级的任务比高优先级的任务具有更高的衰减速率。
例如一个默认优先级的任务运行了200ms,则它的虚拟运行时间也为 200ms。然而,如果一个较低优先级的任务运行 200ms,则它的虚拟运行时间将大于 200ms。同样,如果一个更高优先级的任务运行 200ms,则它的虚拟运行时间将小于 200ms。所以可以说,进程的虚拟时间在红黑树中的位置是由实际的运行时间和进程优先级所共同决定的。

I/O 密集型和CPU密集型
假如现在有一个任务是 I/O 密集型而另一个为 CPU 密集型。通常,I/O 密集型任务在运行很短时间后就会阻塞以便等待更多的 I/O;而 CPU 密集型任务只要有在处理器上运行的机会,就会用完它的时间片。因此,I/O 密集型任务的虚拟运行时间最终将会小于 CPU 密集型任务的,从而使得 I/O 密集型任务具有更高的优先级。这时,如果 CPU 密集型任务在运行,而 I/O 密集型任务变得有资格可以运行(如该任务所等待的 I/O 已成为可用),那么 I/O 密集型任务就会抢占 CPU 密集型任务。

2.挑选的进程进行运行了,它运行多久,时间片分给进程有多少

[ 分配给进程的运行时间 = 调度周期 *(进程权重 / 所有进程权重之和)]

由上式可知,分配给进程的运行时间与调度周期和权重所占的比重相关,调度周期应该为目标延迟值(如假定为20ms,出自《linux内核设计与实现》),分配时间的最低阈值为1ms,避免过大的切换损耗。
例如目标延迟为20ms,现在有两个可运行的进程,nice值分别为0和5,那么nice值为0的进程分到的运行时间就是15ms,nice值为5的分到5ms,同样nice值分别为10和15的进程分到的时间一样,所以分到的运行时间片只与权重和目标延迟有关,而与优先级的值(nice值)无直接因果关系

4.实时进程调度算法

对于实时调度策略分为两种:SCHED_FIFO 和 SCHED_RR,这两种进程都比任何普通进程的优先级更高(SCHED_NORMAL),都会比他们更先得到调度。

SCHED_FIFO : 一个这种类型的进程处于可执行的状态,就会一直执行,直到它自己被阻塞或者主动放弃 CPU,它不基于时间片,可以一直执行下去,只有更高优先级的 SCHED_FIFO 或者 SCHED_RR 才能抢占它的任务,如果有两个同样优先级的 SCHED_FIFO 任务,它们会轮流执行,其他低优先级的只有等它们变为不可执行状态,才有机会执行。

SCHED_RR : 与 SCHED_FIFO 大致相同,只是 SCHED_RR 级的进程在耗尽其时间后,不能再执行,需要接受 CPU 的调度。当 SCHED_RR 耗尽时间后,同一优先级的其他实时进程被轮流调度。

5.用户抢占和内核抢占

5.1 用户抢占

每个进程都包含一个TIF_NEED_RESCHED标志,内核根据这个标志判断该进程是否应该被抢占,设置TIF_NEED_RESCHED标志就意味着触发抢占。当从内核态返回到用户态时,该标志的值会被检查,假如它为1,调度器会选择一个新的任务并执行。

调度的执行时机:
从内核态返回用户态时,执行系统调用会从用户态陷入内核态,执行中断也会陷入内核态。所以执行调度一般发生在从系统调用返回用户空间时以及从中断处理程序返回用户空间时。

触发调度(也就是设置进程TIF_NEED_RESCHED标志的条件):
1、进程阻塞或主动让出CPU
当前进程主动放弃CPU或者发生阻塞时, 则会调用主调度器schedule进行程序调度

2、周期性的时钟中断
时钟中断处理函数会调用scheduler_tick(),检查进程的时间片是否耗尽,如果耗尽则触发抢占。

3、唤醒进程的时候
当进程被唤醒的时候,如果优先级高于CPU上的当前进程,就会触发抢占。相应的内核代码中try_to_wake_up()最终通过check_preempt_curr()检查是否触发抢占。

4、新进程创建的时候
如果新进程的优先级高于CPU上的当前进程,会触发抢占。

5、进程修改nice值的时候
如果进程修改nice值导致优先级高于CPU上的当前进程,也会触发抢占。内核代码参见 set_user_nice()

5.2 内核抢占、可抢占内核

对比用户抢占, 顾名思义, 内核抢占就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取代。

内核抢占发生在:
1.中断处理程序正在执行,返回内核空间之前,这是隐式的调用schedule()函数。
2.当内核代码再一次具有可抢占性的时候,如解锁及使能软中断等, 此时当kernel code从不可抢占状态变为可抢占状态时。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数。
3.显式调用schedule()。
4.内核中的任务阻塞。

不可抢占的情况:
1、进程正在执行被锁保护的临界区代码不可抢占,这是通过每个进程的thread_info引入preempt_count计数器,计数器的初始值为0,CPU当前进程使用锁的时候加1,释放锁减1,数值为0时,内核才可以抢占来实现的。
2、在内核的中断上下半部是不允许抢占的。
3、内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。

非抢占式内核的优点有
中断响应快(与抢占式内核比较);
允许使用不可重入函数;
几乎不需要使用信号量保护共享数据, 运行的任务占有CPU,不必担心被别的任务抢占。这不是绝对的,在打印机的使用上,仍需要满足互斥条件。
非抢占式内核的缺点有
任务响应时间慢。高优先级的任务已经进入就绪态,但还不能运行,要等到当前运行着的任务释放CPU
非抢占式内核的任务级响应时间是不确定的,不知道什么时候最高优先级的任务才能拿到CPU的控制权,完全取决于应用程序什么时候释放CPU

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值