xv6進程切換-swtch函數


https://blog.csdn.net/Swartz2015/article/details/61615603

xv6進程切換-swtch函數

進程切換中由於需要保存當前進程的寄存器狀態信息,又要將新進程記錄的寄存器狀態信息加載到寄存器,因此涉及到許多棧的操作,堆棧間的來回切換,容易讓人眼花繚亂,難以理解。本文試圖分析以下xv6中的進程切換過程。

當前進程通過調用yield函數,進行進程切換。yield函數調用sched函數,sched函數啟動swtch函數完成進程切換。整個流程是這樣的:

yield->sched->swtch
  • 1

在sched函數中,可以看到,當前進程總是先切換到當前cpu的scheduler切換器:

//void sched(void)
swtch(&proc->context, cpu->scheduler);
  • 1
  • 2

切換器是一個死循環,該循環不斷在進程表中掃描,選擇一個RUNNABLE的進程調度,即從scheduler切換器轉換到新選擇的進程:

//scheduler切換器 void scheduler(void) { struct proc *p; for(;;){ // Enable interrupts on this processor. sti(); // Loop over process table looking for process to run. acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->state != RUNNABLE) continue; // Switch to chosen process. It is the process's job // to release ptable.lock and then reacquire it // before jumping back to us. proc = p; switchuvm(p); p->state = RUNNING; swtch(&cpu->scheduler, proc->context); switchkvm(); // Process is done running for now. // It should have changed its p->state before coming back. proc = 0; } release(&ptable.lock); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

顯然,swtch函數是重點。swtch函數的原型是:

void swtch(struct context **old, struct context *new);
  • 1

它的工作主要包括:1. 保存當前(old)進程的上下文。 2. 加載新進程(new)的上下文到機器寄存器中。

contex中其實就是幾個寄存器變量,用來保存這些寄存器的值。

swtch的函數代碼如下:

# Context switch # # void swtch(struct context **old, struct context *new); # # Save current register context in old # and then load register context from new. .globl swtch swtch: movl 4(%esp), %eax movl 8(%esp), %edx # Save old callee-save registers pushl %ebp pushl %ebx pushl %esi pushl %edi # Switch stacks movl %esp, (%eax) movl %edx, %esp # Load new callee-save registers popl %edi popl %esi popl %ebx popl %ebp ret 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

下面分析以下swtch函數的執行過程。


swtch函數的執行過程

當任意進程調用swtch函數時,該進程的堆棧是下面這個樣子的:


這里寫圖片描述

其中棧中每個存儲區域都是4個字節。這里需要提一下,在執行指令call時,當前指令的下一條指令的地址將會push到棧中,也就是上面看到的eip,這樣就可以保證在函數返回時,可以回到函數調用的下一條指令的地方。然后swtch開始執行,

movl 4(%esp), %eax movl 8(%esp), %edx
  • 1
  • 2

所以寄存器eax和edx都會指向如下圖所示的位置:


這里寫圖片描述

接下來會依次將有關上下文信息的寄存器push到棧中
  pushl %ebp pushl %ebx pushl %esi pushl %edi
  • 1
  • 2
  • 3
  • 4

push完之后,就可以堆棧布局就變成下圖所示:


這里寫圖片描述

對比contex和當前的棧布局,可以直觀看到,此時在棧上存儲的剛好是一個contex。當前的棧頂esp剛好指向當前進程的contex中的第一個元素的地址,所以這里可以說contex中包括了棧信息和寄存器信息,不僅可以用它來進行棧切換,還可以通過它恢復寄存器。因此可以想到,下次重新調度這個進程時,只需要直接將棧上的信息彈到相應寄存器中就可以了。而這個棧頂指針,我們可以直接用proc->contex指針記錄就可以了。即

proc->contex = esp;
  • 1

我們繼續看swtch函數:

# Switch stacks movl %esp, (%eax) movl %edx, %esp
  • 1
  • 2
  • 3

根據注釋也可以看到,這兩行主要是用於棧的切換。前面我們知道,eax中存儲的是指向old_proc->contex的指針,old_proc->contex是指向當前進程的contex的指針,所以movl %esp, (%eax),相當於:

old_proc->contex = esp;
  • 1

也就是讓當前進程的contex指針指向棧頂。根據前面的示意圖我們可以理解原理。

到此為止,舊進程的contex保存工作已經完成了。

下面的工作自然是怎么把新進程的contex彈出到對應的寄存器中。其實保存過程和彈出恢復過程是對稱的

從保存過程中,我們知道新進程的contex信息總是可以通過new_proc->contex獲得,因為new_proc->contex指向了new_proc內核棧的棧頂,而棧頂依次保存着上下文寄存器信息所以將new_proc->contex賦值到esp就完成了堆棧切換,再依次pop就可以恢復上下文寄存器信息。但是我們怎么獲得new_proc的contex呢?回到前面,我們看到,就進程在調用swtch時,就把new_proc的contex放在了堆棧中,並且根據前面edx=new_proc->contex。所以堆棧切換就是把

esp= edx;
  • 1

然后

popl %edi popl %esi popl %ebx popl %ebp
  • 1
  • 2
  • 3
  • 4

就可以依次將new_contex中保存的寄存器值依次彈回到對應寄存器中了。

最后的

ret
  • 1

指令將eip彈回。所以新進程會從它上次調用swtch函數的下一條指令開始執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM