上下文切换分为两种情况
-
用户程序陷入到内核,再从内核返回
-
两个应用程序之间的上下文切换
用户程序陷入到内核
用户程序陷入到内核通过中断INT指令,在xv6中系统调用的号为64
操作系统在初始化的时候会建立IDT表以及GDT表
通过INT找到IDT中的项,通过IDT中的项找到GDT中的项,最后定位到代码。
在执行陷入指令的时候首先会到trapasm.S中的alltraps中,将trapframe存入到应用程序的内核栈中。将esp的地址存入栈中作为trap函数的输入,然后调用trap。注意trapframe中有的字段会自动存入,比如int指令会通过CPU存入ss,esp,eflags,cs,eip,errorcode,trapno。系统调用号存在寄存器eax中。应用程序在初始化的时候操作系统会为每个应用程序保留一个内核栈,其中TSS断记录内核栈的一些信息比如内核栈的入口地址esp0。
系统调用将返回值存入到寄存器eax中。返回的时候通过trapret将原来的状态返回。最后一条指令iret回到用户程序。
两个应用程序之间的上下文切换
两个应用程序不能直接进行上下文切换,必须通过内核进行上下文切换。上下文切换通过proc中的数据结构context进行的。上下文切换的过程首先从 shell用户栈->shell程序的内核栈->scheduler的内核栈->cat的内核栈->cat的用户程序。记住第一运行的程序为shell
由于shell没有返回值,因此程序会卡在红色线的地方。注意在shell中调用fork会释放ptable.lock锁
CPU在每一个时钟中断的时候会调用
yield中会调用
注意此时锁已经释放了,
让出当前进程的CPU资源。将上下文切换到scheduler的上下文,由于上下文切换了因此下一条指令跳转到了scheduler的swtch下一条指令(还记得上面提到的卡在scheduler中的swtch,因此已经将swtch的返回eip地址压入栈中)。然后通过for循环找要runable的进程进行切换cpu->scheduler到要切换的进程上下文切换,到此就可以运行要执行的进程了。
应用程序第一次创建的时候,在返回值设置为trapret。context指向forkret因此在scheduler第一次swtch到forkret函数,在forkret函数中调用执行结束后执行trapret进行从内核栈返回到用户栈。