CPU调度子系统
作者:互联网
# CPU调度子系统
- [CPU调度子系统](#cpu调度子系统)
- [CPU调度系统](#cpu调度系统)
- [调度系统的机制](#调度系统的机制)
- [调度系统的策略](#调度系统的策略)
- [参考链接](#参考链接)
研究调度系统,主要想搞明白两个问题:CFS的调度是如何防御恶意进程的;补充进程调度的相关知识,进而为搞明白编译选项做基础。
## CPU调度系统
1. 调度系统的诞生
- 调度是为了给用户一个程序并行执行的错觉,让用户的使用更加友好
- 随着用户的需求的逐渐演变,我们有了不同调度系统
2. 调度系统的两个要求(评价指标)
- 不同的任务不能互相干扰
- 这个是调度的基本要求,也是必须要满足的,被称为上下文切换,很考验的是内存管理部分的内容
- 需要满足不同的调度效率和响应时间的需求,在考虑效率和时间的同时,还需要考虑调度的公平性
- 调度是调度效率和响应时间的一个权衡,公平性是在地位相同的进程间考虑的,效率和时间是调度的主要评价指标
- 如果想要调度效率最高,那么就是批处理
- 这个的使用场景大多是超算一类的机器,对机器的效率有很高的要求
- 如果想要提升响应时间但是还想要尽可能的保证效率,那么就是我们目前经常用的操作系统
- 保证响应时间是为了让用户得到更好的使用体验
- 保证效率是为了让服务器性能得到更好的发挥
- 如果想要以响应时间为最重要的指标,那么就是实时操作系统,需要通过抢占的方式来提升实时性,保证任务及时完成
- 硬实时操作系统可以保证任务在规定的时间内完成
- 软实时操作系统只要按照任务的优先级完成就可以,不保证任务完成的时间
- 使用的场景大多是工业机器
3. 调度系统的组成部分
- 为了完成第一个要求,有一个机制来管理进程,并进行上下文切换
- 机制是为了让进程可以完成上下文切换,将CPU虚拟化
- 我们这个系列主要是完成策略
- 为了完成第二个调度的要求,需要有一个策略模块
- 策略是调度系统的灵魂
## 调度系统的机制
1. 进程的分类
- 根据进程的不同的要求,我们将其分为不同的种类,进而在后面的调度策略时,尽量满足不同进程的(这里和课本上讲的泾渭分明的调度策略不同,我们可以将不同的要求在同一个电脑上尽可能满足)
- 硬实时进程
- 需要在一定时间内完成的进程
- 软实时进程
- 需要快速得到结果的进程
- 普通进程
- 普通进程也有一定的优先级,比如在PC上,用来给用户以反馈的进程优先级高,计算繁重的进程优先级较低
2. 进程的生命周期
- 进程的状态
- 运行
- 正在CPU上跑
- 等待
- 在队列里面等着放在CPU上跑
- 睡眠
- 等待外部的事件完成,进而放在等待队列,能CPU上跑
- 终止
- 进程结束,资源释放(如果资源已经释放,但是进程任然在进程表中,我们称之为僵尸进程)
- 进程状态之间的转换
![状态转换](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210814195728.png)
- 进程的权限
- 我们经常说操作系统处于用户态和核心态,就是指在当前运行的进程时在用户态还是核心态,这两者代表了不同的权限
- 用户态
- 此时进程只能访问自身的数据,无法干扰系统中的其他应用程序,甚至也不会注意到自身之外其他程序的存在
- 核心态
- 如果进程想要访问系统数据或功能(后者管理着所有进程之间共享的资源,例如文件系统空间),则需要在核心态
- 从用户态到核心态的切换只能通过两个方式
- 系统调用
- 中断
- 当中断来临时自动切换
- 中断可以是内核在运行过程中产生的,也可以是外部产生的,注意这里中断和异常在含义上不做区分
- 中断根据中断的来源分为硬中断(外部)和软中断(内部)
- 硬中断是电路产生的
- 软中断是执行程序产生的
3. 进程的表示和机制实现
- 表示(数据结构)
- 数据结构task_stuct,这个结构的[代码](https://elixir.bootlin.com/linux/v5.10/source/include/linux/sched.h#L640)直到1366行
- 这个数据结构包含的内容及其多,我们只能从一些主要的方面入手解读这个结构
- 进程状态
- 执行信息
- 进程身份
- 资源情况
- 机制
- fork
- 生成一个子进程
- exec
- 加载另一个进程,来替代当前的进程,一般是和fork配合,先fork,再在子进程里面exec
- 上下文切换
## 调度系统的策略
1. 调度器基础
- 在这个部分,我主要参考了[1-4]
- 结构
![调度器结构](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210814210740.png)
- 主调度器和周期性调度器合称为通用调度器或者核心调度器,它是一个分配器,通过和调度器类交互选择进程,和调度机制交互完成上下文切换
- 主调度器和周期性调度器都会和各个调度器类,调度机制交互
- 调度器调度时机
- 进程因为睡眠或者IO等原因主动放弃CPU。主调度器就是在这个时机来介入
- 通过周期性机制,判断是否有必要选择进程。周期性调度器周期性运作,来保证系统不会被某个程序一直占用
- 也就是主调度器或者周期性调度器适时介入,然后再通过调度类判断下一个执行的进程,最后通过调度器机制,完成上下文切换等一系列调度工作
- 主调度器
- 相关函数
- schedule
- 主调度器的任务
- 在内核中的许多地方,如果要将CPU分配给与当前活动进程不同的另一个进程,都会直接调用主调度器函数(schedule)。
- 在从系统调用返回之后,内核也会检查当前进程是否设置了重调度标志TIF_NEED_RESCHED
- 调度流程
![调度流程](https://raw.githubusercontent.com/Richardhongyu/pic/main/640)
- 相当多的工作是交给调度器类完成的
- 周期性调度器
- 相关函数
- scheduler_tick
- 周期性调度器的任务
- 管理内核中与整个系统和各个进程的调度相关的统计量。其间执行的主要操作是对各种计数器加1
- 激活负责当前进程的调度类的周期性调度方法。
- 调度流程
![调度流程](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210815122254.png)
- 通过函数指针,调用调度器类里面实现的task_tick,让调度器类实现相应的工作
- 如果当前的进程需要被调度,那么调度器类方法会在task_struct中设置TIF_NEED_RESCHED标志,以表示该请求,而内核会在接下来的适当时机完成该请求,本质还是传递给主调度器完成。
- 调度器类
![调度器类](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210814211853.png)
- 调度器类结构
```shell
struct sched_class {
const struct sched_class *next;
void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep);
void (*yield_task) (struct rq *rq);
void (*check_preempt_curr) (struct rq *rq, struct task_struct *p);
struct task_struct * (*pick_next_task) (struct rq *rq);
void (*put_prev_task) (struct rq *rq, struct task_struct *p);
void (*set_curr_task) (struct rq *rq);
void (*task_tick) (struct rq *rq, struct task_struct *p);
void (*task_new) (struct rq *rq, struct task_struct *p);
};
```
- 调度器类提供了通用调度器和调度策略之间的关系
- 实时进程调度器类在最前面,然后是普通进程调度器类,最后是空闲进程调度器类
- 调度器类在被通用调度器中的周期性调度器调用时,task_tick是其执行的函数
- 这里充分体现了包括了函数指针的妙用
- 进程调度策略
- 我们给每个进程一个调度策略,表明我们希望如何调度这个进程,以在一个系统内,满足不同的要求
![调度策略](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210814212256.png)
- SCHED_NORMAL调度普通进程,这也是我们日常使用最多的进程
- SCHED_BATCH调度批处理进程
- SCHED_FIFO和SCHED_RR和SCHED_DEADLINE则采用不同的调度策略调度实时进程,
- SCHED_IDLE则在系统空闲时调用idle进程
- 不同的调度进程
- 调度实体
- 调度器不限于调度进程,还可以调度更大的实体。CPU时间一般用于组调度,首先在进程组之间分配,然在组内分配。
```shell
struct sched_entity {
struct load_weight load; /* 用于负载均衡 */
struct rb_node run_node;
unsigned int on_rq;
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
...
}
```
- 调度优先级
- 优先级的计算
- 虚拟时间
- 优先级的实现
- 主调度器
- 周期性调度器
2. CFS调度类
- 这一小节的内容主要参考了[1-3],由于之后并不会用到这里的内容,这里用[3]中的几个流程图来描述这一小节的内容
- 首先来看调度类是如何实现的
![调度类](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210815115808.png)
- 然后看解决出队和入队的流程
![出入队](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210815120144.png)
- 接着看如何挑选下一个进程
![挑选进程](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210815120558.png)
- 任务创建
![任务创建](https://raw.githubusercontent.com/Richardhongyu/pic/main/20210815120620.png)
3. 实时调度类
- 和优化CPU统计时间的主题关系不是太大,这里先略过
## 参考链接
[1] https://blog.csdn.net/gatieme/article/details/51699889
[2] 《深入理解Linux架构》
[3] https://mp.weixin.qq.com/s/6as3ZB5s-H9jKtxAorGIfg
[4] https://mp.weixin.qq.com/s/BOmuGLb1iQ57lO_nnAn2xA
标签:task,rq,调度,子系统,https,进程,CPU,struct 来源: https://blog.csdn.net/weixin_41070748/article/details/120142080