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数值表示进程优先级,数值越小,优先级越高。优先级0
99给实时进程使用,100
139给普通进程使用。另外,在用户空间中有一个传统的变量nice,用于映射普通进程的优先级,即100~139
linux按照优先级分为两类进程
-
实时进程:
要求最快被响应,比如视频、工业机器控制程序,
其优先级为0 ~ 99
-
非实时进程
:即普通进程,我们大部分程序使用的,
其优先级为100 ~ 139
其还可以细分为两类- 交互式进程,需要响应前台请求
- 后台批处理进程
linux内核中task_struct数据结构中使用了4个成员描述进程的优先级
字段 | 含义 |
---|---|
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的进程替换下来,根据进程生命周期的图示,
内核运行调度程序的条件为
- 进程从运行状态切换到就绪状态或者等待状态
- 进程被终结了,就需要从就绪队列中选择下一个进程进行运行
同时哪些场景不能发生调度呢?
-
在中断处理过程中:
中断处理过程,与硬件密切相关,很难做到中断处理过程进行进程切换 -
在内核程序临界区:
-
在原子操作过程中:
非抢占内核和抢占内核
在不支持内核抢占的系统中,进程/线程一旦运行于内核空间,就可以一直运行,直到他主动放弃或者耗尽时间片为止,这样就会导致非常紧急的进程或线程长时间得不到运行。
例如,当外部来了一个中断,中断程序程序处理完后,需要一个用户程序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 SCHED_IDLE |
普通进程,优先级为100 ~ 139 | 由CFS来调度 |
idle | 无 | 最低优先级的进程 | 当就绪队列中没有其他的进程时,进入idle调度类,idle调度器会让CPU进入低功耗模式 |
用户空间程序 可以使用调度策略接口函数(sched_setscheduler())来设定用户进程的调度策略,linux内核对于调度策略定义如下:
4. 总结