进程切换过程之一:上下文切换
注:下面给出的源码均出自https://github.com/mit-pdos/xv6-public
在进程进行切换时,需要进行上下文切换,也就是寄存器的值的保存。
对于上下文的切换,有两种情况:硬件自动(隐式)完成、操作系统显式保存。
下面分析两段xv6的源代码,都是上下文切换部分,版本有所不同:
注:每个进程都有一个用来维护过程调用的栈,有的书把这个栈叫做内核栈;在编译原理中,这个栈的术语叫活动记录,在下文中,提到的栈都是说的这个保存活动记录的栈。每个进程都有自己的活动记录(当然,如果是多CPU的情况下,就会引入线程的概念,则每个线程有一个活动记录)
较老的版本
# void swtch(struct context *old, struct context *new);
#
# Save current register context in old
# and then load register context from new.
.globl swtch
swtch:
# Save old registers
movl 4(%esp), %eax
popl 0(%eax) # %eip
movl %esp, 4(%eax)
movl %ebx, 8(%eax)
movl %ecx, 12(%eax)
movl %edx, 16(%eax)
movl %esi, 20(%eax)
movl %edi, 24(%eax)
movl %ebp, 28(%eax)
# Load new registers
movl 4(%esp), %eax # not 8(%esp) - popped return address above
movl 28(%eax), %ebp
movl 24(%eax), %edi
movl 20(%eax), %esi
movl 16(%eax), %edx
movl 12(%eax), %ecx
movl 8(%eax), %ebx
movl 4(%eax), %esp
pushl 0(%eax) # %eip
这个版本的上下文结构struct context的内容如下:
struct context {
int eip; //程序计数器
int esp; //栈指针
int ebp;
int ecx;
int edx;
int esi;
int edi;
int ebp;
};
分析一下上下文切换的源代码:
源代码的上半段是将“老”的进程的上下文保存,下半段将“新”的进程(要切换到的)的上下文恢复。
执行swtch的指令时,栈的状态:
movl 4(%esp), %eax
将old(参数)的值放入寄存器%eax。
popl 0(%eax)
然后将栈顶的值保存到0(%eax)的位置。也就是将返回地址(%eip)的值保存到old指向的context空间中
之后,就是将其他的寄存器放入old指向的context空间中。
通过上面的源码,可以得到context在内存中分配空间的方式:
下面的代码就是从new指向的context空间中,将各个寄存器的值恢复,就不详细说明了。
较新的版本:就像一开始提到的,硬件会自动保存一部分
硬件会保存一部分的寄存器,还有剩余的需要进行手动保存。
# Context switch
#
# void swtch(struct context **old, struct context *new);
#
# Save the current registers on the stack, creating
# a struct context, and save its address in *old.
# Switch stacks to new and pop previously-saved registers.
.globl swtch
swtch:
movl 4(%esp), %eax
movl 8(%esp), %edx
# Save old callee-saved registers
pushl %ebp
pushl %ebx
pushl %esi
pushl %edi
# Switch stacks
movl %esp, (%eax)
movl %edx, %esp
# Load new callee-saved registers
popl %edi
popl %esi
popl %ebx
popl %ebp
ret
开始的两条代码,将old与new放入%eax和%edx,方便后面通过指针的方式访问内存。
接下来的四条压栈指令,直接将要手动保存的寄存器压入栈中。
执行到这里,“老”进程和“新”进程对应的栈的示意图如下:
与之前旧版本的有些许不同,新版本的直接将上下文保存在进程自己的栈中;旧版本的保存在了其他的地方(暂时就理解到这个程度,由于没有分析过所有的源码,这里的分析可能比较片面,但这是我整个的分析过程,如有有大佬看出哪里不会,请指正)