进程切换过程详解

/*

注:在学习内核的时候有一个困难,那就是任何一个模块都不是孤立的,比如进程的调度就设计到中断处理、信号处理还有进程上下文的切换等等。作为一个初学者,想一下子把操作系统的整个运行过程都清晰地展现在脑海是不现实的。尽管如此,每个模块还是有它所关注的焦点,我们所采取的策略是把整个操作系统分为几个大模块,比如:进程的管理、内存管理、文件系统等等。然后把这些大模块进一步分解成一个个小模块,比如进程的管理可以细分为进程的创建、进程的切换、系统调用的处理、信号的处理等等。在分析每一个模块时,先把其他的模块抽象化,千万不要陷入其他模块的细节当中,,也可以说这是一种各个击破的方法,当你把每个小模块的功能搞清楚后,到最后整个操作系统的运行过程就很清晰了!

*/

 

 

 

在上一篇博客中,我们提到当一个任务从系统调用处理函数返回之前会检查是否需要进行进程切换。那么什么时候会发生进程的切换呢(任务调度)?当系统发生硬件中断、系统调用或者时钟中断时,就有可能发生进程的切换。 下面我们以时钟中断为例来看看进程的切换是如何进行的。

在此之前,我们先要做一个说明,由于我们并没有开始介绍进程的详细知识,对进程的详细介绍将放在进程的创建这一篇博客中(还没开始写O(∩_∩)O~),因此在这里我们先对进程做一个粗略的抽象:一个任务(就是进程)含有代码段、数据段、堆栈段,还有一个任务状态段TSS。这个任务状态段TSS记录当前任务的所有状态信息,包括寄存器、系统参数等等。TSS段的描述符放在TR寄存器中(也就是说访问TR就能访问当前任务的TSS段了)。

假设此刻CPU正在执行进程1,我们知道:系统有一个时钟频率,每隔一段时间就会发生一次时钟中断,这个时间段我们称为一个滴答。假设经过了一个滴答,系统发生时钟中断,此时时钟中断处理程序就会被自动调用(timer_interrupt),timer_interrupt定义在kernel/System_call.s中,如下图所示:

技术分享

同我们上一篇讲的_system_call一样,它首先会执行一些保护现场的工作,接着在第189行代码中把_jiffies的值加1(_jiffies表示自系统启动以来经过的滴答数),接下来第192-194的代码将执行此次时钟中断的特权级CPL压入堆栈,用来作为后面do_timer的参数,接下来开始执行do_timer,do_timer函数定义在Kernel/Sched.c中,这个函数的主要作用是将当前进程的用户态执行时间或内核态执行时间加1,然后将当前进程的剩余时间片减1.

技术分享

技术分享

如果当前进程的时间片还有剩余,那么直接return返回继续执行,接下来判断当前任务的CPL是否是0,如果是0,说明当前任务是在内核态被中断的,而Linux0.11中内核态是不能被抢占的,所以直接返回执行,如果不是0,则执行进程调度程序schedule()。接下来我们来分析schedule()这个函数,schedule函数同样定义在Sched.c中,它里面包含下面两段代码:

技术分享

这段代码的作用是:遍历任务数组,检查它们的报警定时值,如果该值小于jiffies,说明该任务的alarm时间已经过了,那么就在它的信号位图中置SIGALRM信号,表示向任务发送SIGALARM信号,然后将alarm清零,接下来检查是不是还有别的未被阻塞的信号,如果有并且当前的进程状态是可以被打断的,那么把这个任务置为就绪态。

技术分享

第124-142行的代码重新遍历整个任务数组,找出任务状态处于TASK_RUNING并且时间片最长的那个任务。并调用swith_to()函数切换到那个任务。swith_to函数定义在include/Linux/Sched.h中。

技术分享

switch_to是一段汇编代码,下面来解释一下这段代码的含义:首先检查要切换的任务是不是当前任务,如果是则直接退出。接下来把任务n(要切换去的任务)的TSS段放到_tmp.b中,然后把任务n放入_current中,把当前任务放入%ecx中切换出来,然后执行一个长跳转到*&_tmp的位置(这是新任务的TSS地址处),此时CPU会把所有寄存器的内容保存到当前任务TR执行的TSS段中,然后把新任务的TSS段中的寄存器信息恢复到CPU的各个寄存器中,这样系统就正式开始执行新的任务了。第178-180的代码是判断原任务是否使用过协处理器,如果没有则直接结束。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。