一、进程的分类
在CPU的角度看进程行为的话,可以分为两类:
- CPU消耗型:此类进程就是一直占用CPU计算,CPU利用率很高。
- IO消耗型:此类进程会涉及到IO,需要和用户交互,比如键盘输入,占用CPU不是很高,只需要CPU的一部分计算,大多数时间是在等待IO。
CPU消耗型进程需要高的吞吐率,IO消耗型进程需要强的响应性,这两点都是调度器需要考虑的。
为了更快响应IO消耗型进程,内核提供了一个抢占(preempt)机制,使优先级更高的进程,去抢占优先级低的进程运行。内核用以下宏来选择内核是否打开抢占机制:
- CONFIG_PREEMPT_NONE:不打开抢占,主要是面向服务器。此配置下,CPU在计算时,当输入键盘之后,因为没有抢占,可能需要一段时间等待键盘输入的进程才会被CPU调度。
- CONFIG_PREEMPT:打开抢占,一般多用于手机设备。在配置下,虽然会影响吞吐率,但可以及时响应用户的输入操作。
二、调度相关的数据结构
1.task_struct
task_struct中和调度相关的结构。
struct task_struct {
......
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
......
struct sched_dl_entity dl;
......
unsigned int policy;
......
}
struct sched_class:对调度器进行抽象,一共分为5类。
- Stop调度器:优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占;
- Deadline调度器:使用红黑树,把进程按照绝对截止期限进行排序,选择最小进程进行调度运行;
- RT调度器:为每个优先级维护一个队列;
- CFS调度器:采用完全公平调度算法,引入虚拟运行时间概念;
- IDLE-TASK调度器:每个CPU都会一个IDLE线程,当没有其他进程可以调度是,调度运行IDLE线程。
unsigned int policy:进程的调度策略有6种,用户可以调用调度器里面的不同调度策略。
- SCHED_DEADLINE:使用task选择DEADLINE调度器来调度运行;
- SCHED_RR:时间片轮转,进程用完时间片后加入优先级对应运行队列的尾部,把CPU让给同优先级的其他进程;
- SCHED_FIFO:先进先出调度没有时间片,没有更高优先级的情况下,只能等待主动让出CPU;
- SCHED_NORMAL:使task选择CFS调度器来调度运行;
- SCHED_BATCH:批量处理,使task选择CFS调度器来调度运行;
- SCHED_IDLE:使task以最低优先级选择CFS调度器来调度运行。
- struct sched_entity se:采用CFS算法调度的普通非实时进程的调度实体。
- struct sched_rt_entity rt:采用Roound-Robin或者FIFO算法调度的实时调度实体。
- struct sched_dl_entity dl:采用EDF算法调度的实时调度实体。
分配给CPU的task,作为调度实体加入到运行队列中。
2.runqueue 运行队列
runqueue运行队列是本CPU上所有可运行进程的队列集合。每个CPU都有一个运行队列,每个运行队列中有三个调度队列,task作为调度实体加入到各自的调度队列中。
struct rq {
......
struct cfs_rq cfs;
struct rt_rq rt;
struct dl_rq dl;
......
}
三个调度队列
- struct cfs_rq cfs:CFS调度队列;
- struct rt_rq rt:RT调度队列;
- struct dl_rq dl:DL调度队列。
- cfs_rq:跟踪就绪队列信息以及管理就绪态调度实体,并维护一棵按照虚拟时间排序的红黑树。tasks_timeline->rb_root是红黑树的根,tasks_timeline->rb_leftmost指向红黑树中最左边的调度实体,即虚拟时间最小的调度实体。
struct cfs_rq {
...
struct rb_root_cached tasks_timeline
...
};
- sched_entity:可被内核调度的实体。每个就绪态的调度实体sched_entity包含插入红黑树中使用的节点rb_node,同时vruntime成员记录已经运行的虚拟时间。
struct sched_entity {
...
struct