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

2019/6/3

Transform hex to decimal in excel


col\row   | 0    | 1   |
------------------------
       A  | 0x123| 291 |

From 0x123 to 291

step 1. remove "0x" : =RIGHT(A0, LEN(A0)-2)
step 2. use "HEXTODEC" : =HEXTODEC(RIGHT(A0, LEN(A0)-2))

From 291 to 0x123

step 1. use "DECTOHEX" : =DECTOHEX(A1)
step 2. add prefix "0x": =CONCATENATE("0x", DECTOHEX(A1))

2019/4/2

Transform recursive make to non-recursive make

工作上有個 repo 使用 recursive make 來 build,剛接手時因為不熟悉,就先照著他的流程使用。使用的過程中發現他有個缺點,需要在每個單獨 build 的 makefile 中寫出所有需要的 include path。照 GNU make 的說明是有方法可以把參數傳進 sub makefile 中的,不過這邊沒有這樣用,這邊使用的方式是把建立 parent path 的 makefile include 進所有的 sub makefile,在依照各個 lib 的需要自己處理 header path。這樣的做法會在各 sub makefile 中發現很多重複的 header path 設定。感覺很阿雜。

最近終於有點時間,project 沒有這麼趕了,就打算把這個問題修一下,之前有考慮過就傳進去就好,但是因為也想要可以使用 -j 來加速,以及改進下 output 的目錄結構來減少一些 maintain multiple core debugger script 的工作,就想改成 non-recursive 的結構順便練一下功。

主要碰到的問題有幾個,
  1. How to collect compile options in sub makefile?
  2. How to collect sources/headers in sub-directories?
  3. How to keep original build method? (build sub-dir as lib, then link them all)
在改的過程中有看到幾篇文章很有幫助,
[1] http://make.mad-scientist.net/papers/how-not-to-use-vpath/
[2] http://make.mad-scientist.net/papers/multi-architecture-builds/#single
[3] https://www.oreilly.com/library/view/managing-projects-with/0596006101/ch06.html#nonrecursive_make
[4] https://www.oreilly.com/library/view/managing-projects-with/0596006101/ch07.html#managing_programs_and_files

問題 1 & 2 的動作有兩個,一個是如何找到 makefile ,另一個是如何記住 makefile 中的資訊。

找檔案的手法有很多,這次是採用 wildcard 的方式,因為已經知道是在哪些目錄下了,這樣找也挺方便的。


再來就是如何記住,參考 [3] 的方法,這邊的重點是要記得先宣告參數,


之後在 sub makefile 中就可以把 source 或者是 compile option 累加進 var 中,


最後問題 3 的需求,主要的問題是如何跟 make 說有哪些 lib 要 build。這邊因為我是每個 sub makefile 都是一個 lib,所以就可以在每個 sub makefile 中加入下面的 rule,這樣每當 sub makefile 被 include 時,就會為每個 sub makefile 建立這個 rule 來 build 出我要的 lib。

2019/3/27

How Make do parallel build?

最近才知道 Make 有 "-j4" 這種可以做平行編譯的指令。再驚訝之餘,問題就來了,平常 compile 時都是一個個 source 在做,到底這個指令下去後,Make 是怎麼做的呢?想了解這後面的原理,感覺很厲害。

翻開 GNU Make manual,5.4 Parallel Execution 中說 make 就是知道如何平行的執行 makefile 中的 "recipe"
GNU make knows how to execute several recipes at once. Normally, make will execute only one recipe at a time, waiting for it to finish before executing the next. However, the ‘-j’ or ‘--jobs’ option tells make to execute many recipes simultaneously.

光這樣子看還是不太清楚這是在說什麼,從這個文章(https://www.cmcrossroads.com/article/pitfalls-and-benefits-gnu-make-parallelization)看就比較清楚,就是說 Make 可以同時執行多個彼此間沒有相依關係的動作。

Make 的基本形式中,"recipe" 是我們實際要讓 Make 做的動作。通常一個 target 的動作是有先後關係的,不過因為一般 makefile 中存在不只一個 target,不同 target 間就不一定有相依關係,這時 Make 就可以同時去執行這些不相依的 target 裡的動作。
target … : prerequisites …
        recipe
        …
        …





2018/6/10

Simple CM4 assembly example

最近有接觸一些 CM4 assembly,發現有些地方一開始不知道的話,很容易造成起步的困擾。

一個簡單的 as file
然後這個小範例裡就藏了幾個要注意的點。
line 1: 必須要有這行。沒有的話 compiler 會跳出奇怪的訊息,而且看不出來要加這一行。 像我碰到的就是下面這些 ,
Error: unshifted register required
Error: Thumb does not support conditional execution
Error: instruction not allowed in IT block
line 3: Must. 告訴 assembler 要用 Thumb ISA

line 6: Must. 告訴 linker 這個 label 是個 funciton,用到這個symbol 的地方都要自動使用 thumb mode,也就是要把 bit 0 設定成 1。不然就會碰到當呼叫這個 function 時就產生 Usage fault。當然,如果這不是個 function ,只是一個變數的 lable,就不用加這個了。
https://e2e.ti.com/support/microcontrollers/stellaris_arm/f/471/t/44006?Q-bit-0-of-reset-vector-must-be-1-

line 11~14: 假設想跳到某個指定的位址時,若是使用 register 來存目標位址,就必須自己assert bit 0。原因跟上一個一樣,也是很常碰到的問題。

ARM directives list
https://sourceware.org/binutils/docs/as/ARM-Directives.html

2018/6/8

CM4 usage fault when using printf

最近碰到一個狀況,當呼叫 printf 時,如果印幾個字都沒問題,但當用%d 時,就會出現 Usage fault。

程式本身很簡單,就一行

printf("%d\n", __LINE__);

就這樣,跑進去 printf 後就會跑出 Usage fault。從出問題的地方看,是 PC 跑到奇怪的地方了,讀回來的值也很怪。

想了好久,後來覺得就算真找出流程了,也很難改,況且只會在某個 task 中出錯,其他都沒有,這也太奇怪了。想來想去,只能懷疑是不是 toolchain library 用錯了?剛好就Google 到這篇,可以嘗試一下。

https://stackoverflow.com/questions/26332119/stm32-hard-faults-when-trying-to-printf-numbers-10

檢查 toolchain 後,發現目前用的 toolchain 把 v7-M 相關的東西都拿掉了,可能是之前沒用到吧。ARM v7-M 就是 CM4 使用的架構。換了另一個有 v7-M 的 toolchain 再重新編一次,跑起來就沒問題了。

有問題的:
/arm-none-eabi-lib/lib/thumb
/lib/gcc/arm-none-eabi/4.9.3/thumb
修正後:
/arm-none-eabi-lib/armv7-m
/lib/gcc/arm-none-eabi/4.8.4/armv7-m