在前面的几篇文章中,我们重点分析了如果通过fork, vfork, pthread_create去创建一个进程或者线程,以及后面说了在内核层面do_fork的实现。目前为止我们已经了解到一个进程是如何创建的。
既然创建了一个进程,那这个进程肯定要去运行,执行它的使命。而进程何时被执行,在计算机系统中需要调度器来选择。所以我们从今天要开启一系列调度相关的知识了。
计算机设计的时候为何会设计调度器? 所谓是“无规律,不成方圆”。想必是计算机系统也是参考日常生活来设计的。举一个很切合实际的例子。
假如有20个人(进程)去火车站买票,而火车站只有一个窗口(CPU)的时候。分为如下场景
很明显在有调度器的情况下,计算机系统才可以胜利的运行起来。所以计算机系统中的调度器就是让所有的进程已某种次序排列,然后从中选择一个最优的进程让CPU执行。
上面的例子是一个很简单的例子,可是在计算机系统中进程的种类众多,而且各个进程的行为也不一样,这对调度器的设计有增加了难度。
Linux操作目前使用的场景很多,比如常见手机终端,家里的智能终端,开发人员使用的PC机器,一些大厂使用的云服务机器。这些机器里面基本都安装了Linux操作。而linux操作系统为了应对各个场景,调度器就必须要考虑的很全面。
在看调度器应该如何设计之前,我们来 看下进程的分类以及进程的行为,最后总结下调度器应该如何设计。
进程按照优先级类分类的话,分为两类
按照进程的行为分类的话,分为两类:
对应的CPU消耗型要求的是吞吐量比较大,IO消耗型一般要求响应比较及时。所以又出现两个概念:
以上这些因素都是调度器设计需要考虑的。而一个完善的调度器就需要考虑到上面的几点,需要在吞吐和响应之间做一个平衡。既不能让系统吞吐率很高,但是响应很慢。也不能为了提高响应,则不管系统的吞吐率。
提到为了更快响应,操作系统提供了一个抢占(preempt)机制,当一个高优先级的实时任务马上要运行时,就需要抢占低优先级的。
Linux系统为了应对各种复杂的场景,将市场的设备分为了Desktop & Server & Mobile Devices,这类设备的区别就是:
linux内核提供了一个宏来供选择,这个宏主要是区分是否内核打开抢占:
│ 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:
│
│ 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:
│
│ 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消耗型进程一般都是在吃CPU,CPU利用率很高;而IO消耗型大多数是在等IO,比如等待键盘,鼠标,基本不占用CPU。但是面向用户来说,如果当我按下键盘半天不响应,用户基本机会炸的。有些急性子的人估计就重启了,所以来说调度器更偏向于IO消耗型进程。为了及时的响应IO消耗型进程,则就是提高优先级,或者我可以强制你等机制。
前面说了进程分为普通进程和实时进程,则Linux提出了进程的优先级来区分普通进程和实时进程;而对普通进程和实时进程分别采用不同的调度策略。关于进程的行为,Linux提出了赏罚机制,通过nice值来设定。
进程刚创建之初nice大家都是相等,这里说的是普通进程。通过进程运行的行为,比如此进程是CPU消耗型,一直狂吃CPU,则通过nice调整降低其优先级;对于IO消耗型进程,则调整期nice值来增大优先级。这样一来IO消耗型进程的优先级就比CPU消耗型进程的优先级高了,则就会做到及时的响应。当CFS调度算法引进之后,普通进程采用了CFS调度算法,通过一课红黑树来选择进程的最新虚拟时间来达到各个普通进程之间的完全公平。
Linux内核使用0-139的数值来表示进程的优先级。0-99是给实时进程使用,100-139是给普通进程使用。数值越大,优先级越小。
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来干这些事情,显然有点浪费,而且功耗比较高。
假如目前机器是单核的CPU。两个不同的任务分别使用大core和小core跑,然后对比下。
就那小米10开卖之前的两个实验:1.小米10计算圆周率 2.小米10拷贝10G大文件
场景1:
场景2:
对于第一个实验其任务就是CPU消耗型的任务,对于第二个实验其任务就是IO消耗型任务,IO消耗型任务重要的是能被及时调度到,CPU强悍不强悍的无所谓。