進程切換過程之一:上下文切換

進程切換過程之一:上下文切換

注:下面給出的源碼均出自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,方便後面通過指針的方式訪問內存。

接下來的四條壓棧指令,直接將要手動保存的寄存器壓入棧中。

執行到這裏,“老”進程和“新”進程對應的棧的示意圖如下:

                                              

與之前舊版本的有些許不同,新版本的直接將上下文保存在進程自己的棧中;舊版本的保存在了其他的地方(暫時就理解到這個程度,由於沒有分析過所有的源碼,這裏的分析可能比較片面,但這是我整個的分析過程,如有有大佬看出哪裏不會,請指正)

發佈了221 篇原創文章 · 獲贊 116 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章