进程调度开篇

Posted Loopers

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程调度开篇相关的知识,希望对你有一定的参考价值。

在前面的几篇文章中,我们重点分析了如果通过fork, vfork, pthread_create去创建一个进程或者线程,以及后面说了在内核层面do_fork的实现。目前为止我们已经了解到一个进程是如何创建的。

既然创建了一个进程,那这个进程肯定要去运行,执行它的使命。而进程何时被执行,在计算机系统中需要调度器来选择。所以我们从今天要开启一系列调度相关的知识了。

 

为何要有调度器

计算机设计的时候为何会设计调度器? 所谓是“无规律,不成方圆”。想必是计算机系统也是参考日常生活来设计的。举一个很切合实际的例子。

假如有20个人(进程)去火车站买票,而火车站只有一个窗口(CPU)的时候。分为如下场景

  • 20个人杂乱无章的一股脑冲到窗口去抢票,而窗口只有一个,售票人员那么多手伸进来,都不知道选择哪个
  • 20个人在安保人员(调度器)管理下,安静有序以先来后到的次序排成一队,就算窗口只有一个,有条不紊的处理起来,效率还是比较快的。
  • 现实生活中肯定有情况紧急的,优先级高的,比如孕妇,军人(高优先级进程)等。则安保人员(调度器)肯定会让他们先买票的。

很明显在有调度器的情况下,计算机系统才可以胜利的运行起来。所以计算机系统中的调度器就是让所有的进程已某种次序排列,然后从中选择一个最优的进程让CPU执行。

 

调度器应该如何设计

上面的例子是一个很简单的例子,可是在计算机系统中进程的种类众多,而且各个进程的行为也不一样,这对调度器的设计有增加了难度。

Linux操作目前使用的场景很多,比如常见手机终端,家里的智能终端,开发人员使用的PC机器,一些大厂使用的云服务机器。这些机器里面基本都安装了Linux操作。而linux操作系统为了应对各个场景,调度器就必须要考虑的很全面。

在看调度器应该如何设计之前,我们来 看下进程的分类以及进程的行为,最后总结下调度器应该如何设计。

 

进程的分类

进程按照优先级类分类的话,分为两类

  • 实时进程: 既然叫实时进程,则就对快速响应的要求就很高。比如航天系统,里面的计算就要求必须是实时的。否则就会出大事情,所以航天方面的操作系统一般不用linux,因为linux不是实时操作系统。
  • 普通进程: 普通进程就是实际进程的反面,对实时性要求不是很高,但是普通进程还是需要得到CPU的调度,不能饿死。

按照进程的行为分类的话,分为两类:

  • CPU消耗型: CPU消耗型,此类进程就是一直占用CPU忙于计算,CPU利用率贼高,比如编译android系统则就是CPU消耗型,服务器上的后台程序,都是默默无闻的在一直干活
  • IO消耗型: IO消耗型就是会涉及到IO,需要和用户交互,比如键盘输入,鼠标滑动,占用CPU不是很高,大多数时间实在等待IO。

对应的CPU消耗型要求的是吞吐量比较大,IO消耗型一般要求响应比较及时。所以又出现两个概念:

  • 吞吐率: 单位时间内占用CPU的时间,单位时间内占用CPU的时间越多,则吞吐率就越高。吞吐越高则进程通常是CPU消耗型。
  • 响应:响应则就是反应的速度,当按键键盘,或者滑动鼠标响应及不及时,一般响应对应的进程是IO消耗型

 

以上这些因素都是调度器设计需要考虑的。而一个完善的调度器就需要考虑到上面的几点,需要在吞吐和响应之间做一个平衡。既不能让系统吞吐率很高,但是响应很慢。也不能为了提高响应,则不管系统的吞吐率。

提到为了更快响应,操作系统提供了一个抢占(preempt)机制,当一个高优先级的实时任务马上要运行时,就需要抢占低优先级的。

 

抢占机制介绍

Linux系统为了应对各种复杂的场景,将市场的设备分为了Desktop & Server & Mobile Devices,这类设备的区别就是:

  • Server主要的工作是一直狂占CPU,CPU的利用率居高不下。Server大多数进程都是CPU消耗型。因为大多数是没界面的,所以很少需要IO,偶尔敲个键盘。
  • DeskTop主要是面向用户的,需要和用户打交道,则就需要快速的响应,但是Desktop有时候也需要来处理一些复杂的运算。主要是还是看用户的行为。开发人员常用来编译android
  • Mobile主要是面向终端用户,尤其是手机,现在已经做到随影不离了。则更需要及时的响应,如果那个品牌的手机打开微信半天没反应,估计就被吐槽的不行了。
     

linux内核提供了一个宏来供选择,这个宏主要是区分是否内核打开抢占:

  • CONFIG_PREEMPT_NONE: 不打开抢占,主要是面向Server,不能保证会遇到低延迟。因为CPU在忙做计算,当输入键盘之后,因为没有抢占,可能需要一段时间等待键盘输入的进程才会被CPU调度

│ CONFIG_PREEMPT_NONE:                                                                                                                  
│                                                                                                                                      
│ This is the traditional Linux preemption model, geared towards                                                                       
│ throughput. It will still provide good latencies most of the                                                                         
│ time, but there are no guarantees and occasional longer delays                                                                       
│ are possible.                                                                                                                        
│                                                                                                                                      
│ Select this option if you are building a kernel for a server or                                                                      
│ scientific/computation system, or if you want to maximize the                                                                        
│ raw processing power of the kernel, irrespective of scheduling                                                                       
│ latencies.
  • CONFIG_PREEMPT_VOLUNTARY: 自愿的抢占,通过增加一些抢占点来实现低延迟高响应,但是往往会牺牲一些吞吐量的。适用用Desktop版本
│ CONFIG_PREEMPT_VOLUNTARY:                                         
│                                                                   
│ This option reduces the latency of the kernel by adding more      
│ "explicit preemption points" to the kernel code. These new        
│ preemption points have been selected to reduce the maximum        
│ latency of rescheduling, providing faster application reactions,  
│ at the cost of slightly lower throughput.                         
│                                                                   
│ This allows reaction to interactive events by allowing a          
│ low priority process to voluntarily preempt itself even if it     
│ is in kernel mode executing a system call. This allows            
│ applications to run more 'smoothly' even when the system is       
│ under load.
  • CONFIG_PREEMPT : 完全的打开抢占,此版本会被CONFIG_PREEMPT_VOLUNTARY抢占点更多,而且更激进,当然代价也是牺牲吞吐的。一般多用于手机设备
│ CONFIG_PREEMPT:                                                                                      
│                                                                                                      
│ This option reduces the latency of the kernel by making                                              
│ all kernel code (that is not executing in a critical section)                                        
│ preemptible.  This allows reaction to interactive events by                                          
│ permitting a low priority process to be preempted involuntarily                                      
│ even if it is in kernel mode executing a system call and would                                       
│ otherwise not be about to reach a natural preemption point.                                          
│ This allows applications to run more 'smoothly' even when the                                        
│ system is under load, at the cost of slightly lower throughput                                       
│ and a slight runtime overhead to kernel code.

CPU消耗型和IO消耗型,调度器更偏向谁?

前面说了CPU消耗型进程一般都是在吃CPU,CPU利用率很高;而IO消耗型大多数是在等IO,比如等待键盘,鼠标,基本不占用CPU。但是面向用户来说,如果当我按下键盘半天不响应,用户基本机会炸的。有些急性子的人估计就重启了,所以来说调度器更偏向于IO消耗型进程。为了及时的响应IO消耗型进程,则就是提高优先级,或者我可以强制你等机制。

总结调度器设计需要考虑的几点:

  • 系统的响应必须要快
  • 系统的吞吐量throughtput要高
  • 而且对于进程之间要做到公平,确保每个进程都有运行的权利
  • 对移动设备来说,功耗是个硬伤,也是需要考虑的。

 

Linux是如何设计调度器的?

前面说了进程分为普通进程和实时进程,则Linux提出了进程的优先级来区分普通进程和实时进程;而对普通进程和实时进程分别采用不同的调度策略。关于进程的行为,Linux提出了赏罚机制,通过nice值来设定。

进程刚创建之初nice大家都是相等,这里说的是普通进程。通过进程运行的行为,比如此进程是CPU消耗型,一直狂吃CPU,则通过nice调整降低其优先级;对于IO消耗型进程,则调整期nice值来增大优先级。这样一来IO消耗型进程的优先级就比CPU消耗型进程的优先级高了,则就会做到及时的响应。当CFS调度算法引进之后,普通进程采用了CFS调度算法,通过一课红黑树来选择进程的最新虚拟时间来达到各个普通进程之间的完全公平。

优先级:

Linux内核使用0-139的数值来表示进程的优先级。0-99是给实时进程使用,100-139是给普通进程使用。数值越大,优先级越小。

nice:

nice值的取值范围是-20 ~ 19。nice值越大优先级越小。nice值只适用普通进程,nice值和实时进程没关系。

调度策略:

调度策略调度算法适用对象
SCHED_FIFO先进先出,同等优先级的实时进程采用先进先出实时进程
SCHED_RR轮流调度,同等优先级的实时进程采用轮流调度实时进程
SCHED_NORAML目前最新普通进程的调度算法是CFS调度算法普通进程
SCHED_DEADLINE最早期限DL进程
SCHED_IDLE当某cpu上无任务时,才会选择idle进程普通进程

功耗:

功耗问题也是近几年比较大热的问题,移动设备如何能保持低功耗高性能呢? 对此ARM芯片公司提出了bigLittLE架构。

bigLITTLE架构就是当一个SOC上有8个cpu时,通常是分为2个cluster,每个cluster中是4个CPU。其中一个cluster中是大core,比如全是A77, 另外一个cluster全是小core,比如全是A73。

这东西有什么用? 通俗的意思就是大core尽量去干一些大事,比如需要狂吃CPU的运算;小core干小事,比如响应手指的按下,一个小core搓搓有余,如果一个大core来干这些事情,显然有点浪费,而且功耗比较高。

  • 大core尽量去做大运算,这时候大core的威力也能发挥出来,效率也提高了。则就是多让大core干CPU消耗型任务。
  • 小core尽量去做IO消耗型任务,因为IO消耗型占用CPU不需要高,大多数是在IO。IO消耗型进程需要的是及时被调度。如果及时被调度则响应就上去了。
  • 这样一来不仅节省了功耗,而且还出色完成任务。

 

举个例子:

假如目前机器是单核的CPU。两个不同的任务分别使用大core和小core跑,然后对比下。

就那小米10开卖之前的两个实验:1.小米10计算圆周率   2.小米10拷贝10G大文件

  • 小米10计算圆周率卖点是CPU的计算能力
  • 小米10拷贝10G大文件卖点是UFS3.0技术

 

场景1:

  • 大core 和小core都来计算圆周率,很明显大core胜出,因为大core计算能力强。这种事情就应该让大core上。

场景2:

  • 通过大core来拷贝10G大文件,其计算机行为是:前面的1ms是大core用来发送拷贝的指令,然后CPU就处于等待。后面60s全是往UFS拷贝东西,真正干活的是UFS
  • 通过小core来拷贝10G大文件,其计算机行为是:前面的2ms是小core用来发送拷贝的指令,然后CPU就处于等待。后面60s全是往UFS拷贝东西,真正干活的是UFS。假设小core比大core慢了1ms。
  • 这么比较下来,其实这个活小core就可以干,完全不需要大core来参与的。如果IO消耗型任务不能被及时调度,则就需要在多等20ms

对于第一个实验其任务就是CPU消耗型的任务,对于第二个实验其任务就是IO消耗型任务,IO消耗型任务重要的是能被及时调度到,CPU强悍不强悍的无所谓。

以上是关于进程调度开篇的主要内容,如果未能解决你的问题,请参考以下文章

磁盘调度算法

操作系统_第三章处理器管理_进程的调度算法

常见调度算法总结(对比分析表)

先来先服务调度算法。 优先级调度算法。 短作业优先调度算法 轮转调度算法 响应比高优先调度算法

Python模拟页面调度LRU算法

linux IO调度