进程调度之进程上下文切换详解...

This commit is contained in:
gatieme
2016-06-26 19:20:18 +08:00
parent 69c8e5539e
commit e6f778bc78
22 changed files with 184 additions and 0 deletions

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

@@ -868,3 +868,7 @@ static struct rq *finish_task_switch(struct task_struct *prev)
```
#4 linux下进程切换性能分析
-------
@@ -0,0 +1,136 @@
Linux进程调度器的设计
=======
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
| 2016-06-14 | [Linux-4.6](http://lxr.free-electrons.com/source/?v=4.6) | X86 & arm | [gatieme](http://blog.csdn.net/gatieme) | [LinuxDeviceDrivers](https://github.com/gatieme/LDD-LinuxDeviceDrivers) | [Linux进程管理与调度](http://blog.csdn.net/gatieme/article/category/6225543) |
#前景回顾
-------
##进程调度
-------
linux把进程区分为实时进程和非实时进程, 其中非实时进程进一步划分为交互式进程和批处理进程
根据进程的不同分类Linux采用不同的调度策略.
对于实时进程,采用FIFO或者Round Robin的调度策略.
对于普通进程,则采用CFS完全功能公平调度器
##linux调度器的演变
-------
一开始的调度器是复杂度为**$O(n)$的始调度算法**(实际上每次会遍历所有任务,所以复杂度为O(n)), 这个算法的缺点是当内核中有很多任务时,调度器本身就会耗费不少时间,所以,从linux2.5开始引入赫赫有名的**$O(1)$调度器**
| 字段 | 版本 |
| ------------- |:-------------:|
| O(n)的始调度算法 | linux-0.11~2.4 |
| O(1)调度器 | linux-2.5 |
| CFS调度器 | linux-2.6~至今 |
#Linux的调度器组成
-------
##2个调度器
-------
可以用两种方法来激活调度
* 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU
* 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要
因此当前linux的调度程序由两个调度器组成:**主调度器**,**周期性调度器**(两者又统称为**通用调度器(generic scheduler)**或**核心调度器(core scheduler)**)
并且每个调度器包括两个内容:**调度框架**(其实质就是两个函数框架)及**调度器类**
##6种调度策略
-------
linux内核目前实现了6中调度策略(即调度算法), 用于对不同类型的进程进行调度, 或者支持某些特殊的功能
比如SCHED_NORMAL和SCHED_BATCH调度普通的非实时进程, SCHED_FIFO和SCHED_RR和SCHED_DEADLINE则采用不同的调度策略调度实时进程, SCHED_IDLE则在系统空闲时调用idle进程.
##5个调度器类
-------
而依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.
其所属进程的优先级顺序为
````c
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
```
##3个调度实体
-------
调度器不限于调度进程, 还可以调度更大的实体, 比如实现组调度: 可用的CPUI时间首先在一半的进程组(比如, 所有进程按照所有者分组)之间分配, 接下来分配的时间再在组内进行二次分配.
这种一般性要求调度器不直接操作进程, 而是处理可调度实体, 因此需要一个通用的数据结构描述这个调度实体,即seched_entity结构, 其实际上就代表了一个调度对象,可以为一个进程,也可以为一个进程组.
linux中针对当前可调度的实时和非实时进程, 定义了类型为seched_entity的3个调度实体
* sched_dl_entity 采用EDF算法调度的DEADLINE实时调度实体
* sched_rt_entity 采用Roound-Robin或者FIFO算法调度的实时调度实体
* sched_entity 采用CFS算法调度的普通非实时进程的调度实体
每个进程都属于某个调度器类(由字段task_struct->sched_class标识), 由调度器类采用进程对应的调度策略调度(由task_struct->policy )进行调度, task_struct也存储了其对应的调度实体标识
linux实现了6种调度策略, 依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.
调度器类, 调度策略, 调度实体他们之间的关系如下表
| 调度器类 | 调度策略 | 调度策略对应的调度算法 | 调度实体 | 调度实体对应的调度对象 |
| ------- |:-------:|:-------:|:-------:||:-------:|
| stop_sched_class | 无 | 无 | 无 | 特殊情况, 发生在cpu_stop_cpu_callback 进行cpu之间任务迁移migration或者HOTPLUG_CPU的情况下关闭任务 |
| dl_sched_class | SCHED_DEADLINE | Earliest-Deadline-First最早截至时间有限算法 | sched_dl_entity | 采用DEF最早截至时间有限算法调度实时进程 |
| rt_sched_class | SCHED_RR<br><br>SCHED_FIFO | Roound-Robin时间片轮转算法<br><br>FIFO先进先出算法 | sched_rt_entity | 采用Roound-Robin或者FIFO算法调度的实时调度实体 |
| fair_sched_class | SCHED_NORMAL<br><br>SCHED_BATCH | CFS完全公平懂调度算法 |sched_entity | 采用CFS算法普通非实时进程 |
| idle_sched_class | SCHED_IDLE | 无 | 无 | 特殊进程, 用于cpu空闲时调度空闲进程idle |
#cfs完全公平调度器
-------
##CFS调度器类fair_sched_class
-------
CFS完全公平调度器的调度器类叫fair_sched_class, 其定义在[kernel/sched/fair.c, line 8521](http://lxr.free-electrons.com/source/kernel/sched/fair.c#L8521), 它是我们熟知的是struct sched_class调度器类类型, 将我们的CFS调度器与一些特定的函数关联起来
| sched_class字段 | fair_sched_class函数| 描述 |
| ------------- |:-------------:|
| load | 指定了权重, 决定了各个实体占队列总负荷的比重, 计算负荷权重是调度器的一项重任, 因为CFS所需的虚拟时钟的速度最终依赖于负荷, 权重通过优先级转换而成,是vruntime计算的关键 |
| run_node | 调度实体在红黑树对应的结点信息, 使得调度实体可以在红黑树上排序 |
| sum_exec_runtime | 记录程序运行所消耗的CPU时间, 以用于完全公平调度器CFS |
| on_rq | 调度实体是否在就绪队列上接受检查, 表明是否处于CFS红黑树运行队列中,需要明确一个观点就是,CFS运行队列里面包含有一个红黑树,但这个红黑树并不是CFS运行队列的全部,因为红黑树仅仅是用于选择出下一个调度程序的算法。很简单的一个例子,普通程序运行时,其并不在红黑树中,但是还是处于CFS运行队列中,其on_rq为真。只有准备退出、即将睡眠等待和转为实时进程的进程其CFS运行队列的on_rq为假 |
| vruntime | 虚拟运行时间,调度的关键,其计算公式:一次调度间隔的虚拟运行时间 = 实际运行时间 * (NICE_0_LOAD / 权重)。可以看出跟实际运行时间和权重有关,红黑树就是以此作为排序的标准,优先级越高的进程在运行时其vruntime增长的越慢,其可运行时间相对就长,而且也越有可能处于红黑树的最左结点,调度器每次都选择最左边的结点为下一个调度进程。注意其值为单调递增,在每个调度器的时钟中断时当前进程的虚拟运行时间都会累加。单纯的说就是进程们都在比谁的vruntime最小,最小的将被调度 |
| cfs_rq | 此调度实体所处于的CFS运行队列 |
| my_q | 如果此调度实体代表的是一个进程组,那么此调度实体就包含有一个自己的CFS运行队列,其CFS运行队列中存放的是此进程组中的进程,这些进程就不会在其他CFS运行队列的红黑树中被包含(包括顶层红黑树也不会包含他们,他们只属于这个进程组的红黑树) |
@@ -0,0 +1,43 @@
8518 /*
8519 * All the scheduling class methods:
8520 */
8521 const struct sched_class fair_sched_class = {
8522 .next = &idle_sched_class,
8523 .enqueue_task = enqueue_task_fair,
8524 .dequeue_task = dequeue_task_fair,
8525 .yield_task = yield_task_fair,
8526 .yield_to_task = yield_to_task_fair,
8527
8528 .check_preempt_curr = check_preempt_wakeup,
8529
8530 .pick_next_task = pick_next_task_fair,
8531 .put_prev_task = put_prev_task_fair,
8532
8533 #ifdef CONFIG_SMP
8534 .select_task_rq = select_task_rq_fair,
8535 .migrate_task_rq = migrate_task_rq_fair,
8536
8537 .rq_online = rq_online_fair,
8538 .rq_offline = rq_offline_fair,
8539
8540 .task_waking = task_waking_fair,
8541 .task_dead = task_dead_fair,
8542 .set_cpus_allowed = set_cpus_allowed_common,
8543 #endif
8544
8545 .set_curr_task = set_curr_task_fair,
8546 .task_tick = task_tick_fair,
8547 .task_fork = task_fork_fair,
8548
8549 .prio_changed = prio_changed_fair,
8550 .switched_from = switched_from_fair,
8551 .switched_to = switched_to_fair,
8552
8553 .get_rr_interval = get_rr_interval_fair,
8554
8555 .update_curr = update_curr_fair,
8556
8557 #ifdef CONFIG_FAIR_GROUP_SCHED
8558 .task_move_group = task_move_group_fair,
8559 #endif
8560 };
@@ -14,6 +14,7 @@ Linux进程退出详解
6. 优先级priority
7. 抢占-内核抢占和用户抢占以及抢占的时机preempt
8. 进程切换context_switch
9. 完全公平调度器cfs-设计
10. cfs-负荷权重
11. cfs-虚拟时钟