再次认识进程切换
最后更新于
最后更新于
我们在第四章已经简单了解过了在内核启动过程中进程切换的过程,在这一节我们再来重新回顾一下这些内容,并且深入讨论下其中的几个细节。
首先我们需要考虑的是,什么时候可以进行进程的切换。这里可以主要分为下面几种情况:
进程主动放弃当前的CPU资源,比如显式调用wait
或sleep
通知操作系统当前进程需要等待
进程想要获取的资源当前不可用,比如尝试获得未被释放的锁,或进行磁盘操作的时候
进程由于外部中断被打断进入内核态,内核发现某些条件满足(比如当前进程时间片用尽),进行进程切换
在我们实现的ucore中,内核进程是不可抢占的。这也就意味着当内核执行的时候,另一个内核进程不可以夺走它的的CPU资源。但这是不是就意味着一个内核进程执行的时候,它就会一直执行到结束呢?虽说内核进程不可以抢占,但是它可以主动放弃自己占有的CPU资源。如果不这样设计的话,内核当中很有可能出现各种死锁导致内核崩溃。
另一方面,内核不能相信用户进程不会无限执行下去,所以需要提供手段在用户进程执行的时候打断他,比如时钟中断。在ucore的实现中,用户进程可以随时被打断进入内核,操作系统会检查当前进程是否需要调度,从而把运行的机会交给别的进程。
由于内核进程是不可抢占的,所以我们在内核中有许多地方使用了显式的函数调用来进行调度,主要有以下几个地方:
当用户进程A发生中断或系统调用之后,首先其中断帧会被保存,CPU进入内核态,执行中断处理函数。在执行完毕中断处理函数后,操作系统检查当前进程是否需要调度,如果需要,就把当前的进程状态保存,switch到另一个进程B中。注意在执行上面的操作的时候,进程A处于内核态,类似的,调度后我们到达的是进程B的内核态。进程B从系统调用中返回,继续执行。如果进程B在中断或系统调用中被调度,控制权可能转交给进程A的内核态,这样进程A从内核态返回后就可以继续执行之前的代码了。
下一小节,我们来看一看ucore的调度器框架以及一些简单的调度算法。
函数
原因
proc.c/do_exit
用户进程退出,放弃CPU资源
proc.c/do_wait
用户进程等待,放弃CPU资源
proc.c/init_main
init
线程会等待所有的用户线程执行完毕,之后调用kswapd
内核线程回收内存资源
proc.c/cpu_idle
idle
线程等待处于就绪态的线程,如果有就调用schedule
sync.c/lock
如果获取锁失败就进入等待
trap.c/trap
用户进程在被中断打断后,内核会检查是否需要调度,如果是则调用调度器进行调度