您的位置:首页 > 运维架构 > Linux

Linux进程调度内核实现分析

2017-01-08 11:11 411 查看
本文根据两篇博客总结,它们的地址分别是:

http://blog.csdn.net/giantpoplar/article/details/51791002

http://blog.csdn.net/giantpoplar/article/details/51791002



目录

目录

所谓抢占

什么是调度

调度实现原理
进程的优先级

时间片

调度的实现原理

Linux上调度实现的方法

上下文切换

调用schedule的时机
抢占时机

实时调度

所谓抢占

抢占式

调度程序决定进程何时停止,为其他进程腾出 CPU 资源,这种强制挂起行为就是抢占。进程被抢占前运行时间为提前设定好的时间片。

非抢占

进程占据 CPU 资源不放,除非自己主动调用 schedule() 函数,这称为进程让步。

什么是调度

调度的原因:现代操作系统都是多任务的,为了能让操作系统更好的发挥多任务的特性,需要一个管理程序来管理所有任务,这个程序就是调度程序。

调度程序的功能有:

负责让那些进程运行,那些进程等待

决定每个进程运行多长时间

此外为了更好地用户体验,运行中的进程还可以立即被其他更紧急的进程打断。

总之调度是一个平衡的过程。一方面,它要保证各个运行的的进程能够最大限度使用CPU(即尽量少的切换进程),另一方面,保证各个进程公平使用CPU(即防止一个进程长时间独占CPU的情况)

调度实现原理

调度主要基于进程优先级和时间片实现。

进程的优先级

进程的优先级有两种度量方法,一种是nice值,一种是实时优先级

nice 值的范围是 -20~+19,值越大优先级越低,也就是说 nice 值为-20的进程优先级最大。

实时优先级的范围是 0~99,与 nice 值的定义相反,实时优先级是值越大优先级越高。

实时进程都是一些对响应时间要求比较高的进程,因此系统中有实时优先级高的进程处于队列的话,它们会抢占一般进程的运行时间。

进程的实时优先级高于 nice 值,在内核中,实时优先级的范围是 0~MAX_RT_PRIO-1。

MAX_RT_PRIO的定义参见 include/linux/sched.h

#define MAX_USER_RT_PRIO    100
#define MAX_RT_PRIO         MAX_USER_RT_PRIO


nice值在内核中的范围是 MAX_RT_PRIO~MAX_RT_PRIO+40 即 MAX_RT_PRIO~MAX_PRIO

#define MAX_PRIO            (MAX_RT_PRIO + 40)


一个进程有只能拥有实时优先级和 nice 值其中之一

时间片

有了优先级,就可以决定谁先运行了。但是对于调度程序来说,并不是运行一次就结束了,必须知道间隔多久进行下次调度。

于是就有了时间片的概念。时间片表示一个进程抢占前能持续运行的时间。

时间片设定,过大会让系统响应变慢;过小会产生由于进程频繁切换带来的开销。

默认的时间片一般为 10ms。

调度的实现原理

基本步骤我的理解是:

首先确定每个进程占用多少 CPU 时间,如利用 nice 值进行计算。

让占用 CPU 时间多的先运行。(让列宁同志先走)

运行完后,扣除运行进程的 CPU 时间,接下来继续重复这三步。

Linux上调度实现的方法

Linux 上采用了“完全公平调度算法”,简称“CFS”。

CFS 算法在分配给每个进程 CPU 时间时,不是分配给它们一个 CPU 的绝对时间,而是根据进程的优先级分配给它们一个占用 CPU 时间的百分比。

举例:

进程名nice值百分比
进程A110%
进程B330%
进程C660%
由上图可知,占据百分比之和就是 100%。所以我的理解是 Linux 下 CFS 算法的主要步骤有:

计算每个进程的 vruntime,通过 update_curr() 函数更新进程的 vruntime。

选择具有最小 vruntime 的进程投入运行。

进程运行完后,更新 vruntime,再回到步骤 2。

vruntime 变量存放进程的虚拟运行时间,该运行时间(花在运行上的时间和)是通过对可运行总数的加权后得出的,单位是 ns。计算权重实际上就是计算占用 CPU 的百分比,通过它得出 vruntime,用来记录一个程序运行了多长时间以及还应运行多久。

时间记账

使用 sched_entity 结构体进行记账,该结构体在 task_struct 中被包含。vruntime 作用上面说过了。

进程选择

使用红黑树存放进程队列,以 vruntime 为键值,每次选择 vruntime 最小的进程。这里有用到了一个 trick,使用 leftmost 缓存了红黑树的最左叶子节点,提高效率(STL也这么干了)。

调度器入口

schedule() 函数调用 pick_next_task() 函数,按照优先级从大到小一次检查每一个调度类,从最高优先级调度类中,选择最高优先级进程。

睡眠和唤醒

睡眠:进程标记为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 状态,从红黑树中移除,放入等待队列,调用 schedule 选择和执行一个其他的进程

唤醒:通过 wake_up() 函数唤醒,进程标记为 TASK_RUNNING 状态,从等待队列放入红黑树中。

上下文切换

switch_mm() 函数把虚拟内存从上一个进程切换到新进程。

switch_to() 从上一个处理器状态切换到新处理器状态,保存回复栈寄存器信息。

调用schedule()的时机

某个进程应该被抢占,scheduer_tick 设置为 need_resched。

一个优先级高的进程进入可执行状态,try_wake_up 设置为 need_resched

从内核返回用户空间或从中断返回,内核检查 need_resched。如果已经设置,内核会在继续执行之前调用调度程序

抢占时机

用户抢占:

从系统调用返回用户空间。

从中断处理程序返回用户空间。

内核抢占:

中断处理程序正在执行,且返回内核空间之前。

内核代码再一次具备可抢占性的时候(我认为就是出于临界区之外时)。

如果内核中的代码显示调用 schedule()。

如果内核中任务阻塞。

实时调度

SCHED_FIFO:不使用时间片,先入先出。只有更高优先级别的SCHED_FIFO,SHED_RR 进程才能抢占,同一级别不能抢占。

SCHED_RR:带有时间片,实时轮流调度。时间片耗尽,允许同一级别进程抢占。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息