齐昱博 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
1、课上总结
(1)进程的第一种分类:不同类型的进程有不同的调度需求(如有i/o密集型和cpu密集型)。
(2)进程的第二种分类:批处理进程、实时进程、交互式进程。
(3)调度策略:是一组规则,决定什么时候以什么样的方式选择一个新进程运行。
(4)Linux的进程根据优先级排队,优先级是动态的。
(5) schedule函数实现进程调度,在运行队列中找到一个进程把cpu分配给它,调用方法:直接调用schedule()和松散调用,调用方法的选择根据need_resched标记。
(6)进程调度时机:1、中断处理过程(时钟中断、I/O中断、系统调用和异常)中,直接调用schedule()或者返回用户态时松散调用。2、内核线程可以直接调用schedule()进行切换,也可以在中断处理过程中进行调度。3、用户态进程无法实现主动调度,只能在中断处理过程中进行调度。
(7)挂起正在cpu上执行的进程,与中断时保存现场时不同的,中断前后是在同一个进程上线文中,只是由用户态转向内核态执行。
(8)进程上下文包含进程执行需要的所有信息:用户地址空间(包括程序代码,数据,用户堆栈等)、控制信息(进程描述符,内核堆栈等)、硬件上下文。
(9)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 to kernel 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
几种特殊情况
•通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
•内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
•创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
•加载一个新的可执行程序后返回到用户态的情况,如execve;
ch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
2、实验内容
和前几次实验一样首先使用老师提供的menuOS,使用gdb跟踪schedule()代码如下:
下面具体分析一下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,函数参数也并不是通过堆栈来传递,而是通过寄存器来传递。