《Linux内核设计与实现》课本第四章自学笔记
进程调度
By20135203齐岳
4.1 多任务
多任务操作系统就是能同时并发的交互执行多个进程的操作系统。多任务操作系统使多个进程处于堵塞或者睡眠状态,实际不被投入执行,这些任务尽管位于内存,但是并不处于可运行状态。
多任务系统分为两种:
-
抢占式多任务:Linux提供了抢占式的多任务模式,由调度程序来决定什么时候停止一个进程的运行。
现代操作系统提供:动态时间片计算的方式;可配置的计算策略
-
非抢占式多任务:除非进程自己主动停止运行,否则会一直执行。
调度程序无法躲每个进程该执行多长时间作出统一规定,所以进程独占的处理器时间可能会超过用户的预料
Unix从一开始就采用的是抢占式的多任务。
4.2 Linux的进程调度
O(1)调度器:对大服务器的工作负载很理想;但是缺少交互进程。
RSDL,反转楼梯最后期限调度算法,吸取了队列理论,公平调度。(又被称为CFS,完美公平调度算法。)
4.3 策略
策略决定调度程序在合适让什么程序运行。
两种典型的进程
I/O消耗性进程
进程的大部分时间用来提交I/O请求或者等待I/O请求,经常处于可运行状态但是运行时间很短,等待更多的请求时最后总会阻塞。
处理器耗费型进程
把时间大多用在执行代码上,除非被抢占,否则通常都会不停运行。
调度策略通常要在两个矛盾的目标中间寻找平衡:
- 进程调度迅速(响应时间短)
- 最大系统利用率(高吞吐量)
Linux倾向于优先调度I/O消耗型进程
进程优先级
调度算法中最基本的一类就是基于优先级的调度这是一种根据进程的价值和其对处理器时间的需求来对进程分级的想法。
调度程序总是选择时间片未用尽而且优先级最高的进程运行。
Linux采用了两种不同的优先级范围:
-
nice
范围[-20,19],默认值为0;
nice值越大,优先级越低;
Linux系统中nice值代表时间片的比例;
ps-el命令查看系统中进程列表,NI列为nice值。 -
实时优先级
值可以配置,默认变化范围是[0,99];
值越高优先级越高;
任何实时进程的优先级都高于普通的进程。
实时优先级和nice优先级处于互不相交的两个范畴,在处理器实际操作的时候[nice+120]来和实时优先级一同比较从而进行处理。
ps-eo state,uid,pid,ppid,rtprio,time,comm.
#查看系统中的进程列表以及对应的实时优先级(rtprio),显示“-”表示不是实时进程
时间片
时间片表示进程在被抢占前所能持续运行的时间。
- I/O消耗型进程不需要很长的时间片
- 处理器消耗型进程希望时间片越长越好
Linux的CFS调度器没有直接分配时间片到进程,而是将处理器的使用比划分给进程。进程所获得的处理器时间和系统负载密切相关。这个比例受nice值影响,nice值作为权重来调整进程所使用的处理器时间使用比:
高nice(低优先权)将被赋予低权重,从而损失小部分处理器使用比;
低nice值(高优先权)将被赋予高权重,从而抢得更多处理器使用比。
Linux进程是抢占式的,是否抢占完全由进程的优先级和是否有时间片来决定。
CFS抢占器:抢占时机取决于新的可执行程序消耗了多少处理器使用比,如果消耗的使用比当前进程小:新程序立刻投入运行,抢占当前进程,否则推迟。
4.4 Linux调度算法
调度器类
Linux调度器是以模块方式提供,以便于允许不同类型的进程可以有针对性地选择调度算法。这种模块化结构被称为调度器类。
调度器类:允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程。每个调度器有一个优先级,会按照优先级顺序遍历调度类,选择优先级最高的调度器类。
完全公平调度CFS是一个针对普通进程【区别于实时进程】的调度类。
Unix系统中的进程调度
Unix使用的调度算法是分配绝对的时间片,这样就会引发固定的切换频率,不利于公平性。
而Linux采用的CFS完全摒弃了时间片,分配给进程一个处理器使用比重,保证恒定的公平性和变动的切换频率。
公平调度CFS
CFS——近乎完美的多任务:允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,在所有可运行进程总数基础上计算出一个进程应该运行多久。nice值作为进程获得的处理器运行比的权重。
4.5 Linux调度的实现
CFS调度算法实现的四个组成部分:
- 时间记账
- 进程选择
- 调度器入口
- 睡眠和唤醒
时间记账
所有的调度器都必须对进程运行时间做记账。
调度器实体结构
CFS不再有时间片的概念,但也必须维护每个进程运行的时间记账。使用调度器实体结构来追踪进程运行记账:进程描述符中的se变量。
虚拟实时
CFS使用了vruntime变量来存放进程的虚拟运行时间,用来表示进程到底运行了多少时间,以及它还应该运行多久。
这个虚拟运行时间是加权的,与定时器节拍无关。
定义在kernel/sched_fair.c文件中的update_curr()函数实现了该记账功能。
它计算了当前进程的执行时间并存放入变量delta_exec中,
然后又将运行时间传递给__update_curr();
__update_curr()根据当前可运行进程总数对进行时间进行加权计算,
最终将权重值与当前运行进程的vruntime值相加。
(undate_curr()是由系统定时器周期调用的。)
进程选择
CFS调度算法的核心:
选择具有最小vruntime的任务。
CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小vruntime值的进程。
Linux中,红黑树被称为rbtree,是一个自平衡二叉搜索树,是一种以树节点形式存储的数据,这些数据会对应一个键值,可以通过这些键值来快速检索节点上的数据,而且检索速度与整个树的节点规模成指数比关系。
挑选下一个任务
节点键值是可运行进程的虚拟运行时间,CFS调度器随机选择待运行的下一个进程,是所有进程中vruntime最小的那个,它对应的是树中最左侧的叶子节点。函数是__pick_next_entity()
这个函数本身不会遍历树找到最左叶子节点,该值缓存在rb_leftmost字段中,
函数返回值就是CFS选择的下一个运行进程。
如果返回NULL,表示树空,没有可运行进程,这时选择idle任务运行。
向树中加入进程
发生在进程被唤醒或者通过fork调用第一次创建进程时。
函数enqueue_entity():更新运行时间和其他一些统计数据,然后调用__enqueue_entity()。
函数__enqueue_entity():进行繁重的插入工作,把数据项真正插入到红黑树中:
从树中删除进程
删除动作发生在进程堵塞或终止时。
相关函数是dequeue_entity()和__dequeue_entity():
调度器入口
进程调度的主要入口点函数是schedule()。
schedule()函数会调用pick_next_task();
pick_next_task()会以优先级为序依次检查每一个调度类,并且选择最高优先级的进程。
pick_next_task()会返回指向下一个可运行进程的指针,没有时返回NULL
pick_next_task()函数实现会调用pick_next_entity()
pick_next_entity()会调用__pick_next_entity()。
睡眠和唤醒
睡眠时:进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待序列,然后调用schedule()选择和执行一个其他进程
唤醒时:进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中。
等待队列
等待队列是由等待某些事件发生的进程组成的简单链表,休眠通过等待队列进行处理。内核用wake _ queue _head _ t来表示等待队列。等待队列可以通过DECLARE _ WAITQUEUE()静态创建,也可以由init _ waitqueue _ head()动态创建
进程通过执行以下几个步骤将自己加入到一个等待队列中:
- 调用宏DEFINE_WAIT()创建一个等待队列的选项。
- 调用add _ wait _ queue()把自己加入到队列中。
- 调用prepare _ to _ wait()方法将进程的状态变更为TASK _ INTERRUPTIBLE或TASK _ UNINTERRUPTIBLE。
- 如果状态被设置成TASK_INTERRUPTIBLE,则信号唤醒进程。(伪唤醒:唤醒不是因为事件的发生。)
- 当进程被唤醒的时候,会再次检查条件是否为真,真则退出循环,否则再次调用schedule()并且一直重复这步动作。
- 当条件满足后,进程将自己设置为TASK _ RUNNING并调用finish _ wait()方法把自己移出等待序列。函数inotify _ read():负责从通知文件描述符中读取信息。
唤醒
唤醒操作通过函数wake_up()进行,会唤醒指定的等待队列上的所有进程。
wake_up()函数调用try_to_wake_up()
try_to_wake_up()函数负责将进程设置成TASK_RUNNING状态
调用enqueue_task()将此进程放入红黑树中
如果被唤醒的进程优先级比正在执行的进程优先级高,设置need_resched标志
通常哪段代码促成等待条件达成,它就负责随后调用wake_up()函数。
虚假唤醒:
有时候进程被唤醒并不是因为它所等待的条件达成了,所以才需要用一个循环处理来保证它等待的条件真正达成。
4.6 抢占和上下文切换
上下文切换由context _ switch()函数负责。
每当一个新进程被选出准备投入运行时,schedule()会调用context _ switch()。
它完成了两项基本工作:
调用switch_mm(),该函数负责把虚拟内存从上一个进程映射到新进程中。
调用switch _ to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态。
这包括保存、恢复栈信息和寄存器信息,还有其他任何与体系结构相关的状态信息,都必须以每个进程为对象进行管理和保存。
内核必须知道什么时候调用schedule()。提供一个need _ resched标志用来表明是否需要重新执行一次调度:
当某个进程应该被抢占时,scheduler _ tick()会设置这个标志;当一个优先级高的进程进入可执行状态时,try _ to _ wake _ up()会设置这个标志。内核检查这个标志确认其被设置,调用schedule()来切换到一个新的进程。该标志对于内核来说是一个信息,表示youqitajinc应当被运行了,要尽快调用调度程序。
再返回用户空间以及从中断返回时,内核也会检查标志。
每个进程都包含一个need_resched标志,因为访问进程描述符里的数值比访问一个全局变量要快。
用户抢占
内核即将返回用户空间的时候,如果need_resched标志被设置,会导致schedule()被调用,此时会发生用户抢占。
用户抢占发生的情况:
- 从系统调用返回用户空间时
- 从中断处理程序返回用户空间时
内核抢占
Linux完整地支持内核抢占。只要重新调度是安全的,内核就可以在任何时间抢占正在执行的任务。
锁是非抢占区域的标志。只要没有持有锁,内核就可以进行抢占。
支持内核抢占而完成的动作:
- 为每个进程的thread _ info中加入preempt _ count计数器,初值为0,使用锁+1,释放锁-1,数值为0时,可以执行抢占。
- 从中断返回内核空间时,先检查need_resched标志,如果被设置表示需要被调度,然后检查preempt_count计数器,如果为0,表示可以被抢占,这时调用调度程序。否则,内核直接从中断返回当前执行进程。
- 当前进程持有的锁全部被释放,这时preempt_count归0,释放锁的代码会检查need_resched是否被设置,如果是就调用调度程序。
- 如果内核中的进程被阻塞了,或者显式地调用了schedule(),内核抢占也会显式地发生。
内核抢占会发生在:
- 中断处理程序正在执行,且返回用户空间之前
- 内核代码再一次具有可抢占性的时候
- 内核中的任务显式地调用schedule()
- 内核中的任务阻塞(同样导致调用schedule())
4.7 实时调度策略
Linux提供了两种实时调度策略:SCHED _ FIFO和SCHED _ RR。普通的非实时的调度策略是SCHED _ NORMAL。
SCHED_FIFO
简单的先入先出算法,不使用时间片
可运行的SCHED _ FIFO比任何SCHED _ NORMAL进程更先得到调度。只有更高优先级的FIFO或者RR才能抢占它;同等优先级的FIFO轮流执行,只有它愿意让出时才会退出。
SCHED_RR
带有时间片的FIFO,是一种实施轮流调度算法。
当RR耗尽它的时间片时,在同一优先级的其他实时进程被轮流调度。时间片只用来重新调度同一优先级进程。
总而言之,高优先级总是立即抢占低优先级,而低优先级决不能抢占高优先级。
这两种实时算法实现的都是静态优先级:
内核部位实施进程计算动态优先级,这能保证给定优先级别的实时进程总是能抢占优先级比它低的进程。
- 软实时:内核调度进程,尽力使进程在它的限定时间到来前进行,但内核不保证总能满足这些进程的要求。
- 硬实时:系统保证在一定条件下,可以满足任何调度的要求。
优先级范围
实时:0~[MAX _ RT _ PRIO-1]
默认MAX _ RT _ PRIO=100,所以默认实时优先级范围为[0,99]SCHED _ NORMAL:[MAX _ RT _ PRIO]~[MAX _ RT _ PRIO+40]。默认情况下,nice值从-20到+19对应的是从100到139的实时优先级范围。
4.8 与调度相关的系统应用
与调度策略和优先级相关的系统调用
nice() 将给定进程的静态优先级增加一个给定的量,只有超级用户才能在调用它时使用负值来提高进程的优先级。
- getpriority()/setpriority() 设置优先级
- sched _ getscheduler()/sched _ setscheduler() 设置和获取进程的调度策略和实时优先级
- sched _ getparam()/sched _ setparam() 设置和获取进程的实时优先级
- sched _ get _ priority _ min()/sched _ get _ priority _ max() 返回给定调度策略的最大和最小优先级
与处理器绑定有关的系统调用
Linux调度程序提供强制的处理器绑定机制
task _ struct中的cpus _ allowed位掩码中
sched_setaffinity() 设置不同的一个或者几个位组合的位掩码
sched _ getaffinity() 返回当前的cpus_ allowed位掩码
放弃处理器时间
sched_yield() 让进程显式地将处理器时间让给其他等待执行进程。普通进程移到过期队列中,实时进程移到优先级队列最后。
内核先调用yield,确定给定进程确实处于可执行状态,然后调用sched _ yield()。
用户空间可以直接调用sched _ yield()。
《Linux内核设计与实现》课本第四章自学笔记——20135203齐岳的更多相关文章
-
《Linux内核设计与实现》课本第三章自学笔记——20135203齐岳
<Linux内核设计与实现>课本第三章自学笔记 进程管理 By20135203齐岳 进程 进程:处于执行期的程序.包括代码段和打开的文件.挂起的信号.内核内部数据.处理器状态一个或多个具有 ...
-
《Linux内核设计与实现》第四章学习笔记
<Linux内核设计与实现>第四章学习笔记 ——进程调度 姓名:王玮怡 学号:20135116 一.多任务 1.多任务操作系统的含义 多任务操作系统就是能同时并发地交 ...
-
《Linux内核设计与实现》第四章学习笔记——进程调度
<Linux内核设计与实现>第四章学习笔记——进程调 ...
-
《Linux内核设计与实现》课本第十八章自学笔记——20135203齐岳
<Linux内核设计与实现>课本第十八章自学笔记 By20135203齐岳 通过打印来调试 printk()是内核提供的格式化打印函数,除了和C库提供的printf()函数功能相同外还有一 ...
-
《Linux内核设计与实现》课本第五章学习笔记——20135203齐岳
<Linux内核设计与实现>课本第五章学习笔记 By20135203齐岳 与内核通信 用户空间进程和硬件设备之间通过系统调用来交互,其主要作用有三个. 为用户空间提供了硬件的抽象接口. 保 ...
-
《Linux内核设计与分析》第四章读书笔记
<内核设计与实现>第四章读书笔记 第四章:进程调度 进程(操作系统)程序的运行态表现形式. 进程调度程序,它是确保进程能有效工作的一个内核子系统. 调度程序负责决定将哪个进程投入运行,何时 ...
-
《Linux内核设计与实现》第四章读书笔记
4.1 多任务 多任务操作系统就是能同时并发地交互执行多个进程的操作系统. 多任务系统可以划分为两类: 非抢占式多任务进程会一直执行直到自己主动停止运行 抢占式多任务Linux/Unix使用的是抢占式 ...
-
《Linux内核设计与实现》第五章学习笔记
<Linux内核设计与实现>第五章学习笔记 姓名:王玮怡 学号:20135116 一.与内核通信 在Linux中,系统调用是用户空间访问内核的唯一手段:除异常和陷入外,它们是内核 ...
-
《Linux内核设计与实现》 第一二章学习笔记
<Linux内核设计与实现> 第一二章学习笔记 第一章 Linux内核简介 1.1 Unix的历史 Unix的特点 Unix很简洁,所提供的系统调用都有很明确的设计目的. Unix中一切皆 ...
随机推荐
-
关于使用微信登录第三方APP的实现(Android版)
使用微信登录APP,免去注册过程,现在已经有很多的类似应用了.集成该功能过程不复杂,但还是有一些地方需要注意的. 开始之前,需要做下面的准备工作. 1.到微信开放平台注册你的APP,并申请开通微信登录 ...
-
linux系统的7种运行级别
Linux系统有7个运行级别(runlevel)运行级别0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动运行级别1:单用户工作状态,root权限,用于系统维护,禁止远程登陆运行级别2:多 ...
-
Luncence .Net 使用
public partial class Form1 : Form { public Form1() { InitializeComponent(); } //标准分词 private void bu ...
-
20145213《Java程序设计学习笔记》第六周学习总结
20145213<Java程序设计学习笔记>第六周学习总结 说在前面的话 上篇博客中娄老师指出我因为数据结构基础薄弱,才导致对第九章内容浅尝遏止地认知.在这里我还要自我批评一下,其实我事后 ...
-
iOS AudioQueue机制的延迟问题探究
关键字:VOIP,AudioUnit,AudioQueue,RemoteIO问题描述VOIP通话,iOS底层音频方式采用AudioUnit机制,本来也挺好,但是会有遇到CS域来电时RemoteIO挂死 ...
-
Rest(Restful)风格的Web API跟RPC风格的SOAP WebService--这些名词都啥意思?
经常看到这些词汇,也有baidu或google过,但记忆里总是模糊,不确定,以至于别人问及的时候,总说不清楚.开篇随笔记录下.大家有补充或者意见的尽请留文. 本文顺序: 一.Rest(Restful) ...
-
[Nginx 2] form表单提交,图片上传
导读:昨晚恶补了一些Nginx服务器的东西,从整体上对Nginx有一个初步的了解.上午去找师哥问了问目前项目中的使用情况,然后就开始上传图片了.这里就简单总结整理一下今天的成果,以后接着提升.简单粗暴 ...
-
linux od命令: 按不同进制显示文件
介绍:od(octal dump)命令可以以八进制.十进制.十六进制和ASCII码来显示文件或者流,它们对于访问或可视地检查文件中不能直接显示在终端上的字符很有用.语法: od [-A 地址进制] [ ...
-
UVA 11090 Going in Cycle!!(二分答案+判负环)
在加权有向图中求平均权值最小的回路. 一上手没有思路,看到“回路”,第一想法就是找连通分量,可又是加权图,没什么好思路,那就转换题意:由求回路权值->判负环,求最小值->常用二分答案. 二 ...
-
Linux企业级开发技术(4)——epoll企业级开发之epoll例程
为了使大家更加深入了解epoll模型在企业应用中的使用,下面给出一段基于epoll的服务器代码,并在代码中添加了详细注释: #include <deque> #include <ma ...