mirror of
https://github.com/gatieme/LDD-LinuxDeviceDrivers.git
synced 2026-09-26 15:33:35 +08:00
进程调度之优先级详解...
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
struct ThreadInfo
|
||||
{
|
||||
int preempt_count;
|
||||
}tiv;
|
||||
|
||||
struct ThreadInfo *ti = &tiv;
|
||||
/*
|
||||
* Increment/decrement the preempt count.
|
||||
*/
|
||||
#ifdef CONFIG_PREEMPT_COUNT
|
||||
.macro inc_preempt_count, ti, tmp
|
||||
ldr \tmp, [\ti, #TI_PREEMPT] @ get preempt count
|
||||
add \tmp, \tmp, #1 @ increment it
|
||||
str \tmp, [\ti, #TI_PREEMPT]
|
||||
.endm
|
||||
|
||||
.macro dec_preempt_count, ti, tmp
|
||||
ldr \tmp, [\ti, #TI_PREEMPT] @ get preempt count
|
||||
sub \tmp, \tmp, #1 @ decrement it
|
||||
str \tmp, [\ti, #TI_PREEMPT]
|
||||
.endm
|
||||
|
||||
.macro dec_preempt_count_ti, ti, tmp
|
||||
get_thread_info \ti
|
||||
dec_preempt_count \ti, \tmp
|
||||
.endm
|
||||
#else
|
||||
.macro inc_preempt_count, ti, tmp
|
||||
.endm
|
||||
|
||||
.macro dec_preempt_count, ti, tmp
|
||||
.endm
|
||||
|
||||
.macro dec_preempt_count_ti, ti, tmp
|
||||
.endm
|
||||
#endif
|
||||
|
||||
int main(void)
|
||||
{
|
||||
ti->preempt_count = 0;
|
||||
inc_preempt_count(ti);
|
||||
|
||||
}
|
||||
@@ -235,16 +235,26 @@ static __always_inline bool need_resched(void)
|
||||
|
||||
linux系统中, 进程在系统调用后返回用户态之前, 或者是内核中某些特定的点上, 都会调用调度器. 这确保除了一些明确指定的情况之外, 内核是无法中断的, 这不同于用户进程.
|
||||
|
||||
如果内核处于相对耗时的操作中, 比如文件系统或者内存管理相关的任务, 这种行为可能会带来问题. 这种情况下, 内核代替特定的进程执行相当长的时间, 而其他进程无法执行, 无法调度
|
||||
== ======================================
|
||||
如果内核处于相对耗时的操作中, 比如文件系统或者内存管理相关的任务, 这种行为可能会带来问题. 这种情况下, 内核代替特定的进程执行相当长的时间, 而其他进程无法执行, 无法调度, 这就造成了系统的延迟增加, 用户体验到"缓慢"的响应. 比如如果多媒体应用长时间无法得到CPU, 则可能发生视频和音频漏失现象.
|
||||
|
||||
在编译内核时如果启用了对**内核抢占**的支持, 则可以解决这些问题. 如果高优先级进程有事情需要完成, 那么在启用了内核抢占的情况下, 不仅用户空间应用程序可以被中断, 内核也可以被中断,
|
||||
|
||||
|
||||
内核抢占主要是为实时系统来设计的, 当然在非实时系统中的确也能提高系统的响应速度, 但也不是在所有情况下都是最优的,因为抢占也需要调度和同步开销,在某些情况下甚至要关闭内核抢占, 比如前面我们将主调度器的时候, linux内核在完成调度的过程中是关闭了内核抢占的.
|
||||
linux内核抢占是在Linux2.5.4版本发布时加入的, 尽管使内核可抢占需要的改动特别少, 但是该机制不像抢占用户空间进程那样容易实现. 如果内核无法一次性完成某些操作(例如, 对数据结构的操作), 那么可能出现静态条件而使得系统不一致.
|
||||
|
||||
内核抢占和用户层进程被其他进程抢占是两个不同的概念, 内核抢占主要是从实时系统中引入的, 在非实时系统中的确也能提高系统的响应速度, 但也不是在所有情况下都是最优的,因为抢占也需要调度和同步开销,在某些情况下甚至要关闭内核抢占, 比如前面我们将主调度器的时候, linux内核在完成调度的过程中是关闭了内核抢占的.
|
||||
|
||||
内核不能再任意点被中断, 幸运的是, 大多数不能中断的点已经被SMP实现标识出来了. 并且在实现内核抢占时可以重用这些信息. 如果内核可以被抢占, 那么单处理器系统也会像是一个SMP系统
|
||||
|
||||
##内核抢占的发生时机
|
||||
-------
|
||||
|
||||
要满足什么条件,kernel才可以抢占一个任务的内核态呢?
|
||||
|
||||
* 没持有锁。锁是用于保护临界区的,不能被抢占。
|
||||
|
||||
* Kernel code可重入(reentrant)。因为kernel是SMP-safe的,所以满足可重入性。
|
||||
|
||||
内核抢占发生的时机,一般发生在:
|
||||
|
||||
1. 当从中断处理程序正在执行,且返回内核空间之前。
|
||||
@@ -271,8 +281,134 @@ linux系统中, 进程在系统调用后返回用户态之前, 或者是内核
|
||||
##内核抢占的实现
|
||||
-------
|
||||
|
||||
###内核如何跟踪它能否被抢占?
|
||||
-------
|
||||
|
||||
|
||||
|
||||
前面我们提到了, 系统中每个进程都有一个特定于体系结构的struct thread_info结构, 用户层程序被调度的时候会检查struct thread_info中的need_resched标识TLF_NEED_RESCHED标识来检查自己是否需要被重新调度.
|
||||
|
||||
自然内核抢占·也可以应用同样的方法被实现, linux内核在thread_info结构中添加了一个自旋锁标识preempt_count, 称为**抢占计数器(preemption counter)**.
|
||||
|
||||
```c
|
||||
struct thread_info
|
||||
{
|
||||
/* ...... */
|
||||
int preempt_count; /* 0 => preemptable, <0 => BUG */
|
||||
/* ...... */
|
||||
}
|
||||
````
|
||||
| preempt_count值 | 描述 |
|
||||
| ------- |:-------:|
|
||||
| >0 | 禁止内核抢占, 其值标记了使用preempt_count的临界区的数目 |
|
||||
| 0 | 开启内核抢占 |
|
||||
| <0 | 锁为负值, 内核出现错误 |
|
||||
|
||||
内核自然也提供了一些函数或者宏, 用来开启, 关闭以及检测抢占计数器preempt_coun的值, 这些通用的函数定义在[include/asm-generic/preempt.h](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L8), 而某些架构也定义了自己的接口, 比如x86架构[/arch/x86/include/asm/preempt.h](http://lxr.free-electrons.com/source/arch/x86/include/asm/preempt.h?v=4.6)
|
||||
|
||||
| 函数 | 描述 | 定义 |
|
||||
| ------- |:-------:|:-------:|
|
||||
| preempt_count | 获取当前current进程抢占计数器的值 | [include/asm-generic/preempt.h, line 8](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L8) |
|
||||
| preempt_count_ptr | 返回指向当前current进程的抢占计数器的指针 | [include/asm-generic/preempt.h, line 13](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L13) |
|
||||
| preempt_count_set | 重设当前current进程的抢占计数器 | [include/asm-generic/preempt.h, line 18](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L18) |
|
||||
| init_task_preempt_count | 初始化task的抢占计数器为FORK_PREEMPT_COUNT | [include/asm-generic/preempt.h, line 26](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L26) |
|
||||
| init_idle_preempt_count | 初始化task的抢占计数器为PREEMPT_ENABLED | [include/asm-generic/preempt.h, line 30](http://lxr.free-electrons.com/source/include/asm-generic/preempt.h?v=4.6#L30) |
|
||||
| preempt_count_add | 将增加current的抢占计数器增加val | [include/linux/preempt.h, line 132](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L32) |
|
||||
| preempt_count_sub | 将增加current的抢占计数器减少val | [include/linux/preempt.h, line 133](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L133) |
|
||||
| preempt_count_dec_and_test | 将current的抢占计数器减少1, 然后看是否可以进程内核抢占, 即检查抢占计数器是否为0(允许抢占), 同时检查tif_need_resched标识是否为真 | [include/linux/preempt.h, line 134, 61](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L134) |
|
||||
| preempt_count_inc | current的抢占计数器增加1 | [include/linux/preempt.h, line 140](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L140) |
|
||||
| preempt_count_dec | current的抢占计数器减少1 | [include/linux/preempt.h, line 141](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L141) |
|
||||
|
||||
|
||||
|
||||
还有其他函数可用于开启和关闭内核抢占
|
||||
|
||||
| 函数 | 描述 | 定义 |
|
||||
| ------- |:-------:|:-------:|
|
||||
| preempt_disable | 通过preempt_count_inc来停用内核抢占, 并且通过路障barrier同步来避免编译器的优化 | [include/linux/preempt.h, line 145](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L145) |
|
||||
| preempt_enable | preempt_count_dec_and_test启用内核抢占, 然后通过__preempt_schedule检测是够有必要进行调度 | [include/linux/preempt.h, line 162](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L162) |
|
||||
| preempt_enable_no_resched | 开启抢占, 但是不进行重调度 | [include/linuxc/preempt.h, line 151](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L151) |
|
||||
| preempt_check_resched | 调用__preempt_schedule检测是够有必要进行调度 | [include/linux/preempt.h, line 176](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L176) |
|
||||
| should_resched | 检查current的抢占计数器是否为参数preempt_offset的值, 同时检查 tif_need_resched是否为真 | [include/linux/preempt.h, line 74](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L74) |
|
||||
| preemptible | 检查是否可以内核抢占, 检查抢占计数器是否为0, 以及是否停用了中断 | [/include/linux/preempt.h, line159](http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L159) |
|
||||
|
||||
###内核如何知道是否需要抢占?
|
||||
-------
|
||||
|
||||
首先必须设置了TLF_NEED_RESCHED标识来通知内核有进程在等待得到CPU时间, 然后会在判断抢占计数器preempt_count是否为0, 这个工作往往通过preempt_check_resched或者其相关来实现
|
||||
|
||||
在内核停用抢占后重新启用时, 检测是否有进程打算抢占当前执行的内核代码, 是一个比较好的时机, 如果是这样, 应该尽快完成, 则无需等待下一次对调度器的例行调用.
|
||||
|
||||
抢占机制中主要的函数是preempt_schedule, 设置了TIF_NEED_RESCHED标志并不能保证可以抢占内核, 内核可能处于临界区, 不能被干扰
|
||||
|
||||
```c
|
||||
// http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L3307
|
||||
|
||||
/*
|
||||
* this is the entry point to schedule() from in-kernel preemption
|
||||
* off of preempt_enable. Kernel preemptions off return from interrupt
|
||||
* occur there and call schedule directly.
|
||||
*/
|
||||
asmlinkage __visible void __sched notrace preempt_schedule(void)
|
||||
{
|
||||
/*
|
||||
* If there is a non-zero preempt_count or interrupts are disabled,
|
||||
* we do not want to preempt the current task. Just return..
|
||||
*/
|
||||
/* !preemptible() => preempt_count() != 0 || irqs_disabled()
|
||||
* 如果抢占计数器大于0, 那么抢占被停用, 该函数立即返回
|
||||
* 如果
|
||||
*/
|
||||
if (likely(!preemptible()))
|
||||
return;
|
||||
|
||||
preempt_schedule_common();
|
||||
}
|
||||
NOKPROBE_SYMBOL(preempt_schedule);
|
||||
EXPORT_SYMBOL(preempt_schedule);
|
||||
|
||||
// http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L159
|
||||
#define preemptible() (preempt_count() == 0 && !irqs_disabled())
|
||||
```
|
||||
|
||||
|
||||
>!preemptible => preempt_count() != 0 || irqs_disabled()表明
|
||||
|
||||
* 如果抢占计数器大于0, 那么抢占仍然是被停用的, 因此内核不能被打断, 该函数立即结束.
|
||||
|
||||
* 如果在某些重要的点上内核停用了硬件中断, 以保证一次性完成相关的处理, 那么抢占也是不可能的.irqs_disabled会检测是否停用了中断. 如果已经停用, 则内核不能被抢占
|
||||
|
||||
接着如果可以被抢占, 则执行如下步骤
|
||||
|
||||
```c
|
||||
|
||||
static void __sched notrace preempt_schedule_common(void)
|
||||
{
|
||||
do {
|
||||
/*
|
||||
preempt_disable_notrace定义在
|
||||
http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L198 等待于__preempt_count_inc();
|
||||
*/
|
||||
preempt_disable_notrace();
|
||||
/* 完成一次调度 */
|
||||
__schedule(true);
|
||||
|
||||
/*
|
||||
preempt_enable_no_resched_notrace
|
||||
http://lxr.free-electrons.com/source/include/linux/preempt.h?v=4.6#L204
|
||||
等价于preempt_enable_no_resched_notrace
|
||||
*/
|
||||
preempt_enable_no_resched_notrace();
|
||||
|
||||
/*
|
||||
* Check again in case we missed a preemption opportunity
|
||||
* between schedule and now.
|
||||
* 再次检查, 以免在__scheudle和当前点之间错过了抢占的时机
|
||||
*/
|
||||
} while (need_resched());
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user