diff --git a/study/kernel/01-process/05-schedule/06-preempt/README.md b/study/kernel/01-process/05-schedule/06-preempt/README.md index 259f170..50900c6 100644 --- a/study/kernel/01-process/05-schedule/06-preempt/README.md +++ b/study/kernel/01-process/05-schedule/06-preempt/README.md @@ -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 + + + diff --git a/study/kernel/01-process/05-schedule/06-preempt/images/NonPreemptiveKernel.png b/study/kernel/01-process/05-schedule/06-preempt/images/NonPreemptiveKernel.png new file mode 100644 index 0000000..74ee9e2 Binary files /dev/null and b/study/kernel/01-process/05-schedule/06-preempt/images/NonPreemptiveKernel.png differ diff --git a/study/kernel/01-process/05-schedule/06-preempt/images/PreemptiveKernel.png b/study/kernel/01-process/05-schedule/06-preempt/images/PreemptiveKernel.png new file mode 100644 index 0000000..3eb8062 Binary files /dev/null and b/study/kernel/01-process/05-schedule/06-preempt/images/PreemptiveKernel.png differ