2019/10/3

context switch 到底在做甚麼?

簡單的說,'context switch' 就是切換  'context'。而所謂的 'context',指的就是當下 CPU  registers 的內容。

因為當程式在執行時,所做的事情就是從 memory 中讀取指令和依照指令做計算。CPU 在做這兩件事情時,就是用 core registers (CPU 內部的 register) 來紀錄讀取到哪裡了,以及當作計算時的暫存區域。

當 CPU 做事做到一半,如果有事件發生了,而且這個事件的處理所用到的 core registers 會把目前的進度蓋掉的話,CPU 就會先需要把 core registers 目前的值先暫存到 memory 的某個地方,等 CPU 處理完事件之後,再把暫存的值讀回來,就可以從事件發生時中斷的地方在繼續處理。這就是所謂的 'context switch'。

因為 CPU 的設計都不太一樣,要知道有哪些 core registers 需要存起來,就要去看 CPU 廠商提供的文件。一般來說可能要保存 Program Counter (PC),stack pointer (SP),和一些計算時會用到的 registers。

以 ARM CM4 來說的話,當發生 exception 時,CM4 需要儲存下圖中的 core registers。R0~R3, R12, LR, PC, xPSR。這邊沒有儲存 SP,原因不明,不過有需要的話,也可以自己存起來。對 CM4 來說,因為 IRQ 也屬於一種 exception,所以 IRQ 觸發後,也會自動先儲存這些  core registers 到 stack 中。



需要 context switch 的時候


目前我知道會需要做 'context switch' 的場景有三種,發生 Exception,發生 IRQ,和 OS scheduling。

Exception and IRQ Context Switch


CM4 有幾種不同的 exception type,IRQ 就被歸類為其中一種。因為 Exception 和 IRQ 被視為是相同的場景,所以處理的手法也一樣。



處理的手法有個名稱 'Exception Entry',在 CM4 上很貼心的會在 exception 發生時自動地把一些 registers 存到 stack 中,這樣 programmer 就不用自己處理 exception 的 context switch 了。



至於 CM4 為什麼不是存全部的 registers 呢? 這就需要知道 caller register 和 callee register 的分別了。不同的 CPU 對這兩者的定義會不一樣,通常 CPU 會提供一份通稱 'ABI' 的文件來描述自家的 CPU 是如何使用 CPU register 的。

下圖就是 ARM ABI 對 core register 的定義。



文件中有一段重要的文字,間接說明了為什麼。



這段文字說明了,因為每個 subroutine (也就是進入一個 function 後),在 function 中 compiler 會在使用到這些 register 前就自動先保存起來 (push 進 stack),當 function 結束 return 後,原本的 caller 是不會察覺到這些 register 有被動過,也就是 'preserve the content'。

因為在 IRQ 的處理 function 中,也會有相同的行為,所以不用再特地去保存這些 register。不過需要注意的是,一定要是 C function 才有 compiler 的幫忙,若是 assembly 的話,就需要自己處理了。

OS scheduling Context Switch


接下來要說明的是由 OS scheduling 觸發的 context switch。這邊會以 FreeRTOS 當標的,因為我不懂 Linux...。

RTOS scheduler 排程的對象是 task,對每個 task 來說,CPU 就像是由自己獨享的資源,各個 task 之前不會預期有分享 CPU 這種假設。於是當 RTOS 在做 task context switch 時,所做的事情就是儲存所有的 core registers,跟前面提到的 exception 和 IRQ 不一樣的地方在這邊沒有在考慮 caller 和 callee register。

FreeRTOS context switch

直接看 FreeRTOS 的 code 如何處理 task context switch 就可以了,邏輯很好懂。

再換 task 之前,先把所有的 core registers 存到 task stack 中,再把目前 task 的 stack pointer 和 task control block (TCB) 也存進 task stack 裡,之後在選出下一個 task,選好後把 sp 換成新選好的 task 的 stack,在按照相反的順序把 task stack,TCB,以及 core registers 從 task stack 中拿出來用就完成了 task context switch。

以小寫字母標示的儲存內容,讀取順序以大寫字母表示。


參考資料

用 Google 找到的,跟 context switch 相關的內容。

How FreeRTOS Works
https://freertos.org/implementation/a00006.html
https://freertos.org/implementation/a00020.html

Definition of context switch for Linux
http://www.linfo.org/context_switch.html

FreeRTOS程式碼分析 -- 1
http://albert-oma.blogspot.com/2012/05/freertos-1.html

淺讀CuRT:task, context switch與scheduling
http://reborn2266.blogspot.com/2013/10/curttask-context-switchscheduling.html

沒有留言:

張貼留言