理解进程调度时机跟踪分析进程调度与进程切换的过程

李洋 原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

这一次实验是针对linux系统中进程调度时机得深入理解。

Linux 调度器将进程分为三类:

1. 交互式进程

2. 批处理进程

3. 实时进程

 根据进程的不同分类 Linux 采用不同的调度策略。对于实时进程,采用 FIFO 或者 Round Robin 的调度策略。对于普通进程,则需要区分交互式和批处理式的不同。传统 Linux 调度器提高交互式应用的优先级,使得它们能更快地被调度。

调度的发生主要有两种方式:

1:主动式调度(自愿调度)

在内核中主动直接调用进程调度函数schedule(),当进程需要等待资源而暂时停止运行时,会把状态置于挂起(睡眠),并主动请求调度,让出cpu。

2:被动式调度(抢占式调度、强制调度)

用户抢占和内核抢占

 (1)用户抢占发生在:从系统调用返回用户空间和从中断处理程序返回用户空间。

 (2)内核抢占:在不支持内核抢占的系统中,进程/线程一旦运行于内核空间,就可以一直执行,直到它主动放弃或时间片耗尽为止。这样一些非常紧急的进程或线程将长时间得不到运行。

实验中具体针对schedule()函数进行分析:它具体包括了三个过程:

下面通过具体实验截图来验证这个过程:

首先是启动内核并进行调试:

技术分享

然后是schedule()函数和__schedule()函数:

技术分享

再者是context_switch():

技术分享

最后是switch_to宏定义,可以通过汇编指令观察对于寄存器状态得保存:

技术分享

下面具体分析一下switch_to得代码:

首先简单提一下这个宏和函数的被调用关系:

schedule() --> context_switch() --> switch_to --> __switch_to()

这里面,schedule是主调度函数,涉及到一些调度算法,这里不讨论。当schedule()需要暂停A进程的执行而继续B进程的执行时,就发生了进程之间的切换。进程切换主要有两部分:1、切换全局页表项;2、切换内核堆栈和硬件上下文。这个切换工作由context_switch()完成。其中switch_to和__switch_to()主要完成第二部分。更详细的,__switch_to()主要完成硬件上下文切换,switch_to主要完成内核堆栈切换。

阅读switch_to时请注意:这是一个宏,不是函数,它的参数prev, next, last不是值拷贝,而是它的调用者context_switch()的局部变量。局部变量是通过%ebp寄存器来索引的,也就是通过n(%ebp),n是编译时决定的,在不同的进程的同一段代码中,同一局部变量的n是相同的。在switch_to中,发生了堆栈的切换,即ebp发生了改变,所以要格外留意在任一时刻的局部变量属于哪一个进程。关于__switch_to()这个函数的调用,并不是通过普通的call来实现,而是直接jmp,函数参数也并不是通过堆栈来传递,而是通过寄存器来传递。

最后需要注意得是:

这些代码是所有进程共用的,代码本身不属于某一个特定的进程,所以判定当前在哪一个进程不是通过看执行的代码是哪个进程的,而是通过esp指向哪个进程的堆栈来判定的。所以,对于上面图中的切换点也可以这样理解,在这一点处,esp指向了其它进程的堆栈,当前进程即被挂起,等待若干时间,当esp指针再次指回这个进程的堆栈时,这个进程又重新开始运行。

 

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