《深入Linux内核架构》(2)进程管理和调度

  • 所有现代操作系统都可以“同时”运行若干进程。
    • 只有一个处理器的系统只能在给定时刻运行一个程序
    • 在多处理器系统中可以真正并行运行的进程数目取决于物理CPU的数目
  • 为什么内核和处理器可以给我们建立多任务的错觉,即可以并行运行多个程序?
    • 短时间内系统运行的应用程序不停切换。切换时间间隔很短,短到用户无法察觉。
  • 这种系统管理方式引起了几个问题,内核必须解决下面这些问题
    • 除非明确地要求,否则应用程序不能彼此干扰。由于Linux是一个多用户系统,它也必须确保程序不能读取或修改其他程序的内存,否则就很容易访问其他用户的私有数据。(可以使用存储保护实现,将在第3章进行处理)
    • CPU时间必须在各种应用程序之间尽可能公平地共享
  • 本章主要介绍内核共享CPU时间的方法,以及如何在进程之间切换。这里有两个任务,其执行是相对独立的。
    • 内核必须决定为各个进程分配多长时间,何时切换到下一个进程。这又引出了哪个进程是下一个的问题。此类决策是平台无关的。
    • 在内核从进程A切换到进程B时,必须确保进程B执行环境与上一次撤销其处理器资源时完全相同。例如,处理器寄存器的内容和虚拟地址空间的结构必须与此前相同。(这项工作与处理器极度相关。不能只用C语言实现,还需要汇编代码的帮助)
    • 上面两个任务是称之为调度器的内核子系统的职责。CPU时间如何分配取决于调度器策略,这与用于在各个进程之间切换的任务切换机制完全无关

1、进程优先级

  • 进程可分为实时进程非实时进程,而实时进程又可以分为硬实时进程软实时进程

    • 硬实时进程:有严格的时间限制,某些任务必须在指定的时限内完成
      • 一个例子:飞控软件
      • 注意,这并不意味着所要求的时间范围特别短,而是要保证在极端的情况下也决不会超过某一时间范围。
      • Linux在主流的内核中不支持硬实时进程,而一些修改版本比如RTLinux则支持。
    • 软实时进程:是硬实时进程的一种弱化形式。尽管仍然需要快速得到结果,但稍微晚一点不会造成重大后果。
    • 普通进程:大多数进程,没有特定时间约束。但仍然可以根据重要性来分配优先级。
  • CPU时间分配简图
    《深入Linux内核架构》(2)进程管理和调度_第1张图片

    • 进程的运行按时间片调度,分配给进程的时间片份额与其相对重要性相当
    • 系统中时间的流动对应于圆盘的转动,CPU由圆周旁的“扫描器”表示。
    • 这种方案称之为 抢占式多任务处理(preemptive multitasking)各个进程都分配到一定的时间段可以执行。时间段到期后,内核会从进程收回控制权,让一个不同的进程运行,而不考虑前一进程所执行的上一个任务。
    • 被抢占进程的运行时环境,即所有CPU寄存器的内容和页表,都会保存起来,因此其执行结果不会丢失。在该进程恢复执行时,其进程环境可以完全恢复。时间片的长度会根据进程重要性(以及因此而分配的优先级)的不同而变化。
  • 上图的CPU时间分配简图是不准确的,因为它没有考虑下面几个重要问题

    • 进程在某些时间可能因为无事可做而无法立即执行,这样会导致CPU资源的巨大浪费,所以这样的进程应该避免。图中假定的所有进程都是可以立即运行的,显然不现实。
    • Linux支持不同的调度类别(在进程之间完全公平的调度和实时调度),调度时也必须考虑到这一点。
    • 此外,在有重要的进程变为就绪状态可以运行时,有一种选项是抢占当前的进程,图中也没有反映出这一点。
  • 调度器代码的更新迭代

    • Linux2.5使用的是O(1)调度器。O(1)调度器一个特别的性质是,它可以在常数时间内完成其工作,不依赖于系统上运行的进程数目
    • Linux2.6.23使用的是 完全公平调度器(CFS,completely fair scheduler) ,该调度器的关键特性是,它试图尽可能地模仿理想情况下的公平调度。此外,它不仅可以调度单个进程,还能够处理更一般性的 调度实体(scheduling entity) 。例如,该调度器分配可用时间时,可以首先在不同用户之间分配,接下来在各个用户的进程之间分配。

2、进程生命周期

  • 进程可能有以下几种状态。
    • 运行:该进程此刻正在执行。
    • 等待:进程能够运行,但没有得到许可,因为CPU分配给另一个进程。调度器可以在下一次任务切换时选择该进程。
    • 睡眠:进程正在睡眠无法运行,因为它在等待一个外部事件。调度器无法在下一次任务切换时选择该进程。
  • 系统将所有进程保存在一个进程表中,无论其状态是运行、睡眠或等待。但睡眠进程会特别标记出来,调度器会知道它们无法立即运行(具体实现,请参考3节)。睡眠进程会分类到若干队列中,因此它们可在适当的时间唤醒,例如在进程等待的外部事件已经发生时。
  • 下图描述了进程的几种状态及其转换。
    《深入Linux内核架构》(2)进程管理和调度_第2张图片
    • 路径①:如果进程必须等待事件,则其状态运行改变为睡眠
    • 路径②:在调度器决定从该进程收回CPU资源时(可能的原因稍后讲述),过程状态从运行改变为等待
    • 路径③:在所等待的事件发生后,处于睡眠状态进程先变回到等待状态,然后重新回到正常循环。
    • 路径④:在分配CPU时间之后,进程由等待状态改变为运行
    • 路径⑤:在程序执行终止(例如,用户关闭应用程序)后,过程状态由运行变为终止
  • 上文没有列出僵尸状态,下面是关于僵尸状态的解释:
    • 什么是僵尸状态?
      • 进程已经死亡,但仍然以某种方式活着。实际上,说这些进程死了,是因为其资源(内存、与外设的连接,等等)已经释放,因此它们无法也决不会再次运行。说它们仍然活着,是因为进程表中仍然有对应的表项
    • 僵尸状态产生的原因
      • 条件1:程序必须由另一个进程或一个用户杀死(通常是通过发送SIGTERMSIGKILL信号来完成,这等价于正常地终止进程);
      • 条件2:进程的父进程在子进程终止时必须调用或已经调用wait4(读做wait for)系统调用。 这相当于向内核证实父进程已经确认子进程的终结。该系统调用使得内核可以释放为子进程保留的资源。
      • 只有在条件1发生(程序终止)而条件2不成立的情况下(wait4),才会出现“僵尸”状态
    • 僵尸进程残留带来的影响
      • 在进程终止之后,其数据尚未从进程表删除之前,进程总是暂时处于 僵尸状态。
      • 有时候(例如,如果父进程编程极其糟糕,没有发出wait调用),僵尸进程可能稳定地寄身于进程表中,直至下一次系统重启。
      • 从进程工具(如pstop)的输出,可以看到僵尸进程。因为残余的数据在内核中占据的空间极少,所以这几乎不是问题。

补注1:抢占式多任务处理

  • Linux进程管理的结构还需要另外两种进程状态选项:用户状态核心态,什么是用户状态和核心态?
    • 进程通常都处于用户状态,只能访问自身的数据,无法干扰系统中的其他应用程序,甚至也不会注意到自身之外其他程序的存在。
    • 如果进程想要访问系统数据或功能(后者管理着所有进程之间共享的资源,例如文件系统空间),则必须切换到核心态
  • 用户状态切换到核心态的方法有哪些?
    • 第一种方法:系统调用。系统调用是由用户应用程序有意调用的。
    • 第二种方法:中断。发生中断时,其发生或多或少是不可预测的,用户状态切换到核心态的过程是自动触发的。处理中断的操作,通常与中断发生时执行的进程无关。
  • 内核的抢占调度模型建立了一个层次结构,用于判断哪些进程状态可以由其他状态抢占。
    • 普通进程总是可能被抢占,甚至是由其他进程抢占。在一个重要进程变为可运行时,例如编辑器接收到了等待已久的键盘输入,调度器可以决定是否立即执行该进程,即使当前进程仍然在正常运行。对于实现良好的交互行为和低系统延迟,这种抢占起到了重要作用。
    • 如果系统处于核心态并正在处理系统调用,那么系统中的其他进程是无法夺取其CPU时间的。调度器必须等到系统调用执行结束,才能选择另一个进程执行,但中断可以中止系统调用(在进行重要的内核操作时,可以停用几乎所有的中断)。
    • 中断可以暂停处于用户状态和核心态的进程。中断具有最高优先级,因为在中断触发后需要
      尽快处理。
  • Linux内核支持 内核抢占(kernel preemption) 选项。该选项支持在紧急情况下切换到另一个进程,甚至当前是处于核心态执行系统调用(中断处理期间是不行的)。

3、进程表示

你可能感兴趣的:(《深入Linux内核架构》,linux,架构,运维)