《Linux内核设计与实现》读书笔记(四)- 进程的调度

主要内容:

  • 什么是调度
  • 调度实现原理
  • Linux上调度实现的方法
  • 调度相关的系统调用

1. 什么是调度

现在的操作系统都是多任务的,为了能让更多的任务能同时在系统上更好的运行,需要一个管理程序来管理计算机上同时运行的各个任务(也就是进程)。

这个管理程序就是调度程序,它的功能说起来很简单:

  1. 决定哪些进程运行,哪些进程等待
  2. 决定每个进程运行多长时间

此外,为了获得更好的用户体验,运行中的进程还可以立即被其他更紧急的进程打断。

总之,调度是一个平衡的过程。一方面,它要保证各个运行的进程能够最大限度的使用CPU(即尽量少的切换进程,进程切换过多,CPU的时间会浪费在切换上);另一方面,保证各个进程能公平的使用CPU(即防止一个进程长时间独占CPU的情况)。

2. 调度实现原理

前面说过,调度功能就是决定哪个进程运行以及进程运行多长时间。

决定哪个进程运行以及运行多长时间都和进程的优先级有关。为了确定一个进程到底能持续运行多长时间,调度中还引入了时间片的概念。

2.1 关于进程的优先级

进程的优先级有2种度量方法,一种是nice值,一种是实时优先级。

nice值的范围是-20~+19,值越大优先级越低,也就是说nice值为-20的进程优先级最大。

实时优先级的范围是0~99,与nice值的定义相反,实时优先级是值越大优先级越高。

实时进程都是一些对响应时间要求比较高的进程,因此系统中有实时优先级高的进程处于运行队列的话,它们会抢占一般的进程的运行时间。

进程的2种优先级会让人不好理解,到底哪个优先级更优先?一个进程同时有2种优先级怎么办?

其实linux的内核早就有了解决办法。

对于第一个问题,到底哪个优先级更优先?

答案是实时优先级高于nice值,在内核中,实时优先级的范围是 0~MAX_RT_PRIO-1 MAX_RT_PRIO的定义参见 include/linux/sched.h

1611 #define MAX_USER_RT_PRIO        100
1612 #define MAX_RT_PRIO             MAX_USER_RT_PRIO

nice值在内核中的范围是 MAX_RT_PRIO~MAX_RT_PRIO+40 即 MAX_RT_PRIO~MAX_PRIO

1614 #define MAX_PRIO                (MAX_RT_PRIO + 40)

第二个问题,一个进程同时有2种优先级怎么办?

答案很简单,就是一个进程不可能有2个优先级。一个进程有了实时优先级就没有Nice值,有了Nice值就没有实时优先级。

我们可以通过以下命令查看进程的实时优先级和Nice值:(其中RTPRIO是实时优先级,NI是Nice值)

$ ps -eo state,uid,pid,ppid,rtprio,ni,time,comm
S   UID   PID  PPID RTPRIO  NI     TIME COMMAND
S     0     1     0      -   0 00:00:00 systemd
S     0     2     0      -   0 00:00:00 kthreadd
S     0     3     2      -   0 00:00:00 ksoftirqd/0
S     0     6     2     99   - 00:00:00 migration/0
S     0     7     2     99   - 00:00:00 watchdog/0
S     0     8     2     99   - 00:00:00 migration/1
S     0    10     2      -   0 00:00:00 ksoftirqd/1
S     0    12     2     99   - 00:00:00 watchdog/1
S     0    13     2     99   - 00:00:00 migration/2
S     0    15     2      -   0 00:00:00 ksoftirqd/2
S     0    16     2     99   - 00:00:00 watchdog/2
S     0    17     2     99   - 00:00:00 migration/3
S     0    19     2      -   0 00:00:00 ksoftirqd/3
S     0    20     2     99   - 00:00:00 watchdog/3
S     0    21     2      - -20 00:00:00 cpuset
S     0    22     2      - -20 00:00:00 khelper

2.2 关于时间片

有了优先级,可以决定谁先运行了。但是对于调度程序来说,并不是运行一次就结束了,还必须知道间隔多久进行下次调度。

于是就有了时间片的概念。时间片是一个数值,表示一个进程被抢占前能持续运行的时间。

也可以认为是进程在下次调度发生前运行的时间(除非进程主动放弃CPU,或者有实时进程来抢占CPU)。

时间片的大小设置并不简单,设大了,系统响应变慢(调度周期长);设小了,进程频繁切换带来的处理器消耗。默认的时间片一般是10ms

2.3 调度实现原理(基于优先级和时间片)

下面举个直观的例子来说明:

假设系统中只有3个进程ProcessA(NI=+10),ProcessB(NI=0),ProcessC(NI=-10),NI表示进程的nice值,时间片=10ms

1) 调度前,把进程优先级按一定的权重映射成时间片(这里假设优先级高一级相当于多5msCPU时间)。

假设ProcessA分配了一个时间片10ms,那么ProcessB的优先级比ProcessA高10(nice值越小优先级越高),ProcessB应该分配10*5+10=60ms,以此类推,ProcessC分配20*5+10=110ms

2) 开始调度时,优先调度分配CPU时间多的进程。由于ProcessA(10ms),ProcessB(60ms),ProcessC(110ms)。显然先调度ProcessC

3) 10ms(一个时间片)后,再次调度时,ProcessA(10ms),ProcessB(60ms),ProcessC(100ms)。ProcessC刚运行了10ms,所以变成100ms。此时仍然先调度ProcessC

4) 再调度4次后(4个时间片),ProcessA(10ms),ProcessB(60ms),ProcessC(60ms)。此时ProcessB和ProcessC的CPU时间一样,这时得看ProcessB和ProcessC谁在CPU运行队列的前面,假设ProcessB在前面,则调度ProcessB

5) 10ms(一个时间片)后,ProcessA(10ms),ProcessB(50ms),ProcessC(60ms)。再次调度ProcessC

6) ProcessB和ProcessC交替运行,直至ProcessA(10ms),ProcessB(10ms),ProcessC(10ms)。

这时得看ProcessA,ProcessB,ProcessC谁在CPU运行队列的前面就先调度谁。这里假设调度ProcessA

7) 10ms(一个时间片)后,ProcessA(时间片用完后退出),ProcessB(10ms),ProcessC(10ms)。

8) 再过2个时间片,ProcessB和ProcessC也运行完退出。

这个例子很简单,主要是为了说明调度的原理,实际的调度算法虽然不会这么简单,但是基本的实现原理也是类似的:

1)确定每个进程能占用多少CPU时间(这里确定CPU时间的算法有很多,根据不同的需求会不一样)

2)占用CPU时间多的先运行

3)运行完后,扣除运行进程的CPU时间,再回到 1)

3. Linux上调度实现的方法

Linux上的调度算法是不断发展的,在2.6.23内核以后,采用了“完全公平调度算法”,简称CFS。

CFS算法在分配每个进程的CPU时间时,不是分配给它们一个绝对的CPU时间,而是根据进程的优先级分配给它们一个占用CPU时间的百分比。

比如ProcessA(NI=1),ProcessB(NI=3),ProcessC(NI=6),在CFS算法中,分别占用CPU的百分比为:ProcessA(10%),ProcessB(30%),ProcessC(60%)

因为总共是100%,ProcessB的优先级是ProcessA的3倍,ProcessC的优先级是ProcessA的6倍。

Linux上的CFS算法主要有以下步骤:(还是以ProcessA(10%),ProcessB(30%),ProcessC(60%)为例)

1)计算每个进程的vruntime(注1),通过update_curr()函数更新进程的vruntime。

2)选择具有最小vruntime的进程投入运行。(注2)

3)进程运行完后,更新进程的vruntime,转入步骤2) (注3)

注1. 这里的vruntime是进程虚拟运行的时间的总和。vruntime定义在:kernel/sched_fair.c 文件的 struct sched_entity 中。

注2. 这里有点不好理解,根据vruntime来选择要运行的进程,似乎和每个进程所占的CPU时间百分比没有关系了。

1)比如先运行ProcessC,(vr是vruntime的缩写),则10ms后:ProcessA(vr=0),ProcessB(vr=0),ProcessC(vr=10)

2)那么下次调度只能运行ProcessA或者ProcessB。(因为会选择具有最小vruntime的进程)

长时间来看的话,ProcessA、ProcessB、ProcessC是公平的交替运行的,和优先级没有关系。

而实际上vruntime并不是实际的运行时间,它是实际运行时间进行加权运算后的结果。

比如上面3个进程中ProcessA(10%)只分配了CPU总的处理时间的10%,那么ProcessA运行10ms的话,它的vruntime会增加100ms。

以此类推,ProcessB运行10ms的话,它的vruntime会增加(100/3)ms,ProcessC运行10ms的话,它的vruntime会增加(100/6)ms。

实际的运行时,由于ProcessC的vruntime增加的最慢,所以它会获得最多的CPU处理时间。

上面的加权算法是我自己为了理解方便简化的,Linux对vruntime的加权方法还得去看源码^-^

注3.Linux为了能快速的找到具有最小vruntime,将所有的进程的存储在一个红黑树中。这样树的最左边的叶子节点就是具有最小vruntime的进程,新的进程加入或有旧的进程退出时都会更新这棵树。

其实Linux上的调度器是以模块方式提供的,每个调度器有不同的优先级,所以可以同时存在多种调度算法。

每个进程可以选择自己的调度器,Linux调度时,首先按调度器的优先级选择一个调度器,再选择这个调度器下的进程。

4. 调度相关的系统调用

调度相关的系统调用主要有2类:

1) 与调度策略和进程优先级相关 (就是上面的提到的各种参数,优先级,时间片等等) - 下表中的前8个

2) 与处理器相关 - 下表中的最后3个


系统调用


描述


nice()


设置进程的nice值


sched_setscheduler()


设置进程的调度策略,即设置进程采取何种调度算法


sched_getscheduler()


获取进程的调度算法


sched_setparam()


设置进程的实时优先级


sched_getparam()


获取进程的实时优先级


sched_get_priority_max()


获取实时优先级的最大值,由于用户权限的问题,非root用户并不能设置实时优先级为99


sched_get_priority_min()


获取实时优先级的最小值,理由与上面类似


sched_rr_get_interval()


获取进程的时间片


sched_setaffinity()


设置进程的处理亲和力,其实就是保存在task_struct中的cpu_allowed这个掩码标志。该掩码的每一位对应一个系统中可用的处理器,默认所有位都被设置,即该进程可以再系统中所有处理器上执行。

用户可以通过此函数设置不同的掩码,使得进程只能在系统中某一个或某几个处理器上运行。


sched_getaffinity()


获取进程的处理亲和力


sched_yield()


暂时让出处理器

时间: 2024-08-01 21:28:48

《Linux内核设计与实现》读书笔记(四)- 进程的调度的相关文章

Linux内核设计与实现 读书笔记 转

Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://blog.csdn.net/yrj/article/category/718110 Linux内存管理和性能学习笔记(一) :内存测量与堆内存 第一篇 内存的测量 2.1. 系统当前可用内存 # cat /proc/meminfoMemTotal:        8063544 kBMemFree:       

Linux内核设计与实现读书笔记——第三章

Linux内核设计与实现读书笔记——第三章 进程管理 20135111李光豫 3.1进程 1.进程即处于执行期的程序,并不局限于一个可执行的代码,是处于执行期程序以及其相关资源的总称. 2.Linux系统中,对于进程和线程并没有明显的区分,线程是一种特殊的进程. 3.Linux系统中,常用fork()进程创建子进程.调用fork()进程的成之为其子进程的父进程. 4.fork()继承实际上由clone()系统调用实现.最后通过exit()退出执行. 3.2任务描述符及任务结构 1.任务队列实质上

Linux内核设计与实现 读书笔记

第三章 进程管理 1. fork系统调用从内核返回两次: 一次返回到子进程,一次返回到父进程 2. task_struct结构是用slab分配器分配的,2.6以前的是放在内核栈的栈底的:所有进程的task_struct连在一起组成了一个双向链表 3. 2.6内核的内核栈底放的是thread_info结构,其中有指向task_struct的指针: 4. current宏可以找到当前进程的task_struct:X86是通过先找到thread_info结构,而PPC是有专门的寄存器存当前task_s

Linux内核设计与实现读书笔记——第十八章

第18章 调试 调试工作艰难是内核级开发区别于用户级开发的一个显著特点,相比于用户级开发,内核调试的难度确实要艰苦得多.更可怕的是,它带来的风险比用户级别更高,内核的一个错误往往立刻就能让系统崩溃. 18.1 准备开始 一个bug.听起来很可笑,但确实需要一个确定的bug.如果错误总是能够重现的话,那对我们会有很大的帮助(有一部分错误确实如此).然而不幸的是,大部分bug通常都不是行为可靠而且定义明确的. 一个藏匿bug的内核版本.如果你知道这个bug最早出现在哪个内核版本中那就再理想不过了.

《Linux内核设计与实现读书笔记之系统调用》

1.系统调用的概念 为了和用户空间上运行的进程进行交互,内核提供了一组借口.透过该接口,应用程序可以访问硬件设备和其他操作系统资源.这组借口在应用程序和内核之间扮演着使者的角色.同时,这组接口也保证了系统稳定可靠,避免应用程序肆意妄行,惹出麻烦.Linux系统的系统调用作为C库的一部分提供,其调用过程中的实例如下图所示: 从程序员的角度看,系统调用无关紧要,他们只需要跟API打交道就可以了.相反,内核只跟系统调用打交道,库函数以及应用程序是怎么使用系统调用不是内核所关心的. 2.系统调用的处理程

Linux内核设计与实现——读书笔记2:进程管理

1.进程: (1)处于执行期的程序,但不止是代码,还包括各种程序运行时所需的资源,实际上进程是正在执行的 程序的实时结果. (2)程序的本身并不是进程,进程是处于执行期的程序及其相关资源的总称. (3)两个或两个以上并存的进程可以共享诸如打开的文件,地址空间等共享资源. (4)在Linux中通常是调用fork()系统函数的结果,通过复制一个现有的进程来创建一个新的子进程. fork()系统函数 (5)fork在这个系统调用结束时,在同一位置上返回两次(从内核返回两次),父进程恢复运行,子进程开始

《Linux内核设计与实现》笔记-1-linux内核简介

一.Linux内核相对于传统的UNIX内核的比较: (1):Linux支持动态内核模块.尽管Linux内核也是整体式结构,可是允许在需要的时候动态哦卸除(rmmod xxx)和加载内核模块(insmod  xxx.ko). (2):Linux支持对称多处理(SMP)机制,尽管许多UNIX的变体也支持SMP,但是传统的UNIX并不支持这种机制. (3):Linux内核可以抢占(preemptive).在Linux 2.4以及以前的版本都是不支持内核抢占的,在Linux 2.6以及以后就支持了. (

《Linux内核设计与实现》——第四章 进程调度

<Linux内核设计与实现>——第四章 进程调度 4.1多任务 多任务操作系统:同时并发地交互执行多个进程的操作系统 多任务操作系统会使多个进程处于堵塞或者睡眠状态.这些任务尽管位于内存,但是并不处于可运行状态.这些进程利用内核堵塞自己,直到某一事件发生. 多任务系统可以划分为两类:非抢占式和抢占式. 抢占:强制挂起. 时间片:分配给每个可运行进程的处理器时间段. 4.3策略 策略决定调度程序在何时让什么程序运行. 4.3.1   I/O消耗型和处理器消耗型的进程 I/O消耗型:进程的大部分时

《Linux内核设计与实现》笔记——内核同步简介

相关概念 竞争条件 多个执行线程(进程/线程/中断处理程序)并发(并行)访问共享资源,因为执行顺序不一样造成结果不一样的情况,称为竞争条件(race condition) 举例说明 #include<thread> using namespace std; int i = 0; void thread1(){ //for(int x=0;x<100000;x++) i++; } void thread2(){ //for(int x=0;x<100000;x++) i++; } i

《Linux内核设计与实现》第四章读书笔记

第四章 进程调度 第4章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有通过调度程序的合理调度,系统资源才能最大限度地发挥作用,多进程才会有并发行的效果. 调度程序没有太复杂的原理,最大限度地利用处理器时间的原则是只要有可以执行的进程,那么就总会有进程正在执行,但是只要系统中可运行的进程的数目比处理器的个数多,就注定某一给定时刻会有一些进程不能执行,这些进程在等待运行,在一组处于可运行状态的进