程 序 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
今天我们要学习的是Linux中进程调度与切换过程。有关进程的知识在前面的博客中已经提到了,有不懂的地方请参考我前面的博客,今天我直接从进程调度和切换开始讲。
Linux一个较大的优势就是进程调度,因为Linux是一个多进程系统,它怎么进行进程调度直接影响这个系统的性能,而Linux系统的一个优势就是它的系统在进程调度这里做的很好。
在讲进程调度前,我们先来看下有关Linux知识(以下4张图片摘自孟宁老师课件)。
图1.Linux内核架构
图2.Linux执行过程
图3.CPU执行指令角度
图4.从内存角度看待Linux系统执行
首先一起来看下Linux的进程的类型,一般将进程分为三种,一种为I/O消耗型进程,另一种是处理器消耗型进程,还有一种是混合型,也就是I/O消耗型进程和处理器消耗型进程混合在一起的。从他们的名字可以看出,这是以进程消耗资源的种类来进行分类的。
在Linux系统中,是按照什么规则来进行调度的呢?我们所知的有优先级调度,还有时间片调度。其中优先级指的是进程的优先级,而时间片则指的是进程所需要消耗的时间。
那么Linux系统中进程调度的过程到底是一个什么流程呢?主要是以下几个方面
1.从schedule()函数开始,进行调度选择
2.从CPU的值变化上,解读switch_to宏执行分析
3.到堆栈发生切换位置,在切换堆栈前后,current_thread_info变化
4.再到地址空间发生切换,解释地址空间的切换不会影响后续切换代码的执行
5.Current宏代表的进程发生变化的源码位置
6.任务状态段中关于内核堆栈的信息发生变化的源码位置
下面来详细的讲解一下各个环节
在Linux内核中,schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换。部分函数具体代码如下,一个调度新进程,一个是进行上下文切换,还有相关堆栈信息的保存。
next= pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
context_switch(rq,prev, next);//进程上下文切换
switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
Schedule:主要负责帮助系统选定下一个执行的进程
调度时机:
1.进程状态转换的时刻,进程终止、进程睡眠。
2.当前进程的时间片用完时。
3.设备驱动程序调用。
4.进程从中断、异常及系统调用返回到用户态时。
进程的从睡眠状态到唤醒状态,完成了一次进程的调度,中间有保存相应的进程信息,有相应的队列进行保存。
进程调度中还有一个现象是抢占,就是优先级高的进程进行抢占低优先级的执行机会,用户抢占发生在两种情况下,一个是从系统调用返回用户空间,另一个是从中断处理程序返回用户空间。
而内核抢占发生在:
1.当从中断处理程序正在执行,且返回内核空间之前;
2.当内核代码再一次具有可抢占性的时候;
3.如果内核中的任务显式调用;
4.内核中的任务被阻塞。
上下文的切换也是进程调用中一个比较重要的问题,其中有一个context_switch()函数完成以下工作,switch_mm()——该函数负责把虚拟内存从上一个进程映射切换到新进程中。而switch_to()——负责从上一个进程的处理器状态切换到新进程的处理器状态。切换的过程包括保存、恢复栈信息和寄存器信息。
其中各种进程之间的调度又有很多方法,主要有先进先出、时间片轮转等方式,这里就不具体分析,有兴趣的可以自行查阅相应的调度方法。
下面来看下具体实验过程:
图5.相关指令操作,创建文件
图6.文件内部修改处
图7.文件内部修改处
图8.调试启动
图9.设置断点
图10.查看context_switch处相关点代码
图11.中途的代码调试过程
总结:从上面可以看出,Linux系统的进程切换的一般执行过程是这样的,从进程X转向进程Y的过程是这样的。
1.正在运行的用户态进程X 。
2.发生中断——save cs:eip/esp/eflags(current)to kernel stack,then load cs:eip(entry of a specific ISR) and
ss:esp(point tokernel stack)。
3.SAVE_ALL//保存现场。
4.中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换。
5.标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)。
6.restore_all //恢复现场。
7.iret- pop cs:eip/ss:esp/eflags from kernel stack //恢复。
8.继续运行用户态进程Y
好了,从上面可以看到,整个Linux系统的进程切换的执行流程就是这个样子的!