进程调度-CFS调度器-进程唤醒抢占...

This commit is contained in:
gatieme
2016-07-08 23:31:24 +08:00
parent 15cd7d41f7
commit 6e52c164b5
4 changed files with 110 additions and 12 deletions
@@ -313,3 +313,5 @@ struct cfs_rq {
http://www.360doc.com/content/15/1006/18/18252487_503643269.shtml
http://linuxperf.com/?p=42
@@ -35,21 +35,22 @@ CFS负责处理普通非实时进程, 这类进程是我们linux中最普遍的
周期调度的工作形式上sched_class调度器类的task_tick函数完成, CFS则对应task_tick_fair函数, 但实际上工作交给entity_tick完成.
##1.4 唤醒抢占
-------
当在try_to_wake_up和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当前进程可以抢占当前运行的进程. 请注意该过程不涉及核心调度器.
当在try_to_wake_up/wake_up_process和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当前进程可以抢占当前运行的进程. 请注意该过程不涉及核心调度器.
每个调度器类都因应该实现一个check_preempt_curr函数, 在全局check_preempt_curr中会调用进程其所属调度器类check_preempt_curr进行抢占检查, 对于完全公平调度器CFS处理的进程, 则对应由check_preempt_wakeup函数执行该策略.
新唤醒的进程不必一定由完全公平调度器处理, 如果新进程是一个实时进程, 则会立即请求调度, 因为实时进程优先极高, 实时进程总会抢占CFS进程.
#2 Linux进程的睡眠和唤醒
-------
#2 Linux进程的睡眠
在Linux中,仅等待CPU时间的进程称为就绪进程,它们被放置在一个运行队列中,一个就绪进程的状 态标志位为TASK_RUNNING. 一旦一个运行中的进程时间片用完, Linux 内核的调度器会剥夺这个进程对CPU的控制权, 并且从运行队列中选择一个合适的进程投入运行.
@@ -77,20 +78,99 @@ func1();
/* Rest of the code ... */
```
在第一个语句中, 程序存储了一份进程结构指针sleeping_task, current 是一个宏,它指向正在执行
的进程结构。set_current_state()将该进程的状态从执行状态TASK_RUNNING 变成睡眠状态
TASK_INTERRUPTIBLE。 如果schedule()是被一个状态为TASK_RUNNING 的进程调度,那么schedule()将调度另外一个进程占用CPU;如果schedule()是被一个状态为TASK_INTERRUPTIBLE 或TASK_UNINTERRUPTIBLE 的进程调度,那么还有一个附加的步骤将被执行:当前执行的进程在另外一个进程被调度之前会被从运行队列中移出,这将导致正在运行的那个进程进入睡眠,因为 它已经不在运行队列中了。
我们可以使用下面的这个函数将刚才那个进入睡眠的进程唤醒。
wake_up_process(sleeping_task);
在调用了wake_up_process()以后,这个睡眠进程的状态会被设置为TASK_RUNNING,而且调度器
会把它加入到运行队列中去。当然,这个进程只有在下次被调度器调度到的时候才能真正地投入运行。
在第一个语句中, 程序存储了一份进程结构指针sleeping_task, current 是一个宏,它指向正在执行的进程结构。set_current_state()将该进程的状态从执行状态TASK_RUNNING变成睡眠状态TASK_INTERRUPTIBLE.
* 如果schedule是被一个状态为TASK_RUNNING的进程调度,那么schedule将调度另外一个进程占用CPU;
* 如果schedule是被一个状态为TASK_INTERRUPTIBLE 或TASK_UNINTERRUPTIBLE 的进程调度,那么还有一个附加的步骤将被执行:当前执行的进程在另外一个进程被调度之前会被从运行队列中移出,这将导致正在运行的那个进程进入睡眠,因为它已经不在运行队列中了.
#3 linux进程的唤醒
-------
当在try_to_wake_up/wake_up_process和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当前进程可以抢占当前运行的进程. 请注意该过程不涉及核心调度器.
## wake_up_process
我们可以使用wake_up_process将刚才那个进入睡眠的进程唤醒, 该函数定义在[kernel/sched/core.c, line 2043](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L2043).
```c
int wake_up_process(struct task_struct *p)
{
return try_to_wake_up(p, TASK_NORMAL, 0);
}
```
在调用了wake_up_process以后, 这个睡眠进程的状态会被设置为TASK_RUNNING,而且调度器会把它加入到运行队列中去. 当然, 这个进程只有在下次被调度器调度到的时候才能真正地投入运行.
## try_to_wake_up
-------
try_to_wake_up函数通过把进程状态设置为TASK_RUNNING, 并把该进程插入本地CPU运行队列rq来达到唤醒睡眠和停止的进程的目的.
例如: 调用该函数唤醒等待队列中的进程, 或恢复执行等待信号的进程.
```c
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
```
该函数接受的参数有: 被唤醒进程的描述符指针(p), 可以被唤醒的进程状态掩码(state), 一个标志wake_flags,用来禁止被唤醒的进程抢占本地CPU上正在运行的进程.
try_to_wake_up函数定义在[kernel/sched/core.c, line 1906](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L1906)
## wake_up_new_task
-------
```c
void wake_up_new_task(struct task_struct *p)
```
该函数定义在[kernel/sched/core.c, line 2421
](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L2421
)
之前进入睡眠状态的可以通过try_to_wake_up和wake_up_process完成唤醒, 而我们fork新创建的进程在完成自己的创建工作后, 可以通过wake_up_new_task完成唤醒工作, 参见[Linux下进程的创建过程分析(_do_fork/do_fork详解)--Linux进程的管理与调度(八)](http://blog.csdn.net/gatieme/article/details/51569932)
使用fork创建进程的时候, 内核会调用_do_fork(早期内核对应do_fork)函数完成内核的创建, 其中在进程的信息创建完毕后, 就可以使用wake_up_new_task将进程唤醒并添加到就绪队列中等待调度. 代码参见[kernel/fork.c, line 1755](http://lxr.free-electrons.com/source/kernel/fork.c?v=4.6#L1755)
## check_preempt_curr
-------
当在try_to_wake_up和wake_up_new_task中唤醒进程时, 内核使用全局check_preempt_curr看看是否进程可以抢占当前进程可以抢占当前运行的进程.
函数定义在[kernel/sched/core.c, line 905](http://lxr.free-electrons.com/source/kernel/sched/core.c?v=4.6#L905)
```c
check_preempt_curr(rq, p, WF_FORK);
#ifdef CONFIG_SMP
if (p->sched_class->task_woken) {
/*
* Nothing relies on rq->lock after this, so its fine to
* drop it.
*/
lockdep_unpin_lock(&rq->lock);
p->sched_class->task_woken(rq, p);
lockdep_pin_lock(&rq->lock);
}
#endif
```
# 无效唤醒
-------
几乎在所有的情况下进程都会在检查了某些条件之后发现条件不满足才进入睡眠可是有的时候
进程却会在 判定条件为真后开始睡眠,如果这样的话进程就会无限期地休眠下去,这就是所谓的无效唤醒问题。在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。
几乎在所有的情况下, 进程都会在检查了某些条件之后, 发现条件不满足才进入睡眠. 可是有的时候进程却会在判定条件为真后开始睡眠, 如果这样的话进程就会无限期地休眠下去, 这就是所谓的无效唤醒问题。在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。
设想有两个进程A 和B,A 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链
表里面的数据进行一些操作,同时B进程也在往这个链表添加节点。当这个链表是空的时候,由于无数据可操作,这时A进程就进入睡眠,当B进程向链表里面添加了节点之后它就唤醒A 进程,其代码如下:
A进程:
@@ -169,6 +169,7 @@ dequeue_entity()
se->vruntime -= cfs_rq->min_vruntime;
task_fork_fair():
se->vruntime -= cfs_rq->min_vruntime;
switched_from_fair():
@@ -185,3 +186,16 @@ switched_from_fair():
加上min_vruntime的情形
```c
enqueue_entity:
// http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L3196
if (!(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING))
se->vruntime += cfs_rq->min_vruntime;
attach_task_cfs_rq:
// http://lxr.free-electrons.com/source/kernel/sched/fair.c?v=4.6#L8267
if (!vruntime_normalized(p))
se->vruntime += cfs_rq->min_vruntime;
```
@@ -31,3 +31,5 @@ http://blog.csdn.net/b02042236/article/details/6076473
http://www.tuicool.com/articles/MjyANr
http://iamzhongyong.iteye.com/blog/1895728
http://blog.csdn.net/xiaofei0859/article/details/8113211
http://blog.csdn.net/u201017971/article/details/50511511