mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-08-19 01:22:36 +08:00
...
This commit is contained in:
@@ -476,7 +476,7 @@ global queue only ever has, at most,
|
||||
|
||||
在schedule核心函数中,使用return_task来把prev进程重新入队,在earliest_deadline_task这个pick-next中,使用take_task将选中的next从队列取出,从而实现队列外执行。
|
||||
|
||||
##6 结论
|
||||
##5.10 结论
|
||||
-------
|
||||
|
||||
从上面的论述,我们丝毫没有看到有任何的诸如“SMP负载均衡”,“CPU亲和力”,“补偿”,“惩罚”之类的字眼,是的,这些字眼在BFS中完全不需要,BFS也正是摒弃了这些字眼才获得成功的,毕竟在一个一般人使用的桌面操作系统中,没有这么多的套套,大多数人使用的就是一个只有一个到两个处理器核心的系统,难道有必要搞什么调度域么?难道有必要搞什么NUMA么?需求决定一切,面对大型服务器,有UNIX的机制站在那里,而如果我们想把Linux推广到每一个掌上设备,那就没必要复制UNIX的那套了,BFS完全可以完美的搞定一切。小手段的去除,说明BFS调度器的发展方向起码是正确的。
|
||||
|
||||
@@ -135,7 +135,7 @@ linux内核实现的6种调度策略, 前面三种策略使用的是cfs调度器
|
||||
| idle_sched_class | 采用CFS算法调度idle进程, 每个cup的第一个pid=0线程:swapper,是一个静态线程。调度类属于:idel_sched_class,所以在ps里面是看不到的。一般运行在开机过程和cpu异常的时候做dump | SCHED_IDLE |
|
||||
|
||||
其所属进程的优先级顺序为
|
||||
````c
|
||||
```c
|
||||
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
|
||||
```
|
||||
|
||||
@@ -167,6 +167,7 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
|
||||
-------
|
||||
|
||||
|
||||
|
||||
本质上, 通用调度器(核心调度器)是一个分配器,与其他两个组件交互.
|
||||
|
||||
* 调度器用于判断接下来运行哪个进程.
|
||||
@@ -176,9 +177,12 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
|
||||
* 在选中将要运行的进程之后, 必须执行底层的任务切换.
|
||||
这需要与CPU的紧密交互. 每个进程刚好属于某一调度类, 各个调度类负责管理所属的进程. 通用调度器自身不涉及进程管理, 其工作都委托给调度器类.
|
||||
|
||||
|
||||
<font color=0x00ffff>
|
||||
每个进程都属于某个调度器类(由字段task_struct->sched_class标识), 由调度器类采用进程对应的调度策略调度(由task_struct->policy )进行调度, task_struct也存储了其对应的调度实体标识
|
||||
|
||||
linux实现了6种调度策略, 依据其调度策略的不同实现了5个调度器类, 一个调度器类可以用一种或者多种调度策略调度某一类进程, 也可以用于特殊情况或者调度特殊功能的进程.
|
||||
</font>
|
||||
|
||||
|
||||
| 调度器类 | 调度策略 | 调度策略对应的调度算法 | 调度实体 | 调度实体对应的调度对象 |
|
||||
@@ -1369,7 +1373,9 @@ struct task_group {
|
||||
|
||||
**通过的调度策略对象--调度类**
|
||||
|
||||
<font color=0x00ffff>
|
||||
linux下每个进程都由自身所属的调度类进行管理, sched_class结构体表示调度类, 调度类提供了通用调度器和各个调度器之间的关联, 调度器类和特定数据结构中汇集地几个函数指针表示, 全局调度器请求的各个操作都可以用一个指针表示, 这使得无需了解调度器类的内部工作原理即可创建通用调度器, 定义在[kernel/sched/sched.h](http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L1184)
|
||||
</font>
|
||||
|
||||
开发者可以根据己的设计需求,來把所属的Task配置到不同的Scheduling Class中.
|
||||
用户层应用程序无法直接与调度类交互, 他们只知道上下文定义的常量SCHED_XXX(用task_struct->policy表示), 这些常量提供了调度类之间的映射。
|
||||
@@ -1382,8 +1388,11 @@ stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle
|
||||
|
||||
**被调度的实体--进程或者进程组**
|
||||
|
||||
<font color=0x00ffff>
|
||||
linux下被调度的不只是进程, 还可以是进程组. 因此需要一种更加通用的形式组织被调度数据结构, 即调度实体, 同样不同的进程用不同的调度实体表示
|
||||
|
||||
</font>
|
||||
|
||||
| 普通进程 | 实时进程 |
|
||||
| ------------- |:-------------:|
|
||||
| sched_entity | rt_entity, sched_dl_entity |
|
||||
|
||||
@@ -5,7 +5,7 @@ Linux进程核心调度器之周期性调度器
|
||||
|
||||
| 日期 | 内核版本 | 架构| 作者 | GitHub| CSDN |
|
||||
| ------- |:-------:|:-------:|:-------:|:-------:|:-------:|
|
||||
| 2016-06-24 | [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) |
|
||||
| 2016-06-29 | [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/details/51456569) |
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Linux进程核心调度器之周期性调度器
|
||||
|
||||
首先还是让我们简单回顾一下子之前的的内容
|
||||
|
||||
##进程调度
|
||||
##1.1 进程调度
|
||||
-------
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ Linux进程核心调度器之周期性调度器
|
||||
|
||||
|
||||
|
||||
##1.1 进程的分类
|
||||
##1.2 进程的分类
|
||||
-------
|
||||
|
||||
linux把进程区分为**实时进程**和**非实时进程**, 其中非实时进程进一步划分为交互式进程和批处理进程
|
||||
@@ -63,11 +63,9 @@ linux把进程区分为**实时进程**和**非实时进程**, 其中非实时
|
||||
对于普通进程则采用CFS完全公平调度器进行调度
|
||||
|
||||
|
||||
##1.2 linux调度器的演变
|
||||
##1.3 linux调度器的演变
|
||||
-------
|
||||
|
||||
|
||||
|
||||
| 字段 | 版本 |
|
||||
| ------------- |:-------------:|
|
||||
| O(n)的始调度算法 | linux-0.11~2.4 |
|
||||
@@ -75,7 +73,7 @@ linux把进程区分为**实时进程**和**非实时进程**, 其中非实时
|
||||
| CFS调度器 | linux-2.6~至今 |
|
||||
|
||||
|
||||
##1.3 Linux的调度器组成
|
||||
##1.4 Linux的调度器组成
|
||||
-------
|
||||
|
||||
|
||||
@@ -111,7 +109,7 @@ linux内核目前实现了6中调度策略(即调度算法), 用于对不同类
|
||||
|
||||
|
||||
其所属进程的优先级顺序为
|
||||
````c
|
||||
```c
|
||||
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
|
||||
```
|
||||
|
||||
@@ -367,6 +365,6 @@ void do_timer(struct pt_regs *regs)
|
||||
|
||||
|
||||
|
||||
>参考
|
||||
>参考
|
||||
>
|
||||
>[进程管理与调度5 -- 进程调度、进程切换原理详解](http://wanderer-zjhit.blogbus.com/logs/156738683.html)
|
||||
@@ -7,7 +7,7 @@ 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) |
|
||||
| 2016-06-30 | [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) |
|
||||
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ Linux进程核心调度器之主调度器
|
||||
在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 从系统调用返回后, 内核也会检查当前进程是否设置了重调度标志TLF_NEDD_RESCHED
|
||||
|
||||
|
||||
#前景回顾
|
||||
#1 前景回顾
|
||||
-------
|
||||
|
||||
|
||||
##进程调度
|
||||
##1.1 进程调度
|
||||
-------
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Linux进程核心调度器之主调度器
|
||||
|
||||
|
||||
|
||||
##进程的分类
|
||||
##1.2 进程的分类
|
||||
-------
|
||||
|
||||
linux把进程区分为**实时进程**和**非实时进程**, 其中非实时进程进一步划分为交互式进程和批处理进程
|
||||
@@ -54,7 +54,7 @@ linux把进程区分为**实时进程**和**非实时进程**, 其中非实时
|
||||
|
||||
|
||||
|
||||
##linux调度器的演变
|
||||
##1.3 linux调度器的演变
|
||||
-------
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ linux把进程区分为**实时进程**和**非实时进程**, 其中非实时
|
||||
| CFS调度器 | linux-2.6~至今 |
|
||||
|
||||
|
||||
##Linux的调度器组成
|
||||
##1.4 Linux的调度器组成
|
||||
-------
|
||||
|
||||
|
||||
@@ -123,14 +123,14 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
|
||||
|
||||
|
||||
|
||||
#主调度器
|
||||
#2 主调度器
|
||||
-------
|
||||
|
||||
在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 从系统调用返回后, 内核也会检查当前进程是否设置了重调度标志TLF_NEDD_RESCHED
|
||||
|
||||
例如, 前述的周期性调度器的scheduler_tick就会设置该标志, 如果是这样则内核会调用schedule, 该函数假定当前活动进程一定会被另一个进程取代.
|
||||
|
||||
##调度函数的__sched前缀
|
||||
##2.1 调度函数的__sched前缀
|
||||
|
||||
在详细论述schedule之前, 需要说明一下__sched前缀, 该前缀可能用于调用schedule的函数, 包括schedule本身.
|
||||
|
||||
@@ -156,10 +156,10 @@ void __sched some_function(args, ...)
|
||||
}
|
||||
```
|
||||
|
||||
##schedule函数
|
||||
##2.2 schedule函数
|
||||
-------
|
||||
|
||||
###schedule主框架
|
||||
###2.2.1 schedule主框架
|
||||
-------
|
||||
|
||||
schedule就是主调度器的函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule.
|
||||
@@ -192,7 +192,7 @@ asmlinkage __visible void __sched schedule(void)
|
||||
EXPORT_SYMBOL(schedule);
|
||||
```
|
||||
|
||||
###sched_submit_work避免死锁
|
||||
###2.2.2 sched_submit_work避免死锁
|
||||
-------
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ static inline void sched_submit_work(struct task_struct *tsk)
|
||||
}
|
||||
```
|
||||
|
||||
###preempt_disable和sched_preempt_enable_no_resched开关内核抢占
|
||||
###2.2.3 preempt_disable和sched_preempt_enable_no_resched开关内核抢占
|
||||
-------
|
||||
|
||||
**内核抢占**
|
||||
@@ -249,13 +249,13 @@ do { \
|
||||
} while (0)
|
||||
```
|
||||
|
||||
## __schedule开始进程调度
|
||||
##2.3 __schedule开始进程调度
|
||||
-------
|
||||
|
||||
__schedule完成了真正的调度工作, 其定义在[kernel/sched/core.c, L3103](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3103), 如下所示
|
||||
|
||||
|
||||
###__schedule函数主框架
|
||||
###2.3.1 __schedule函数主框架
|
||||
-------
|
||||
|
||||
|
||||
@@ -406,7 +406,7 @@ static void __sched notrace __schedule(bool preempt)
|
||||
STACK_FRAME_NON_STANDARD(__schedule); /* switch_to() */
|
||||
```
|
||||
|
||||
###pick_next_task选择抢占的进程
|
||||
###2.3.2 pick_next_task选择抢占的进程
|
||||
-------
|
||||
|
||||
内核从cpu的就绪队列中选择一个最合适的进程来抢占CPU
|
||||
@@ -525,7 +525,7 @@ extern const struct sched_class idle_sched_class;
|
||||
>对于FIFO和RR的区别,在scheduler_tick中通过curr->sched_class->task_tick进入到task_tick_rt的处理, 如果是非RR的进程则直接返回,否则递减时间片,如果时间片耗完,则需要将当前进程放到运行队列的末尾, 这个时候才操作运行队列(FIFO和RR进程,是否位于同一个plist队列?),时间片到点,会重新移动当前进程requeue_task_rt,进程会被加到队列尾,接下来set_tsk_need_resched触发调度,进程被抢占进入schedule
|
||||
|
||||
|
||||
**问题1 : 为什么要多次一举判断所有的进程是否全是cfs调度的普通非实时进程?**
|
||||
**问题1 : 为什么要多此一举判断所有的进程是否全是cfs调度的普通非实时进程?**
|
||||
|
||||
|
||||
加快经常性事件, 是程序开发中一个优化的准则, 那么linux系统中最普遍的进程是什么呢? 肯定是非实时进程啊, 其调度器必然是cfs, 因此
|
||||
@@ -549,7 +549,7 @@ rev->sched_class == class && rq->nr_running == rq->cfs.h_nr_running
|
||||
#endif
|
||||
```
|
||||
|
||||
##context_switch进程上下文切换
|
||||
##2.4 context_switch进程上下文切换
|
||||
-------
|
||||
|
||||
>进程上下文的切换其实是一个很复杂的过程, 我们在这里不能详述, 但是我会尽可能说明白
|
||||
@@ -557,7 +557,7 @@ rev->sched_class == class && rq->nr_running == rq->cfs.h_nr_running
|
||||
>具体的内容请参照
|
||||
|
||||
|
||||
###进程上下文切换
|
||||
###2.4.1 进程上下文切换
|
||||
-------
|
||||
|
||||
|
||||
@@ -577,7 +577,7 @@ rev->sched_class == class && rq->nr_running == rq->cfs.h_nr_running
|
||||
上下文切换只能发生在内核态中, 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
|
||||
Linux相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少.
|
||||
|
||||
###context_switch流程
|
||||
###2.4.2 context_switch流程
|
||||
-------
|
||||
|
||||
context_switch函数完成了进程上下文的切换, 其定义在[kernel/sched/core.c#L2711](http://lxr.free-electrons.com/source/kernel/sched/core.c#L2711)
|
||||
@@ -597,7 +597,7 @@ context_switch( )函数保证:如果next是一个内核线程, 它使用prev
|
||||
由于不同架构下地址映射的机制有所区别, 而寄存器等信息弊病也是依赖于架构的, 因此switch_mm和switch_to两个函数均是体系结构相关的
|
||||
|
||||
|
||||
###switch_mm切换进程虚拟地址空间
|
||||
###2.4.3 switch_mm切换进程虚拟地址空间
|
||||
-------
|
||||
|
||||
switch_mm主要完成了进程prev到next虚拟地址空间的映射, 由于内核虚拟地址空间是不许呀切换的, 因此切换的主要是用户态的虚拟地址空间
|
||||
@@ -623,7 +623,7 @@ switch_mm主要完成了进程prev到next虚拟地址空间的映射, 由于内
|
||||
>CR3中含有页目录表物理内存基地址,因此该寄存器也被称为页目录基地址寄存器PDBR(Page-Directory Base address Register)。
|
||||
|
||||
|
||||
###switch_to切换进程堆栈和寄存器
|
||||
###2.4.4 switch_to切换进程堆栈和寄存器
|
||||
-------
|
||||
|
||||
执行环境的切换是在switch_to()中完成的, switch_to完成最终的进程切换,它保存原进程的所有寄存器信息,恢复新进程的所有寄存器信息,并执行新的进程
|
||||
@@ -656,10 +656,10 @@ switch_mm主要完成了进程prev到next虚拟地址空间的映射, 由于内
|
||||
3. 堆栈的切换, 即ebp的切换, ebp是栈底指针, 它确定了当前用户空间属于哪个进程
|
||||
|
||||
|
||||
##need_resched, TIF_NEED_RESCHED标识与用户抢占
|
||||
##2.5 need_resched, TIF_NEED_RESCHED标识与用户抢占
|
||||
-------
|
||||
|
||||
###need_resched标识TIF_NEED_RESCHED
|
||||
###2.5.1 need_resched标识TIF_NEED_RESCHED
|
||||
-------
|
||||
|
||||
|
||||
@@ -687,7 +687,7 @@ static __always_inline bool need_resched(void)
|
||||
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
|
||||
```
|
||||
|
||||
###用户抢占和内核抢占
|
||||
###2.5.2 用户抢占和内核抢占
|
||||
-------
|
||||
|
||||
当内核即将返回用户空间时, 内核会检查need_resched是否设置,如果设置,则调用schedule(),此时,发生用户抢占。
|
||||
@@ -717,10 +717,10 @@ static __always_inline bool need_resched(void)
|
||||
|
||||
|
||||
|
||||
#总结
|
||||
#3 总结
|
||||
-------
|
||||
|
||||
**schedule调度流程**
|
||||
##3.1 **schedule调度流程**
|
||||
|
||||
schedule就是主调度器的函数, 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 该函数定义在[kernel/sched/core.c, L3243](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3243), 如下所示
|
||||
|
||||
@@ -740,7 +740,7 @@ schedule就是主调度器的函数, 在内核中的许多地方, 如果要将CP
|
||||
} while (need_resched()); /* 如果该进程被其他进程设置了TIF_NEED_RESCHED标志,则函数重新执行进行调度 */
|
||||
```
|
||||
|
||||
**__schedule如何完成内核抢占**
|
||||
##3.2 **__schedule如何完成内核抢占**
|
||||
|
||||
1. 完成一些必要的检查, 并设置进程状态, 处理进程所在的就绪队列
|
||||
|
||||
@@ -757,7 +757,7 @@ schedule就是主调度器的函数, 在内核中的许多地方, 如果要将CP
|
||||
* 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息
|
||||
|
||||
|
||||
**调度的内核抢占和用户抢占**
|
||||
##3.3 **调度的内核抢占和用户抢占**
|
||||
|
||||
内核在完成调度的过程中总是先关闭内核抢占, 等待内核完成调度的工作后, 再把内核抢占开启, 如果在内核完成调度器过程中, 这时候如果发生了内核抢占, 我们的调度会被中断, 而调度却还没有完成, 这样会丢失我们调度的信息.
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
|
||||
#3 linux用户抢占
|
||||
-------
|
||||
|
||||
#3.1 #linux用户抢占
|
||||
##3.1 #linux用户抢占
|
||||
-------
|
||||
|
||||
当内核即将返回用户空间时, 内核会检查need_resched是否设置, 如果设置, 则调用schedule(),此时,发生用户抢占.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Linux用户抢占和内核抢占详解(概念, 实现和触发时机)
|
||||
Linux进程上下文切换过程context_switch详解
|
||||
=======
|
||||
|
||||
|
||||
@@ -101,8 +101,11 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
|
||||
|
||||
而__schedule则执行了如下操作
|
||||
|
||||
|
||||
**__schedule如何完成内核抢占**
|
||||
|
||||
<font color=0x00ffff>
|
||||
|
||||
1. 完成一些必要的检查, 并设置进程状态, 处理进程所在的就绪队列
|
||||
|
||||
2. 调度全局的pick_next_task选择抢占的进程
|
||||
@@ -117,6 +120,8 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
|
||||
|
||||
那么我们今天就详细讲解一下context_switch完成进程上下文切换的原理
|
||||
|
||||
</font>
|
||||
|
||||
|
||||
#2 进程上下文
|
||||
-------
|
||||
@@ -179,24 +184,29 @@ linux中进程调度时, 内核在选择新进程之后进行抢占时, 通过co
|
||||
>
|
||||
>在进程发生调度时, 只有当前内核发生当前进程因为主动或者被动需要放弃CPU时, 内核才会选择一个与当前活动进程不同的进程来抢占CPU
|
||||
|
||||
|
||||
|
||||
context_switch其实是一个分配器, 他会调用所需的特定体系结构的方法
|
||||
|
||||
* 调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中
|
||||
|
||||
switch_mm更换通过task_struct->mm描述的内存管理上下文, 该工作的细节取决于处理器, 主要包括加载页表, 刷出地址转换后备缓冲器(部分或者全部), 向内存管理单元(MMU)提供新的信息
|
||||
switch_mm更换通过task_struct->mm描述的内存管理上下文, 该工作的细节取决于处理器, 主要包括加载页表, 刷出地址转换后备缓冲器(部分或者全部), 向内存管理单元(MMU)提供新的信息
|
||||
|
||||
* 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息
|
||||
|
||||
switch_to切换处理器寄存器的呢内容和内核栈(虚拟地址空间的用户部分已经通过switch_mm变更, 其中也包括了用户状态下的栈, 因此switch_to不需要变更用户栈, 只需变更内核栈), 此段代码严重依赖于体系结构, 且代码通常都是用汇编语言编写.
|
||||
|
||||
<font color=0x00ffff>
|
||||
|
||||
context_switch函数建立next进程的地址空间。进程描述符的active_mm字段指向进程所使用的内存描述符,而mm字段指向进程所拥有的内存描述符。对于一般的进程,这两个字段有相同的地址,但是,内核线程没有它自己的地址空间而且它的 mm字段总是被设置为 NULL
|
||||
|
||||
context_switch( )函数保证:如果next是一个内核线程, 它使用prev所使用的地址空间
|
||||
</font>
|
||||
|
||||
由于不同架构下地址映射的机制有所区别, 而寄存器等信息弊病也是依赖于架构的, 因此switch_mm和switch_to两个函数均是体系结构相关的
|
||||
|
||||
|
||||
|
||||
##3.1 context_switch完全注释
|
||||
-------
|
||||
|
||||
@@ -639,7 +649,11 @@ __switch_to函数
|
||||
|
||||
在进程A被选中再次执行的时候, 会出现一个问题, 此时控制权即将回到A, switch_to函数返回, 内核开始执行switch_to之后的点, 此时内核栈准确的恢复到切换之前的状态, 即进程A上次被切换出去时的状态, prev = A, next = B. 此时, 内核无法知道实际上在进程A之前运行的是进程C.
|
||||
|
||||
|
||||
<font color=0x00ffff>
|
||||
|
||||
因此, 在新进程被选中执行时, 内核恢复到进程被切换出去的点继续执行, 此时内核只知道谁之前将新进程抢占了, 但是却不知道新进程再次执行是抢占了谁, 因此底层的进程切换机制必须将此前执行的进程(即新进程抢占的那个进程)提供给context_switch. 由于控制流会回到函数的该中间, 因此无法通过普通函数的返回值来完成. 因此使用了一个3个参数, 但是逻辑效果是相同的, 仿佛是switch_to是带有两个参数的函数, 而且返回了一个指向此前运行的进程的指针.
|
||||
</font>
|
||||
|
||||
>switch_to(prev, next, last);
|
||||
>
|
||||
@@ -868,7 +882,5 @@ static struct rq *finish_task_switch(struct task_struct *prev)
|
||||
```
|
||||
|
||||
|
||||
#4 linux下进程切换性能分析
|
||||
-------
|
||||
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ Linux进程优先级的处理
|
||||
| 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) |
|
||||
|
||||
|
||||
#前景回顾
|
||||
#1 前景回顾
|
||||
-------
|
||||
|
||||
|
||||
##进程调度
|
||||
##1.1 进程调度
|
||||
-------
|
||||
|
||||
内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来.
|
||||
@@ -26,7 +26,7 @@ Linux进程优先级的处理
|
||||
调度器的一般原理是, 按所需分配的计算能力, 向系统中每个进程提供最大的公正性, 或者从另外一个角度上说, 他试图确保没有进程被亏待.
|
||||
|
||||
|
||||
##进程的分类
|
||||
##1.2 进程的分类
|
||||
-------
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进
|
||||
在linux中, 调度算法可以明确的确认所有实时进程的身份, 但是没办法区分交互式程序和批处理程序, linux2.6的调度程序实现了基于进程过去行为的启发式算法, 以确定进程应该被当做交互式进程还是批处理进程. 当然与批处理进程相比, 调度程序有偏爱交互式进程的倾向
|
||||
|
||||
|
||||
##不同进程采用不同的调度策略
|
||||
##1.3 不同进程采用不同的调度策略
|
||||
-------
|
||||
|
||||
根据进程的不同分类Linux采用不同的调度策略.
|
||||
@@ -57,7 +57,7 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进
|
||||
但是普通进程的调度策略就比较麻烦了, 因为普通进程不能简单的只看优先级, 必须公平的占有CPU, 否则很容易出现进程饥饿, 这种情况下用户会感觉操作系统很卡, 响应总是很慢,因此在linux调度器的发展历程中经过了多次重大变动, linux总是希望寻找一个最接近于完美的调度策略来公平快速的调度进程.
|
||||
|
||||
|
||||
##linux调度器的演变
|
||||
##1.4 linux调度器的演变
|
||||
-------
|
||||
|
||||
一开始的调度器是复杂度为**$O(n)$的始调度算法**(实际上每次会遍历所有任务,所以复杂度为O(n)), 这个算法的缺点是当内核中有很多任务时,调度器本身就会耗费不少时间,所以,从linux2.5开始引入赫赫有名的**$O(1)$调度器**
|
||||
@@ -73,7 +73,7 @@ linux把进程区分为实时进程和非实时进程, 其中非实时进程进
|
||||
| CFS调度器 | linux-2.6~至今 |
|
||||
|
||||
|
||||
##Linux的调度器组成
|
||||
##1.5 Linux的调度器组成
|
||||
-------
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ linux内核目前实现了6中调度策略(即调度算法), 用于对不同类
|
||||
|
||||
|
||||
其所属进程的优先级顺序为
|
||||
````c
|
||||
```c
|
||||
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
|
||||
```
|
||||
|
||||
@@ -146,15 +146,15 @@ linux实现了6种调度策略, 依据其调度策略的不同实现了5个调
|
||||
|
||||
它们的关系如下图
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
#linux优先级的表示
|
||||
#2 linux优先级的表示
|
||||
-------
|
||||
|
||||
##优先级的内核表示
|
||||
##2.1 优先级的内核表示
|
||||
-------
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ linux实现了6种调度策略, 依据其调度策略的不同实现了5个调
|
||||
| 0——99 | 实时进程 |
|
||||
| 100——139 | 非实时进程 |
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
**内核的优先级表示**
|
||||
@@ -290,7 +290,7 @@ static inline bool dl_time_before(u64 a, u64 b)
|
||||
```
|
||||
|
||||
|
||||
##进程的优先级表示
|
||||
##2.2 进程的优先级表示
|
||||
-------
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ struct task_struct
|
||||
|
||||
|
||||
|
||||
#进程优先级的计算
|
||||
#3 进程优先级的计算
|
||||
-------
|
||||
|
||||
|
||||
@@ -346,14 +346,17 @@ struct task_struct
|
||||
|
||||
|
||||
|
||||
##normal_prio设置普通优先级normal_prio
|
||||
##3.1 normal_prio设置普通优先级normal_prio
|
||||
-------
|
||||
|
||||
<font color=0x00ffff>
|
||||
静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点
|
||||
</font>
|
||||
|
||||
因此他们也是进程创建的时候设定好的, 我们通过nice修改的就是静态优先级static_prio
|
||||
因此他们也是进程创建的时候设定好的, 我们通过nice修改的就是普通进程的静态优先级static_prio
|
||||
|
||||
首先通过静态优先级static_prio计算出普通优先级normal_prio, 改工作可以由nromal_prio来完成, 该函数定义在[kernel/sched/core.c#L861](http://lxr.free-electrons.com/source/kernel/sched/core.c#L861)
|
||||
|
||||
首先通过静态优先级static_prio计算出普通优先级normal_prio, 该工作可以由nromal_prio来完成, 该函数定义在[kernel/sched/core.c#L861](http://lxr.free-electrons.com/source/kernel/sched/core.c#L861)
|
||||
|
||||
```c
|
||||
/*
|
||||
@@ -395,7 +398,7 @@ static inline int normal_prio(struct task_struct *p)
|
||||
|
||||
普通优先级normal_prio需要根据普通进程和实时进程进行不同的计算, 其中__normal_prio适用于普通进程, 直接将普通优先级normal_prio设置为静态优先级static_prio. 而实时进程的普通优先级计算依据其实时优先级rt_priority.
|
||||
|
||||
###辅助函数task_has_dl_policy和task_has_rt_policy
|
||||
###3.1.1 辅助函数task_has_dl_policy和task_has_rt_policy
|
||||
-------
|
||||
|
||||
定义在[kernel/sched/sched.h#L117](http://lxr.free-electrons.com/source/kernel/sched/sched.h?v=4.6#L117) 中
|
||||
@@ -439,7 +442,7 @@ static inline int task_has_dl_policy(struct task_struct *p)
|
||||
```
|
||||
|
||||
|
||||
###关于rt_priority数值越大, 实时进程优先级越高的问题
|
||||
###3.1.2 关于rt_priority数值越大, 实时进程优先级越高的问题
|
||||
--------
|
||||
|
||||
我们前面提到了数值越小, 优先级越高, 但是此处我们会发现rt_priority的值越大, 其普通优先级越小, 从而优先级越高.
|
||||
@@ -458,15 +461,19 @@ MAX_RT_PRIO = 100, ;这样意味着rt_priority值越大,优先级越高;
|
||||
|
||||
所以用户在使用实时进程或线程,在修改优先级时,就会有“优先级值越大,优先级越高的说法”,也是对的。
|
||||
|
||||
###为什么需要__normal_prio函数
|
||||
###3.1.3 为什么需要__normal_prio函数
|
||||
-------
|
||||
|
||||
我们肯定会奇怪, 为什么增加了一个__normal_prio函数做了这么简单的工作, 这个其实是有历史原因的: 在早期的$O(1)$调度器中, 普通优先级的计算涉及相当多技巧性地工作, 必须检测交互式进程并提高其优先级, 而必须"惩罚"非交互进程, 以便是得系统获得更好的交互体验. 这需要很多启发式的计算, 他们可能完成的很好, 也可能不工作
|
||||
|
||||
##effective_prio设置动态优先级prio
|
||||
|
||||
##3.2 effective_prio设置动态优先级prio
|
||||
-------
|
||||
|
||||
|
||||
<font color=0x00ffff>
|
||||
可以通过函数effective_prio用静态优先级static_prio计算动态优先级prio, 即·
|
||||
</font>
|
||||
|
||||
```c
|
||||
p->prio = effective_prio(p);
|
||||
@@ -496,7 +503,10 @@ static int effective_prio(struct task_struct *p)
|
||||
}
|
||||
```
|
||||
|
||||
<fonr color=0x00ffff>
|
||||
我们会发现函数首先effective_prio设置了普通优先级, 显然我们用effective_prio同时设置了两个优先级(普通优先级normal_prio和动态优先级prio)
|
||||
</font>
|
||||
|
||||
|
||||
因此计算动态优先级的流程如下
|
||||
|
||||
@@ -513,7 +523,8 @@ static int effective_prio(struct task_struct *p)
|
||||
| 普通进程 | 不使用 | static_prio | static_prio | static_prio |
|
||||
| 优先级提高的普通进程 | 不使用 | static_prio(改变) | static_prio | 维持原prio不变 |
|
||||
|
||||
###为什么effective_prio使用优先级数值检测实时进程
|
||||
|
||||
###3.2.1 为什么effective_prio使用优先级数值检测实时进程
|
||||
-------
|
||||
|
||||
t_prio会检测普通优先级是否在实时范围内, 即是否小于MAX_RT_PRIO.参见[include/linux/sched/rt.h#L6](http://lxr.free-electrons.com/source/include/linux/sched/rt.h#L6)
|
||||
@@ -535,17 +546,18 @@ policy == SCHED_FIFO || policy == SCHED_RR;
|
||||
对于临时提高至实时优先级的非实时进程来说, 这个是必要的, 这种情况可能发生在是哦那个实时互斥量(RT-Mutex)时.
|
||||
|
||||
|
||||
##设置prio的时机
|
||||
##3.3 设置prio的时机
|
||||
-------
|
||||
|
||||
|
||||
* 在新进程用wake_up_new_task唤醒时, 或者使用nice系统调用改变其静态优先级时, 则会通过effective_prio的方法设置p->prio
|
||||
|
||||
>wake_up_new_task(), 计算此进程的优先级和其他调度参数,将新的进程加入到进程调度队列并设此进程为可被调度的,以后这个进程可以被进程调度模块调度执行。
|
||||
|
||||
* 进程创建时copy_process通过调用sched_fork来初始化和设置调度器的过程中会设置子进程的优先级
|
||||
|
||||
##nice系统调用的实现
|
||||
|
||||
|
||||
##3.4 nice系统调用的实现
|
||||
-------
|
||||
|
||||
nice系统调用是的内核实现是sys_nice, 其定义在[kernel/sched/core.c#L7498](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L7498),
|
||||
@@ -555,7 +567,7 @@ nice系统调用是的内核实现是sys_nice, 其定义在[kernel/sched/core.c#
|
||||
关于其具体实现我们会在另外一篇博客里面详细讲
|
||||
|
||||
|
||||
##fork时优先级的继承
|
||||
##3.5 fork时优先级的继承
|
||||
-------
|
||||
|
||||
|
||||
@@ -622,12 +634,13 @@ int sched_fork(unsigned long clone_flags, struct task_struct *p)
|
||||
```
|
||||
|
||||
|
||||
#总结
|
||||
|
||||
#4 总结
|
||||
-------
|
||||
|
||||
<font color=0x00ffff>
|
||||
task_struct采用了四个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级. 同时还用了rt_priority表示实时进程的优先级
|
||||
|
||||
|
||||
</font>
|
||||
|
||||
|
||||
| 字段 | 描述 |
|
||||
@@ -641,8 +654,12 @@ task_struct采用了四个成员表示进程的优先级:prio和normal_prio表
|
||||
调度器会考虑的优先级则保存在prio. 由于在某些情况下内核需要暂时提高进程的优先级, 因此需要用prio表示. 由于这些改变不是持久的, 因此静态优先级static_prio和普通优先级normal_prio不受影响.
|
||||
此外还用了一个字段rt_priority保存了实时进程的优先级静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点, 通过他们计算进程的普通优先级normal_prio和动态优先级prio.
|
||||
|
||||
<font color=0x00ffff>
|
||||
内核通过normal_prIo函数计算普通优先级normal_prio
|
||||
通过effective_prio函数计算动态优先级prio
|
||||
</font>
|
||||
|
||||
|
||||
|
||||
>参考
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user