进程调度之优先级详解...

This commit is contained in:
gatieme
2016-06-23 23:51:22 +08:00
parent ff7c46a486
commit bce3b4e8c4
3 changed files with 175 additions and 0 deletions
@@ -60,6 +60,7 @@ linux内核目前实现了6中调度策略(即调度算法), 用于对不同类
stop_sched_class -> dl_sched_class -> rt_sched_class -> fair_sched_class -> idle_sched_class
```
**3个调度实体**
调度器不限于调度进程, 还可以调度更大的实体, 比如实现组调度.
@@ -105,3 +106,177 @@ linux中针对当前可调度的实时和非实时进程, 定义了类型为sech
#非抢占式和可抢占式内核
-------
为了简化问题,我使用嵌入式实时系统uC/OS作为例子
首先要指出的是,uC/OS只有内核态,没有用户态,这和Linux不一样
多任务系统中, 内核负责管理各个任务, 或者说为每个任务分配CPU时间, 并且负责任务之间的通讯.
内核提供的基本服务是任务切换. 调度(Scheduler,英文还有一词叫dispatcher, 也是调度的意思.
这是内核的主要职责之一, 就是要决定该轮到哪个任务运行了. 多数实时内核是基于优先级调度法的, 每个任务根据其重要程度的不同被赋予一定的优先级. 基于优先级的调度法指,CPU总是让处在就绪态的优先级最高的任务先运行. 然而, 究竟何时让高优先级任务掌握CPU的使用权, 有两种不同的情况, 这要看用的是什么类型的内核, 是**不可剥夺型**的还是**可剥夺型内核**
##非抢占式内核
-------
**非抢占式内核**是由任务主动放弃CPU的使用权
非抢占式调度法也称作合作型多任务, 各个任务彼此合作共享一个CPU. 异步事件还是由中断服务来处理. 中断服务可以使一个高优先级的任务由挂起状态变为就绪状态.
但中断服务以后控制权还是回到原来被中断了的那个任务, 直到该任务主动放弃CPU的使用权时,那个高优先级的任务才能获得CPU的使用权。非抢占式内核如下图所示.
![非抢占式内核](./images/NonPreemptiveKernel.png)
非抢占式内核的优点有
* 中断响应快(与抢占式内核比较)
* 允许使用不可重入函数;
* 几乎不需要使用信号量保护共享数据, 运行的任务占有CPU,不必担心被别的任务抢占。这不是绝对的,在打印机的使用上,仍需要满足互斥条件。
非抢占式内核的缺点有
* 任务响应时间慢。高优先级的任务已经进入就绪态,但还不能运行,要等到当前运行着的任务释放CPU。
* 非抢占式内核的任务级响应时间是不确定的,不知道什么时候最高优先级的任务才能拿到CPU的控制权,完全取决于应用程序什么时候释放CPU。
##抢占式内核
-------
使用抢占式内核可以保证系统响应时间. 最高优先级的任务一旦就绪, 总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入了就绪态, 当前任务的CPU使用权就会被剥夺,或者说被挂起了,那个高优先级的任务立刻得到了CPU的控制权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的那个任务开始运行。
抢占式内核如下图所示
![抢占式内核](./images/PreemptiveKernel.png)
抢占式内核的优点有
* 使用抢占式内核,最高优先级的任务什么时候可以执行,可以得到CPU的使用权是可知的。使用抢占式内核使得任务级响应时间得以最优化。
抢占式内核的缺点有:
* 不能直接使用不可重入型函数。调用不可重入函数时,要满足互斥条件,这点可以使用互斥型信号量来实现。如果调用不可重入型函数时,低优先级的任务CPU的使用权被高优先级任务剥夺,不可重入型函数中的数据有可能被破坏。
#Linux下的用户态抢占和内核态抢占
-------
CPU抢占分两种情况, **用户抢占**, **内核抢占**
其中内核抢占是在Linux2.5.4版本发布时加入, 同SMP(Symmetrical Multi-Processing, 对称多处理器), 作为内核的可选配置。
##用户抢占
-------
###用户抢占的概念
-------
当内核即将返回用户空间时, 内核会检查need_resched是否设置, 如果设置, 则调用schedule(),此时,发生用户抢占.
###用户抢占的发生时机
一般来说,用户抢占发生几下情况:
1. 从系统调用返回用户空间;
2. 从中断(异常)处理程序返回用户空间
从这里我们可以看到, 用户抢占是发生在用户空间的抢占现象.
###need_resched标识
-------
内核如何检查一个进程是否需要被调度呢?
内核在即将返回用户空间时检查进程是否需要重新调度,如果设置了,就会发生调度, 这被称为**用户抢占**, 因此**内核在thread_info的flag中设置了一个标识来标志进程是否需要重新调度, 即重新调度need_resched标识TIF_NEED_RESCHED**
并提供了一些设置可检测的函数
| 函数 | 描述 | 定义 |
| ------- |:-------:|:-------:|
| set_tsk_need_resched | 设置指定进程中的need_resched标志 | [include/linux/sched.h, L2920](http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L2920) |
| clear_tsk_need_resched | 清除指定进程中的need_resched标志 | [include/linux/sched.h, L2926](http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L2931) |
| test_tsk_need_resched | 检查指定进程need_resched标志 | [include/linux/sched.h, L2931](http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L2931) |
而我们内核中调度时常用的need_resched()函数检查进程是否需要被重新调度其实就是通过test_tsk_need_resched实现的, 其定义如下所示
```c
// http://lxr.free-electrons.com/source/include/linux/sched.h?v=4.6#L3093
static __always_inline bool need_resched(void)
{
return unlikely(tif_need_resched());
}
// http://lxr.free-electrons.com/source/include/linux/thread_info.h?v=4.6#L106
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
```
##内核抢占
-------
###内核抢占的概念
-------
对比用户抢占, 顾名思义, 内核抢占就是指一个在内核态运行的进程, 可能在执行内核函数期间被另一个进程取代.
###为什么linux需要内核抢占
-------
linux系统中, 进程在系统调用后返回用户态之前, 或者是内核中某些特定的点上, 都会调用调度器. 这确保除了一些明确指定的情况之外, 内核是无法中断的, 这不同于用户进程.
如果内核处于相对耗时的操作中, 比如文件系统或者内存管理相关的任务, 这种行为可能会带来问题. 这种情况下, 内核代替特定的进程执行相当长的时间, 而其他进程无法执行, 无法调度
== ======================================
内核抢占主要是为实时系统来设计的, 当然在非实时系统中的确也能提高系统的响应速度, 但也不是在所有情况下都是最优的,因为抢占也需要调度和同步开销,在某些情况下甚至要关闭内核抢占, 比如前面我们将主调度器的时候, linux内核在完成调度的过程中是关闭了内核抢占的.
##内核抢占的发生时机
-------
内核抢占发生的时机,一般发生在:
1. 当从中断处理程序正在执行,且返回内核空间之前。
2. 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等。
3. 如果内核中的任务显式的调用schedule()。
4. 如果内核中的任务阻塞(这同样也会导致调用schedule())
内核抢占,并不是在任何一个地方都可以发生,以下情况不能发生
1. 内核正进行中断处理。在Linux内核中进程不能抢占中断(中断只能被其他中断中止、抢占,进程不能中止、抢占中断),在中断例程中不允许进行进程调度。进程调度函数schedule()会对此作出判断,如果是在中断中调用,会打印出错信息。
2. 内核正在进行中断上下文的Bottom Half(中断下半部,即软中断)处理。硬件中断返回前会执行软中断,此时仍然处于中断上下文中。如果此时正在执行其它软中断,则不再执行该软中断。
3. 内核的代码段正持有spinlock自旋锁、writelock/readlock读写锁等锁,处干这些锁的保护状态中。内核中的这些锁是为了在SMP系统中短时间内保证不同CPU上运行的进程并发执行的正确性。当持有这些锁时,内核不应该被抢占。
4. 内核正在执行调度程序Scheduler。抢占的原因就是为了进行新的调度,没有理由将调度程序抢占掉再运行调度程序。
5. 内核正在对每个CPU“私有”的数据结构操作(Per-CPU date structures)。在SMP中,对于per-CPU数据结构未用spinlocks保护,因为这些数据结构隐含地被保护了(不同的CPU有不一样的per-CPU数据,其他CPU上运行的进程不会用到另一个CPU的per-CPU数据)。但是如果允许抢占,但一个进程被抢占后重新调度,有可能调度到其他的CPU上去,这时定义的Per-CPU变量就会有问题,这时应禁抢占。
##内核抢占的实现
-------
http://blog.csdn.net/li4850729/article/details/28136643
http://blog.csdn.net/xiaofei0859/article/details/8113211
http://blog.sina.com.cn/s/blog_502c8cc401012pxj.html
http://www.cnblogs.com/hustcat/archive/2009/08/31/1557507.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB