diff --git a/study/kernel/01-process/02-create/06-thread_info/images/20140506.jpg b/study/kernel/01-process/02-create/06-thread_info/images/20140506.jpg new file mode 100644 index 0000000..7f29581 Binary files /dev/null and b/study/kernel/01-process/02-create/06-thread_info/images/20140506.jpg differ diff --git a/study/kernel/01-process/05-schedule/06-main_scheduler/README.md b/study/kernel/01-process/05-schedule/06-main_scheduler/README.md index ab0d942..bab9231 100644 --- a/study/kernel/01-process/05-schedule/06-main_scheduler/README.md +++ b/study/kernel/01-process/05-schedule/06-main_scheduler/README.md @@ -581,8 +581,41 @@ rev->sched_class == class && rq->nr_running == rq->cfs.h_nr_running ##context_switch进程上下文切换 ------- + +**进程上下文切换** + + +**上下文切换**(有时也称做**进程切换**或**任务切换**)是指CPU从一个进程或线程切换到另一个进程或线程 + +稍微详细描述一下,上下文切换可以认为是内核(操作系统的核心)在 CPU 上对于进程(包括线程)进行以下的活动: + +1. 挂起一个进程,将这个进程在 CPU 中的状态(上下文)存储于内存中的某处, + +2. 在内存中检索下一个进程的上下文并将其在 CPU 的寄存器中恢复 + +3. 跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程 + + +上下文是指某一时间点CPU寄存器和程序计数器的内容, 广义上还包括内存中进程的弟子映射信息 + +上下文切换只能发生在内核态中, 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 +Linux相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少. + +context_switch函数完成了进程上下文的切换, 其定义在[kernel/sched/core.c#L2715](http://lxr.free-electrons.com/source/kernel/sched/core.c#L2715), http://abcdxyzk.github.io/blog/2014/05/22/kernel-sched-tick/ +执行如下操作 + +* 调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中 + +* 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息 + +context_switch( )函数建立next进程的地址空间。进程描述符的active_mm字段指向进程所使用的内存描述符,而mm字段指向进程所拥有的内存描述符。对于一般的进程,这两个字段有相同的地址,但是,内核线程没有它自己的地址空间而且它的 mm字段总是被设置为 NULL + +context_switch( )函数保证:如果next是一个内核线程, 它使用prev所使用的地址空间 + +进程切换并不是我们今天的重点, 我们将在后面的章节中着重讲解 + ##need_resched与TIF_NEED_RESCHED标识 ------- diff --git a/study/kernel/01-process/05-schedule/06-main_scheduler/context_switch.c b/study/kernel/01-process/05-schedule/06-main_scheduler/context_switch.c new file mode 100644 index 0000000..04c3ea2 --- /dev/null +++ b/study/kernel/01-process/05-schedule/06-main_scheduler/context_switch.c @@ -0,0 +1,62 @@ +/* + * context_switch - switch to the new MM and the new thread's register state. + */ +static __always_inline struct rq * +context_switch(struct rq *rq, struct task_struct *prev, + struct task_struct *next) +{ + struct mm_struct *mm, *oldmm; + + prepare_task_switch(rq, prev, next); + + mm = next->mm; + oldmm = prev->active_mm; + /* + * For paravirt, this is coupled with an exit in switch_to to + * combine the page table reload and the switch backend into + * one hypercall. + */ + arch_start_context_switch(prev); + + if (!mm) { + next->active_mm = oldmm; + atomic_inc(&oldmm->mm_count); + enter_lazy_tlb(oldmm, next); + } else + switch_mm(oldmm, mm, next); + + if (!prev->mm) { + prev->active_mm = NULL; + rq->prev_mm = oldmm; + } + /* + * Since the runqueue lock will be released by the next + * task (which is an invalid locking op but in the case + * of the scheduler it's an obvious special-case), so we + * do an early lockdep release here: + */ + lockdep_unpin_lock(&rq->lock); + spin_release(&rq->lock.dep_map, 1, _THIS_IP_); + + /* Here we just switch the register state and the stack. */ + switch_to(prev, next, prev); + barrier(); + + return finish_task_switch(prev); +} + +/* + * nr_running and nr_context_switches: + * + * externally visible scheduler statistics: current number of runnable + * threads, total number of context switches performed since bootup. + */ +unsigned long nr_running(void) +{ + unsigned long i, sum = 0; + + for_each_online_cpu(i) + sum += cpu_rq(i)->nr_running; + + return sum; +} \ No newline at end of file diff --git a/study/kernel/01-process/05-schedule/06-main_scheduler/pick_next_task.c b/study/kernel/01-process/05-schedule/06-main_scheduler/pick_next_task.c index db4bf25..3fd78d2 100644 --- a/study/kernel/01-process/05-schedule/06-main_scheduler/pick_next_task.c +++ b/study/kernel/01-process/05-schedule/06-main_scheduler/pick_next_task.c @@ -25,8 +25,8 @@ pick_next_task(struct rq *rq, struct task_struct *prev) { /* 调用cfs的选择函数pick_next_task找到最优的那个进程p*/ p = fair_sched_class.pick_next_task(rq, prev); - /* #define RETRY_TASK ((void *)-1UL)没有找到合适的进程 */ - if (unlikely(p == RETRY_TASK)) + /* #define RETRY_TASK ((void *)-1UL)有被其他调度气找到合适的进程 */ + if (unlikely(p == RETRY_TASK)) goto again; /* 则遍历所有的调度器类找到最优的进程 */ /* assumes fair_sched_class->next == idle_sched_class */