1. linux进程分类
当涉及有关调度的问题时, 传统上把进程分类为”I/O受限(I/O-dound)”或”CPU受限(CPU-bound)”.
类型 | 别名 | 描述 | 示例 |
I/O消耗性 | I/O-Bound | 频繁进行I/O处理,并花很多时间等待I/O操作完成 | 键盘等待输入、写磁盘 |
CPU消耗性 | CPU-Bound | 花费大量时间进行数字计算,一直占用CPU | 大量数学计算 |
另外一种分法把进程区分成三类
类型 | 描述 | 示例 |
交互式进程 | 此进程经常需要与用户进行交互,因此需要花费很多时间等待键盘、鼠标、触摸屏等操作,当接受了用户的输入后,进程必须被很快唤醒,否则用户就会抱怨系统卡顿,这类进程的特点就是响应时间越短越好 | shll,文本编辑程序和图形应用程序 |
批处理进程 | 此类进程不必与用户交互,因此经常在后台运行,因为这类进程不必很快响应,因此常受调度器程序的怠慢,这类进程的特点是会占用比较多的系统资源 | 科学计算、编译代码 |
实时进程 | 这样的进程对整体时延有严格的要求,不能被低优先级的进程阻塞 | 视频播放等 |
1.2 linux优先级和分类
操作系统的经典的进程调度算法是基于优先级调度的,优先级的核心思想就是把进程按照优先级进行分类,紧急的进程优先级高,不紧急、不重要的进程优先级低。调度器总是从就绪队列中选择优先级高的进程进行调度,而且优先级高的进程分配时间片会比优先级低的进程长,这体现了一种等级制度。
所以进程优先级代表了进程需要运行的紧急程度和需要更多的运行时间片,linux操作系统最早开始采用nice值来调整进程的优先级。nice值的思想是要对其他进程友好,就需要降低优先级来支持其他进程消耗更多的处理器时间。nice值的范围是 【-20 , 19】, 默认值是0,nice值越大,优先级反而越低,nice值越小,优先级越高。nice值为-20表明这个进程非常重要,优先级最高;而nice值是19,表明允许其他进程比这个进程优先享有宝贵的CPU资源。
内核使用0-139数值表示进程优先级,数值越小,优先级越高。优先级099给实时进程使用,100139给普通进程使用。另外,在用户空间中有一个传统的变量nice,用于映射普通进程的优先级,即100~139
linux按照优先级分为两类进程
- 实时进程: 要求最快被响应,比如视频、工业机器控制程序,其优先级为0 ~ 99
- 非实时进程:即普通进程,我们大部分程序使用的,其优先级为100 ~ 139其还可以细分为两类
- 交互式进程,需要响应前台请求
- 后台批处理进程
linux内核中task_struct数据结构中使用了4个成员描述进程的优先级
struct task_struct {
int load_weight; nice权重调度
int prio, static_prio, normal_prio;
//prio是动态调度优先级,static_prio静态优先级进程启动是分配,内核不使用nice值使用静态优先级。normal_prio是基于static_prio和调度策略计算出来的优先级
unsigned int rt_priority;实时进程优先级
}
字段 | 含义 |
prio | 进程的动态优先级,唯一被调度器使用的优先级字段,其依赖于normal_prio字段,调度器在运行期间可调整改值 |
static_prio | 1. 普通进程的静态优先级,仅能通过nice值修改,取值为【100,139】,值越小优先级越高 2. 启动时分配,不会随着时间改变,用户可以通过Nice()或sched_setscheduler()等系统调用来修改 |
normal_prio | 在创建进程时,会继承父进程的normal_prio,对于普通进程来说,改值等同于static_prio,对于实时进程,会根据rt_priorty重新计算normal_prio |
rt_priority | 实时进程的优先级,取值为[0,99], |
1.3 linux权重
linux内核除了使用优先级来表示进程的轻重缓急之外,在实际调度器中还是用了权重的概念来表示进程的优先级,为了计算方便,内核约定nice值为0的进程权重为1024,其他的nice值对应的进程权重值可以通过查表的方式来获取,内核预先计算好了一个表sched_prio_to_weight,表的下标对应Nice值为[-20 ~ 19]
2. linux调度时机
对于调度器,一个很重要的时调度时机问题,在什么情况下,什么时候发生调度?也就是说在什么情况下,什么时候,把现在占用CPU的进程替换下来,根据进程生命周期的图示,
内核运行调度程序的条件为
进程从运行状态切换到就绪状态或者等待状态
进程被终结了,就需要从就绪队列中选择下一个进程进行运行
同时哪些场景不能发生调度呢?
在中断处理过程中: 中断处理过程,与硬件密切相关,很难做到中断处理过程进行进程切换
在内核程序临界区:
在原子操作过程中:
2.1 非抢占内核和抢占内核
在不支持内核抢占的系统中,进程/线程一旦运行于内核空间,就可以一直运行,直到他主动放弃或者耗尽时间片为止,这样就会导致非常紧急的进程或线程长时间得不到运行。
例如,当外部来了一个中断,中断程序程序处理完后,需要一个用户程序B对此进程进行进一步处理。而此时进程A正在使用系统调用进入内核态,那么只有等到A从系统调用返回,内核进行调度,B才有可能被运行。假设A的系统调用占用CPU的时间为T,这个T大于用户要求的响应时间,那么这个系统就不够实时
为了提高Linux的实时性。在linux2.6中引入了“Kernel preemption”(内核抢占调度模式)。并很好的解决了这个问题。一句话就是抢占式内核可以在进程处于内核态时,进行抢占。
为保证Linux内核在以上情况下不会被抢占,抢占式内核使用了一个变量preempt_count,称为内核抢占计数。这一变量被设置在进程的thread_info结构中。每当内核要进入以上几种状态时,变量preempt_count就加1,指示内核不允许抢占。每当内核从以上几种状态退出时,变量preempt_count就减1,同时进行可抢占的判断与调度。
抢占式内核什么时候能发生调度呢?
中断请求被服务历程响应完成,在返回之前被中断的进程时,可以根据需要进行调度
一系统抢占式的调度器函数:preempt_schedule;preempt_schedule_irq。它们都是调用schedule来完成调度的
同样的当时间片用完,进程处于就绪等待状态的时候,会发生调度
在支持内核抢占的系统中,某些特例下是不允许内核抢占的:
内核正进行中断处理。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。
内核正在进行中断上下文的Bottom Half(中断的底半部)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。
进程正持有spinlock自旋锁、writelock/readlock读写锁等,当持有这些锁时,不应该被抢占,否则由于抢占将导致其他CPU长期不能获得锁而死等。
内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。
3. linux调度策略
根据什么准则挑选下一个进入运行的进程。从running的进程队列中找出一个进程,来占用CPU,让它运行。
linux调度依赖于调度策略,Linux内核把相同的调度策略抽象成调度类(schedule class)。不同类型的进程采用不同的调度策略,目前Linux内核默认实现了5中调度类,分别是stop、deadline、realtime、CFS和idle,他们分别使用sched_class来实现,并且通过next指针串联在一起
调度类 | 调度策略 | 使用范围 | 说明 |
stop | 无 | 最高优先级的进程,比deadline进程优先级高 | 1. 可以抢占任何进程 2. 在每个CPU上实现一个名为"migration/N"的内核线程,说明该线程的优先级最高,可以抢占任何进程的运行,一般用来运行特殊的功能 3. 用于负载均衡机制中的进程迁移、softlockup检测、cpu热插拔、RCU等 |
deadline | SCHED_DEADLINE | 最高优先级的实时进程,优先级为-1 | 用于调度有严格要求的实时进程,如视频编解码 |
realtime | SCHED_FIFO SCHED_RR | 普通实时进程,优先级为0 ~ 99 | 用于普通的实时进程 |
CFS | SCHED_NORMAL SCHED_BATCH | 普通进程,优先级为100 ~ 139 | 由CFS来调度 |
idle | 无 | 最低优先级的进程 | 当就绪队列中没有其他的进程时,进入idle调度类,idle调度器会让CPU进入低功耗模式 |
用户空间程序 可以使用调度策略接口函数(sched_setscheduler())来设定用户进程的调度策略,linux内核对于调度策略定义如下:
4. 总结