计算机底层知识拾遗(十)理解进程调度【转】

转自:http://www.cnblogs.com/zfyouxi/p/4504042.html

这篇说说内核的进程调度机制,进程调度是内核的一个重要工作,由调度器完毕。

进程状态

内核调度器调度的实体(KSE, kernal schedule entry)是进程和线程。内核必须知道全部进程和线程的状态,比方把时间片给一个堵塞的进程是没有意义的。从内核的角度来看,进程的状态有3种:

1. 执行,表示正在执行的进程

2. 等待,没有执行,可是等待时间片执行的进程

3. 睡眠,也就是堵塞,包含可中断的堵塞和不可中断的堵塞。睡眠的进程在等待一个事件的发生,调度器无法在下一次任务切换时选择睡眠的进程

进程在几种状态中不断的切换

1表示执行的进程要等待某个事件,进入到了睡眠状态

2表示执行的进程交出CPU资源,进入到了等待状态

3表示睡眠的进程等待的事件发生了,它进入到等待状态,不能直接进入到执行状态

4表示等待的进程获得CPU资源,变成执行状态

5表示执行的进程结束,进入到终止状态

内核将全部的进程保存在一个进程表中,不论是执行,等待,睡眠。睡眠的进程会被特别标记出来,调度器知道它们无法马上执行,就不会在下一个任务切换时选择它们。睡眠的进程被分在多个队列中,它们会在适当的时候被唤醒。对于睡眠的进程,又分为两种:

1. TASK_INTERUPTIBLE,可中断的睡眠,当内核发送信号给该进程表示它等待的事情已经发生时,响应信号处理程序把进程状态改成TASK_RUNNING,表示进入可执行状态,仅仅要调度器选中它就能够执行

2. TASK_UNINTERUPTIBLE,不可中断的睡眠,不能由外部信号唤醒,即不响应外部信号,仅仅能由内核亲自唤醒

所谓的僵尸进程是指进程资源已经被释放,可是还保存在进程表中的进程。通常造成僵尸进程的原因是子进程已经被终结,可是父进程没有调用wait4系统调用来确认父进程知道子进程已死亡。这样子进程因为没有被父进程确认死亡,可是已经释放了资源,变成了僵尸进程。

从运行权限的维度来考察进程状态,进程状态分为用户态和核心态。用户态仅仅能訪问进程自身的数据,是受限的。核心态具有无限权限,能够訪问随意数据。

用户态到核心态的切换有两种方式,一种是用户态运行系统调用,会切换到核心态。另外一种是中断,其中断发生时,也会切换到核心态。

对于抢占式调度模型来说,

1. 中断具有最高权限,能够抢占处于用户态或者核心态的进程的时间片

2. 当进程处于核心态并运行系统调用时,不能被其它进程抢占,当然中断除外

3. 在用户态运行的进程能够随时被抢占

调度器

调度器主要解决两个问题

1. 调度策略,即决定为每一个进程分配多少执行时间,何时切换到下一个进程,下一个进程是什么

2. 上下文切换,即从进程A切换到进程B时,要保证进程B的运行环境和上次被撤销运行时全然一致,比方寄存器的内容,虚拟地址空间的各个数据结构。

Linux的调度器和传统基于时间片的调度器有所差别,它考虑的是进程的等待时间,即全部可执行的进程在一个就绪队列里面等待的时间。对CPU时间要求最严格的进程被挑选执行。就绪队列中的进程被组织成一棵红黑树来加速操作。等待最长的进程在最左边。

调度器子系统的主要组件例如以下,

1. 主调度器和周期性调度器称为通用调度器,来确定是否要调度。前者处理进程打算睡眠或者由于某种原因放弃CPU的情况,后者以周期性频率执行,检測是否须要上下文切换

2. 调度器类来挑选下一个执行的进程。调度器类分装了不同的调度算法,比方全然公平调度,实时调度,以模块的方式执行

3. 选中下一个要运行的进程后,就要进行上下文切换,须要和CPU紧密结合

4. 每一个进程都属于一个特定的调度器类,由调度器类来管理所属的进程,通用调度器不涉及进程的状态

进程的task_struct结构中和调度相关的属性例如以下

1. prio, static_prio,
normal_prio表示进程的优先级信息。static_prio表示静态优先级,也就是进程启动时分配的nice优先级值。normal_prio是基于static_prio和调度策略计算出来的优先级,进程分支时,子进程基础normal_prio。prio是调度器考虑的优先级,有时候内核要暂时提升某个进程的优先级,就是改动prio值,不影响static_prio和normal_prio

2. sched_class表示该进程所属的调度器类

3. sched_entity表示进程所属的调度实体,调度器不仅能够调度进程,还能够调度进程组,线程等调度实体

4. policy表示进程的调度策略,比方SCHED_NORMAL调度普通进程,用全然公平调度器类来处理。SCHED_BATCH,SCHED_IDEL,SCHED_RR,SCHED_FIFO等

5. time_slice指定该进程能够使用的剩余时间片

调度器类必须提供sched_class的实例,指定了调度器类能够进行的操作

1. enqueue_task表示把一个进程增加到就绪队列,当一个进程状态从睡眠变成可执行时,就是发生了这个操作进入到了就绪队列

2. dequeue_task表示把一个进程移出就绪队列,比方一个进程从可执行状态切换到不可执行状态

3. yield_task表示进程自愿放弃CPU控制权的操作

4. check_preempt_curr表示一个新唤醒的进程来抢占当前进程,比方wake_up_new_task唤醒新进程时发生这个操作

5. pick_next_task用于选择下一个运行的进程,向进程提供CPU资源。但在不同进程切换时,还须要运行一个底层的上下文切换

6. task_tick表示激活周期性调度器

7. task_new表示将fork出来的新进程增加到调度器类

用户层程序是无法直接和调度器类交互的,都是通过调度策略常量,比方SCHED_NORMAL,SCHED_BATCH,SCHED_IDEL映射到全然公平调度器类fair_sched_class,
SCHED_RR,SCHED_FIFO映射到实时调度器rt_sched_class。

每一个CPU都相应一个就绪队列,一个活动进程仅仅能出如今一个就绪队列中。对于仅仅使用进程的程序来说,一个进程仅仅能同一时候在一个CPU执行。而基于线程的程序来说,源于一个进程的不同线程能够同一时候执行在多个CPU。就绪队列的结构例如以下

1. nr_running和load表示当前就绪队列的负载情况。就绪队列的虚拟时钟的速度就是基于这个信息

2. cfs_rq是全然公平调度器类的子就绪队列,rt_rq是实时调度器类的就绪队列

3. curr指向当前正在执行的进程

4. clock用于实现就绪队列自身的时钟,每次调用周期性调度器都会更新clock值

调度实体的结构例如以下,它表示一般性的能够调度的实体,包含进程,进程组,线程等等

1. load表示负载,表示该实体占用队列总负荷的比例。计算负荷是调度器类的一个重任,它影响到虚拟时钟的速度

2. run_node表示红黑树的节点,让这个实体能够出如今红黑树上

3. on_rq来表示该实体是否处于就绪队列

4.
exec_start表示进程每次開始运行的时间,sum_exec_runtime表示该进程总共运行了的时间。每次进程运行開始时,都会记录exec_start值,然后每次调用update_curr系统调用都会用当前时间减去exec_start,把差值加到sum_exec_runtime中。

5. 当进程被撤销CPU时,会把sum_exec_runtime保存到pre_sum_exec_runtime中去。当进程抢占时,sun_exec_time又单调增长。

6. vruntime记录进程执行期间虚拟时钟流式的时间数量

通用调度器有两类,一个是周期性调度器,一个是主调度器。周期性调度器在schedule_tick函数中实现。内核依照频率自己主动调用这个函数,会激活当前进程的调度器类的周期性

调度方法。

假设当前进程须要被又一次调度,那么调度器类会在task_struct中设置TIF_NEED_RESCHED标志,内核会在适当的时机完毕该调度请求。

主调度器负责把CPU从一个进程交给还有一个进程。主调度器在schedule函数中实现。当系统调用返回时,内核会检查当前进程是否设置了重调度标志TIF_NEED_RESCHED。假设设置了该标志,那么内核会调用schedule函数来切换。

1.schedule函数首先确定当前就绪队列,并在prev指针保存当前还在执行的进程task_struct。改动就绪队列的clock值,取消TIF_NEED_RESCHED标志。

2. 假设当前进程处于可中断睡眠状态,而且接收到唤醒信号,那么把当前进程状态改成可执行,否则使进程停止活动,进入睡眠队列。

3.
调用调度器类的put_prev_task通知调度器类当前进程要被还有一个进程所替换。调用pick_next_task选取下一个进程。不一定会选取新的进程,比方其它进程都处于睡眠状态,就一个能够执行的进程。一旦选择了一个进程,那么就要准备执行硬件级的上下文切换

4. context_switch负责运行硬件级的上下文切换

5. 检查重调度标志,假设当前进程设置了TIF_NEED_SCHED标志,就goto到need_resched运行。

来看看上下文切换做了什么工作

1. 先调用和体系结构相关的prepare_task_switch函数为切换做事先准备

2. mm和oldmm表示下一个进程的用户空间虚拟地址上下文实例和上一个进程的用户空间虚拟地址上下文实例

3. switch_mm函数更换由task_struct的mm描写叙述的用户空间虚拟地址上下文,比方载入的页表,刷出TLB。主要是存放在快速缓存和TLB中的供CPU使用的数据。而很多其它的数据保存在内存中,是不须要切换的的,在须要时从内存载入

4.
swtich_to函数切换寄存器和内核栈中的数据,将新进程用户空间程序之前使用到的寄存器内容恢复到寄存器。关于寄存器的内容,有一个要点是当用户态进入到核心态时,会把用户空间程序的寄存器内容保存到内核栈。所以上下文切换时,寄存器内容不须要特别处理。进程总是从核心态開始运行,返回到用户空间时,寄存器的内容会从内核栈恢复。

全然公平调度器类的基本原理是计算进程的虚拟时钟值,虚拟时钟值是度量一个等待的进程能够获得的CPU时间。这个值是由实际时钟和进程的负荷权重计算出来的。全部与虚拟时钟相关的计算都是有update_curr函数相关的。update_curr被周期性调度器触发

就绪队列的红黑树最左边节点是要被选出的节点,红黑树是依照vruntime的值来排序的。红黑树还维护了一个min_vruntime值,表示整棵数最小的虚拟时钟值。当最左边节点的vruntime值大于min_vruntime时,会更新min_vruntime值为最左边节点的vruntime值。

1. vruntime值总是不断增长的,也就是说节点在红黑树中不断右移。当进程进入执行时,它的vruntime会增长。越重要的进程的vruntime值增长越慢,也就是说右移的速度越慢

2. min_vruntime值总是单调增长的。当一个进程进入睡眠时它的vruntime是保持不变的,当它醒来时,它在红黑树中的位置相对左移,被更优先调度。

进程调度是基于就绪队列的,就绪队列中等待的进程都是可执行状态。而睡眠的进程则处于等待队列,在等待队列中的进程不会被调度器选择。而睡眠的进程醒来之后,会进入就绪队列。睡眠的进程有两种,可中断和不可中断。唤醒一个在等待队列中睡眠的进程有几种方式。

1. 等待的信号到达,处理可中断的睡眠进程

2. 等待的事件发生,比方读取网卡数据的进程在网卡没有数据的时候进入不可中断睡眠,当网卡数据达到时,会调用wake_up函数,来唤醒在等待网卡数据的进程

时间: 2024-10-12 11:55:09

计算机底层知识拾遗(十)理解进程调度【转】的相关文章

计算机底层知识拾遗(二)深入理解进程和线程

关于进程和线程,大家总是说的一句话是"进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元".这句话理论上没问题,我们来看看什么是所谓的"资源"呢. 什么是计算机资源 经典的冯诺依曼结构把计算机系统抽象成 CPU + 存储器 + IO,那么计算机资源无非就两种: 1. 计算资源 2. 存储资源 CPU是计算单元,单纯从CPU的角度来说它是一个黑盒,它只对输入的指令和数据进行计算,然后输出结果,它不负责管理计算哪些"指令和数据". 换句话

计算机底层知识拾遗(三)理解磁盘的机制

磁盘是一种重要的存储器,位于主存结构的下方,是永久存储的介质.在计算机底层知识拾遗(一)理解虚拟内存机制 这篇中说了虚拟内存是面向磁盘的,理解磁盘的工作原理对理解计算机的很多概念有很大的帮助.尤其是在数据库和分布式存储领域,要经常和磁盘打交道. 磁盘这块主要有几个部分的概念: 1. 磁盘的基本结构和工作原理 2. 如何在虚拟内存机制下与内存高效地交换数据 3. 磁盘如何保证数据存储的可靠性及故障恢复 磁盘的基本结构和工作原理 从单个磁盘来说,由一个个的同心圆组成,一个同心圆就是一个磁道,每个磁道

计算机底层知识拾遗(四)理解文件系统

操作系统的很多核心组件都是相互关联的,比如虚拟内存管理,物理内存管理,文件系统,缓存系统,IO,设备管理等等,都要放在一起来看才能从整体上理解各个模块到底是如何交互和工作的.这个系列的目的也就是从整体上来理解计算机底层硬件和操作系统的一些重要的组件是如何工作的,从而来指导应用层的开发.这篇讲讲文件系统的重要概念,为后面的IO系统做铺垫. 文件系统主要有三类 1. 位于磁盘的文件系统,在物理磁盘上存储文件,比如NTFS, FAT, ext3, ext4 2. 虚拟文件系统,在内核中生成,没有物理的

计算机底层知识拾遗(六)理解页缓存page cache和地址空间address_space

在这篇计算机底层知识拾遗(五)理解块IO层 中讲了块缓存buffer cache块缓存,这篇说说页缓存page cache以及相关的地址空间address_space的要点. 在Linux 2.4内核中块缓存buffer cache和页缓存page cache是并存的,表现的现象是同一份文件的数据,可能即出现在buffer cache中,又出现在页缓存中,这样就造成了物理内存的浪费.Linux 2.6内核对两个cache进行了合并,统一使用页缓存在做缓存,只有极少数的情况下才使用到buffer

计算机底层知识拾遗(一)理解虚拟内存机制

这个系列会总结计算机,网络相关的一些重要的底层原理.很多底层原理大家上学的时候都学过,但是在学校的时候大部分的同学都是为了应付考试而学习,过几天全忘了.随着工作的时间越久,越体会到这些基础知识的重要性.做技术和练武功一样,当你到了一定的阶段,也会遇到一个瓶颈,突破了你的眼界就会大不同,突破不了,只能困在原地无法成长.我自己深有体会,这些基础知识,底层原理是助你打破瓶颈的灵丹妙药.当理解了一些底层原理之后,会发现现在很多热门技术,原理,常见的设计都是在底层基础上发展而来的. 这篇总结一下单机系统的

计算机底层知识拾遗(七)页缓存数据同步和页回收机制

这篇说说Linux的页缓存数据同步和页回收机制.数据同步和页回收是两个独立的概念,数据同步处理的是内存/缓存的数据和后备设备的数据一致问题,页回收处理的是在内存空间不足时如何回收已分配的物理内存页,来获得足够空间分配干净页,支持优先级更高的工作.数据同步在任意时刻都有可能触发,页回收则是在物理内存使用达到一定阀值的时候触发. 数据同步就是把物理内存和页缓存中的脏页写回到后备设备的文件中去.有两种方式可以调用数据同步 1. 周期性的调用,主要是pdflush机制 2. 强制调用,比如调用sync,

计算机底层知识

1 这篇万字长文,一下子把计算机底层知识说明白了 https://mp.weixin.qq.com/s?__biz=Mzg2NTA4OTUwOQ==&mid=2247486427&idx=3&sn=f03bc1a695a9e2f837673f3012d65c6c&chksm=ce5e2951f929a047413a06b85c26d898ca9126f1369147e35a763542d435134584987a4e45b6&mpshare=1&scene=

计算机基本知识拾遗(七)页面缓存数据的同步和恢复机制页

本讲座Linux页面缓存数据的同步和恢复机制页.数据同步和恢复是两个独立的页面概念.数据同步处理是存储器/数据一致性问题缓存数据和备份设备.页面回收是如何回收分配的内存空间不足的物理内存页.为了获得足够的空间来分配一个干净的页面,支持更高优先级的工作.步在随意时刻都有可能触发,页回收则是在物理内存使用达到一定阀值的时候触发. 数据同步就是把物理内存和页缓存中的脏页写回到后备设备的文件里去.有两种方式能够调用数据同步 1. 周期性的调用,主要是pdflush机制 2. 强制调用,比方调用sync,

计算机基础知识对编程的重要性

码农和架构师的差别在哪里 从题目看文章,大家的第一想法是本篇文章是一篇讲述计算机基础知识和编程之间的关系,但是为什么开篇是这样的呢?当然这样的开篇并没有多大的意义,唯一的目的就是吸引读者而已. 纵观计算机专业的学生在毕业之后大多数会走向编写程序的道路,当然也会有部分同学有自己更感兴趣的事情,从而在毕业之后选择其他的职业.在这里根据我个人在毕业之后的编写程序的道路,写下一点总结,希望能和大家交流,有什么不对的地方,希望大家能给我指正,因为我个人觉得"三人行必有我师"是一句很有道理的话,每