一.进程调度
现代的操作系统是多道的,这必然涉及到进程的调度,调度需要许多的调度算法。
1.需要多种调度算法的理由:
- 不同的进程对于计算机的资源(CPU、IO等)的需求是不同的,比如有些需要频繁的IO,有些需要一直占CPU。那么他们之间的调度成为提升计算机效率的关键。
- 不同进程还可以分类成批处理进程,例如编译程序、科学计算等,这类进程特点是不需要和用户交互,而是直接在后台埋头苦干,还有一点就是不需要实时。还可以分类成实时进程,例如视频音频、机械控制等,特点是要及时稳定的响应。还可以分类为交互式进程,例如shell、文本编辑程序、图形应用程序等,特点就是经常和用户交互,因此会长时间的阻塞,不过响应还是要快。
基于这两点,linux对不同进程使用不同调度策略。调度策略就是操作系统从进程就绪队列中选一个使之获得CPU执行权的算法。那么操作系统具体是怎么做的呢?就是它根据某些信息由算法计算出一个值,这个值就代表了进程的优先级。
2.进程调度的时机:
一个进程调用schedule()函数会发生进程调度(理解:切换到另一个进程),该函数在内核态,而且不是系统调用,因此用户态程序无法主动调用它,只能被调度。 - 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();比如sleep后,立即会调用schedule。
- 内核线程(只有内核态没有用户态的特殊进程,虽然不会系统调用,但是仍然可以有时钟中断、IO中断等)可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
- 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。
3.进程的切换
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行;
进程上下文包含了进程执行需要的所有信息,包括: - 用户地址空间:�包括程序代码,数据,用户堆栈等
- 控制信息�:进程描述符,内核堆栈等
- 硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
context_switch(rq, prev, next);//进程上下文切换
switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程。使得当前进程的内核堆栈切换为下一个要执行进程的内核堆栈;然后切换到下一个进程的EIP。
二.简单跟踪 Linux 系统切换过程.
1.首先在schedule,pick_next_task,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,函数参数也并不是通过堆栈来传递,而是通过寄存器来传递。
四、实验总结:
通过实验可知schedule()函数用来选择一个新的进程来运行,并调用context_switch()进行上下文的切换,这个宏调用switch_to()来进行关键上下文切换,其中pick_next_task()函数封装了进程调度算法。中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。