进程调度是一个内核子系统,负责决定将哪个进程投入运行,何时运行以及运行多长的时间。它是多任务操作系统的基础。
调度相关概念
多任务操作系统
-
在单处理器机器上,会产生多个进程同时在运行的幻觉
-
在多处理器机器上,会使多个进程在不同的处理器上真正同时,并行的执行。
I/O消耗性与处理器消耗性
进程可以被分为:
- I/O消耗型:进程大部分时间用来提交I/O请求或者是等待I/O请求,这种进程运行时间短,经常处于阻塞状态,不需要太长的时间片(比如文字编辑程序)
- 处理器消耗型:进程大部分时间用在执行代码上,除非被强占,否则他们一直都在不停的运行,因为它们没有太多I/O需求,希望时间片越长越好(比如视频编码程序)
Linux倾向于优先调度I/O消耗性
优先级
nice值:值越低,优先级越高
实时优先级:值越高,优先级越高
处理器时间使用比
linux的CFS调度器没有直接分配时间片到进程,它是将处理器的使用比划分给了进程。进程的nice值越小,分到的处理器时间使用比例越大。
CFS调度器和一般操作系统用的时间片+优先级的方式不同,进程的抢占时机取决于新的可以运行进程消耗了多少处理器使用比,如果小号的使用比比当前进程小,则新进程立刻投入运行,抢占当前线程,否则,将推迟其运行。
还是以文字编辑程序和视频编码程序为例子,假设他们有同样的nice值和处理器使用比50%,文字编辑程序由于总是在等用户输入,所以很难消耗到50%,而视频编码程序很快就可以消耗50%,所以当文字编辑程序ready的时候,文字编辑程序就可以立马被调度了。
Linux调度算法
CFS调度算法
nice值在CFS中被作为进程获得处理器运行比的权重,这种算法使得任何进程所活动的处理器时间是由它自己和其他所有可运行进程nice值的相对差值决定的。
CFS调度算法的实现
时间记账
利用调度器实体结构中的进程虚拟运行时间 vruntime ,记录一个程序到底运行了多长时间以及它还应该再运行多久。
当CFS需要选择下一个进程时,会选择一个具有最小 vruntime 的进程
进程选择
CFS使用红黑树来组织可运行进程队列。所以说这个进程队列的操作其实就是直接利用红黑树的插入,删除,查找。真是方便啊。
红黑树节点的键值是可运行进程的vruntime,那么树种最左侧的叶子节点就是vruntime值最小的那个进程。(红黑树相关知识)
调度器
进程调度的主要入口函数是schedule(),
这个函数找到一个最高优先级的调度器类,然后从这个类的可运行队列里面找出下一个该运行的进程。找的方法就是前面进程选择中提到的方法(利用红黑树)。
休眠和唤醒
等待队列是由等待某些事件发生的进程组成的简单链表,(不像可运行进程那样是用红黑树组织的)
- 休眠
进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。 - 唤醒
进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中。
抢占
内核提供了一个 need_resched 标志来表明是否需要重新执行一次调度(就是调用一次schedule()函数)。
某个进程应该被抢占时,某个高优先级的进行进入可执行态时,都会有相应的函数去设置这个need_resched标志。
-
用户抢占
发生在
从系统调用返回用户空间时;
从中断处理程序返回用户空间时;
这种时候都会去检查need_resched标志。来判断是不是要发生抢占。 -
内核抢占
发生在
中断处理程序正在执行,且返回内核空间之前
内核代码再一次具有可抢占性的时候(没有持有锁)
内核中的任务显示调用schedule()
内核任务阻塞
上下文切换
从一个可执行进程切换到另一个可执行进程
schedule()调用
switch_mm(): 把虚拟内存从上一个进程映射切换到新进程中。
switch_to():负责切换处理器状态(栈,寄存器之类的)。
与调度相关的系统调用
- 处理器绑定 sched_setaffinity()
- 优先级设置 sched_setscheduler()
等等。。懒得写了