进程间调度
当一个计算机是多道程序设计系统时,会频繁的有很多进程或者线程来同时竞争CPU时间片。当两个或两个以上的进程/线程处于就绪状态时,就会发生这种情况。如果只有一个CPU可用,那么必须选择接下来哪个进程/线程可以运行。操作系统中有一个叫做调度程序(scheduler)的角色存在,它就是做这件事儿的,该程序使用的算法叫做调度算法(scheduling algorithm) 。
尽管有一些不同,但许多适用于进程调度的处理方法同样也适用于线程调度。当内核管理线程的时候,调度通常会以线程级别发生,很少或者根本不会考虑线程属于哪个进程。下面我们会首先专注于进程和线程的调度问题,然后会明确的介绍线程调度以及它产生的问题。
读操作不需要获得任何锁就可以访问,不使用原子操作。写操作在访问前需要先复制一份副本,然后对副本进行修改,最后使用一个回调机制,在适当的时机把指向原来数据的指针重新指向新的被修改的数据。
调度介绍
让我们回到早期以磁带上的卡片作为输入的批处理系统的时代,那时候的调度算法非常简单:依次运行磁带上的每一个作业。对于多道程序设计系统,会复杂一些,因为通常会有多个用户在等待服务。一些大型机仍然将 批处理和 分时服务结合使用,需要调度程序决定下一个运行的是一个批处理作业还是终端上的用户。由于在这些机器中 CPU 是稀缺资源,所以好的调度程序可以在提高性能和用户的满意度方面取得很大的成果。
进程行为
几乎所有的进程(磁盘或网络)I/O 请求和计算都是交替运行的。
如上图所示,CPU 不停顿的运行一段时间,然后发出一个系统调用等待 I/O 读写文件。完成系统调用后,CPU 又开始计算,直到它需要读更多的数据或者写入更多的数据为止。当一个进程等待外部设备完成工作而被阻塞时,才是 I/O 活动。
上面 a 是 CPU 密集型进程;b 是 I/O 密集型进程,a 因为在计算的时间上花费时间更长,因此称为计算密集型(compute-bound) 或者 CPU 密集型(CPU-bound) ,b 因为I/O 发生频率比较快因此称为 I/O 密集型(I/O-bound)。计算密集型进程有较长的 CPU 集中使用和较小频度的 I/O 等待。I/O 密集型进程有较短的 CPU 使用时间和较频繁的 I/O 等待。注意到上面两种进程的区分关键在于 CPU 的时间占用而不是 I/O 的时间占用。I/O 密集型的原因是因为它们没有在 I/O 之间花费更多的计算、而不是 I/O 请求时间特别长。无论数据到达后需要花费多少时间,它们都需要花费相同的时间来发出读取磁盘块的硬件请求。
值得注意的是,随着 CPU 的速度越来越快,更多的进程倾向于 I/O 密集型。这种情况出现的原因是 CPU 速度的提升要远远高于硬盘。这种情况导致的结果是,未来对 I/O 密集型进程的调度处理似乎更为重要。这里的基本思想是,如果需要运行 I/O 密集型进程,那么就应该让它尽快得到机会,以便发出磁盘请求并保持磁盘始终忙碌。
何时调度
第一个和调度有关的问题是何时进行调度决策。存在着需要调度处理的各种情形。首先,在创建一个新进程后,需要决定是运行父进程还是子进程。因为二者的进程都处于就绪态下,这是正常的调度决策,可以任意选择,也就是说,调度程序可以任意的选择子进程或父进程开始运行。
第二,在进程退出时需要作出调度决定。因为此进程不再运行(因为它将不再存在),因此必须从就绪进程中选择其他进程运行。如果没有进程处于就绪态,系统提供的空闲进程通常会运行
空闲进程
空闲进程(system-supplied idle process) 是 Microsoft 公司 windows 操作系统带有的系统进程,该进程是在各个处理器上运行的单个线程,它唯一的任务是在系统没有处理其他线程时占用处理器时间。System Idle Process 并不是一个真正的进程,它是核心虚拟出来的,多任务操作系统都存在。在没有可用的进程时,系统处于空运行状态,此时就是System Idle Process 在正在运行。你可以简单的理解成,它代表的是 CPU 的空闲状态,数值越大代表处理器越空闲,可以通过 Windows 任务管理器查看 Windows 中的 CPU 利用率
第三种情况是,当进程阻塞在 I/O 、信号量或其他原因时,必须选择另外一个进程来运行。有时,阻塞的原因会成为选择进程运行的关键因素。例如,如果 A 是一个重要进程,并且它正在等待 B 退出关键区域,让 B 退出关键区域从而使 A 得以运行。但是调度程序一般不会对这种情况进行考量。
第四点,当 I/O 中断发生时,可以做出调度决策。如果中断来自 I/O 设备,而 I/O 设备已经完成了其工作,那么那些等待 I/O 的进程现在可以继续运行。由调度程序来决定是否准备运行新的进程还是重新运行已经中断的进程。
如果硬件时钟以 50 或 60 Hz 或其他频率提供周期性中断,可以在每个时钟中断或第 k 个时钟中断处做出调度决策。根据如何处理时钟中断可以把调度算法可以分为两类。非抢占式(nonpreemptive) 调度算法挑选一个进程,让该进程运行直到被阻塞(阻塞在 I/O 上或等待另一个进程),或者直到该进程自动释放 CPU。即使该进程运行了若干个小时后,它也不会被强制挂起。这样会在时钟中断发生时不会进行调度。在处理完时钟中断后,如果没有更高优先级的进程等待,则被中断的进程会继续执行。
另外一种情况是 抢占式 调度算法,它会选择一个进程,并使其在最大固定时间内运行。如果在时间间隔结束后仍在运行,这个进程会被挂起,调度程序会选择其他进程来运行(前提是存在就绪进程)。进行抢占式调度需要在时间间隔结束时发生时钟中断,以将 CPU 的控制权交还给调度程序。如果没有可用的时钟,那么非抢占式就是唯一的选择。
调度算法分类
毫无疑问,不同的环境下需要不同的调度算法。之所以出现这种情况,是因为不同的应用程序和不同的操作系统有不同的目标。也就是说,在不同的系统中,调度程序的优化也是不同的。这里有必要划分出三种环境
批处理(Batch)
交互式(Interactive)
实时(