本系列博客追寻《Linux内核设计与实现-Robert Love》,各个Linux机中的内核源代码不一,因此直接下载官网内核源码
参考书籍:《Linux内核设计与实现-Robert Love》
进程调度是什么?
进程调度能确保进程能有效工作的一个内核子系统。
进程调度负责决定将哪个进程投入运行,何时运行以及运行多长时间。进程调度程序可看作在可运行态进程之间分配有限的处理器时间资源的内核子系统。
他没有太复杂的原理。最大限度的利用处理器时间的原则是:只要有可以执行的进程,那么就总会有进程正在执行。在一组处于可运行态的进程中选择一个来执行,是调度程序所需完成的基本工作
多任务
多任务操作系统就是能同时并发的执行多个进程的操作系统。
单处理机器上,这会产生多个进程在同时运行的幻觉;在多处理机器上,这会使多个进程在不同的处理机上同时、并行的运行。
无论在单处理机还是多处理机器上,多任务操作系统都能使多个进程处于堵塞或者睡眠状态,也就是说,实际上不能被投入执行,直到工作确实就绪。
多任务系统可以划分为两类:
- 非抢占式任务:除非进程自己主动停止运行,否则他会一直运行,称为让步。
- 抢占式任务:由调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行机会(强制挂起,称为抢占)。进程在抢占之前能够运行的时间是预先设置好的,叫做进程的时间片(是分配给每个可运行进程的处理器时间段)
有效管理时间片能使调度程序从系统全局的角度做出调度决定,这样还可以避免个别进程独占系统资源。
Linux没有采用这种
Linux的进程调度
经历:
- O(1)调度程序:缺少交互进程
- RSDL:公平调度
- CFS:安全公平调度
策略
策略决定调度程序在何时让什么进程执行
I/O消耗型和处理器消耗型的进程
进程可以被分为I/O消耗)和处理器消耗型
I/O消耗型:进程的大部分时间用来提交I/O请求或是等待I/O请求。这种进程经常处于可运行状态,但通常都是运行短短的一会儿,因为他在等待更多的I/O请求时最终总会阻塞(如屏幕要等待来自键盘的用户交互操作)
处理器消耗型:把太多时间大多用在执行代码上。除非被抢占,否则一直不停的运行,因为他们没有太多的I/O请求(很少被阻塞)。但是,因为他们不属于I/O驱动类型,所以调度器不应该让经常让他们运行。
进程优先级
调度算法中最基本的一类就是基于优先级的调度,这是一种根据进程的价值和其对处理器时间的需求来对进程分级的想法。
通常做法是优先级高的进程先运行,低的后运行,相同优先级的进程按轮转方式调度(一个接一个,重复运行),在某些系统中,优先级高的进程使用的时间片也长。
调度程序总是选择时间片未佣金而且优先级高的进程运行
Linux采用了两种不同的优先级范围。
nice值(-20~+19):值越高优先级越低,说明分配时间片的绝对值越低。
ps -el # NI
实时优先级(0~99):其值是可配置的,值越高优先级越高,任何实时进程的优先级都高于普通的进程
ps -eo state,uid,pid,ppid,rtprio,time,comm # RTPRIO
时间片
时间片是一个数字,他表明进程在被抢占前所能运行的时间(过大过小都不好)
Linux的CFS调度器是将处理器的使用比划分给了进程,而不是时间片
Linux系统是抢占式的,因此,是否要将一个进程立即投入执行(也就是抢占当前进程),是完全由进程优先级和是否有时间片决定的。而在CFS调度器中,其抢占实际取决于新的可运行程序消耗了多少处理器使用比,如果消耗的时间比比当前进程小,则新进程立即投入运行,抢占当前进程,否则,推迟运行。
Linux调度算法
调度器类
Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性的选择调度算法。
UNIX系统中的进程调度
不做概述
公平调度
CFS的做法是允许每个进程运行一段时间、循环轮转。选择运行最少的进程作为下一个运行进程,而不再采用分配给每个进程时间片的做法了,CFS在所有可运行进程总数基础上计算处一个进程应该运行多久,而不是依靠nice值来计算时间片(处理器的使用比)
每个进程都按其权重在全部可运行进程中所占比例的“时间片”来运行。
CFS我引入每个进程的时间片底线,这个底线称为最小粒度
任何进程所获得的处理器时间是由他自己和其他可运行进程的nice的相对差值决定的。
使用比越低,运行越快
抢占和上下文切换
上下文切换,也就是从一个可执行进程切换到另一个可执行进程。
完成了两项基本工作:
- 调用switch_mm(),该函数负责把虚拟内存从上一个进程映射切换到新进程中
- 调用switch_to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态。
内核需要知道什么时候进行调度(schedule)。
在进程描述符(task_struct)中有一个need_resched标志来表面是否需要重新执行调度。
当某个进程应该被抢占时,schedule_tick()就会设置这个标志
当一个优先级高的进程进入可执行状态的时候,try_to_wake_up(0也会设置这个标志
内核检查该标志确认其被设置,调用schedule()来切换到一个新的进程,该标志对于内核来讲是一个信息,他表示有其他进程应当被运行了,要尽快调用调度程序
用户抢占
内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时就会发生用户抢占
在内核返回用户空间的时候,他知道自己是安全的,因为既然他可以继续去执行当前进程,那么他当然可以再去选择一个新的进程去执行。
无论是中断处理程序还是系统调用返回,都会检查need_resched这个标志
内核抢占
现在,只要重新调度是安全的,内核就可以在任何时间抢占正在执行的任务。
传统的不支持内核抢占的内核中,内核代码可以一直执行到完成,也就是说,调度程序没有办法在一个内核级的任务正在执行的时候重新调度—内核中的各任务是以协作方式调度的,不具备抢占性