进程切换过程之一:上下文切换

进程切换过程之一:上下文切换

注:下面给出的源码均出自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,方便后面通过指针的方式访问内存。

接下来的四条压栈指令,直接将要手动保存的寄存器压入栈中。

执行到这里,“老”进程和“新”进程对应的栈的示意图如下:

                                              

与之前旧版本的有些许不同,新版本的直接将上下文保存在进程自己的栈中;旧版本的保存在了其他的地方(暂时就理解到这个程度,由于没有分析过所有的源码,这里的分析可能比较片面,但这是我整个的分析过程,如有有大佬看出哪里不会,请指正)

发布了213 篇原创文章 · 获赞 116 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq2071114140/article/details/104089999