RTOS - context switch - SVC
收錄於 : 關於Ameba的一百篇
續前篇 : RTOS - context switch
接續 Jserv 的課程作業 :
mini-arm-os 的 hackpad
embedded-2016
embedded-2015
github 程式 - link
在這例子, 主要是介紹 SVC
在之前的 vector table 和 RTOS 植入, 有稍微提及
在做實驗前, 記得如前一篇所提方式, 將這包 arduino 內建的 RTX 先移除.
還記得我們在前一篇時, activate() 會把系統切換到 user mode
(使用 PSP ), 並呼叫 usertask().
那在 user mode 時, 怎麼樣去呼叫 kernel mode 的 程式來執行呢?
這時候就要透過 SVC 來切換到 kernel mode ( handler , 使用 MSP), 執行 OS 的 API.
---
實驗1, vector table 的 SVC handler.
在這包 Arduino SDK, 其實在執行到 variant.cpp 的 app_start() 之前,
就已經把 VTOR 設到 NewVectorTable[] 這個全域 變數.
所以我們可以這樣來置換 vector table 裡的 SVC handler
寫一個 svc handler
__attribute__((irq)) void svc_handler(void)
{
Serial.println("I am IN SVC");
}
PS: function name 長相奇怪, 這是因為 arduino 寫的是 C++,
一個方法是在前頭用 extern "C" { .. } 把 function name 宣告一下,
就會是 C 的命名方式
續前篇 : RTOS - context switch
接續 Jserv 的課程作業 :
mini-arm-os 的 hackpad
embedded-2016
embedded-2015
github 程式 - link
在這例子, 主要是介紹 SVC
在之前的 vector table 和 RTOS 植入, 有稍微提及
在做實驗前, 記得如前一篇所提方式, 將這包 arduino 內建的 RTX 先移除.
還記得我們在前一篇時, activate() 會把系統切換到 user mode
(使用 PSP ), 並呼叫 usertask().
那在 user mode 時, 怎麼樣去呼叫 kernel mode 的 程式來執行呢?
這時候就要透過 SVC 來切換到 kernel mode ( handler , 使用 MSP), 執行 OS 的 API.
---
實驗1, vector table 的 SVC handler.
在這包 Arduino SDK, 其實在執行到 variant.cpp 的 app_start() 之前,
就已經把 VTOR 設到 NewVectorTable[] 這個全域 變數.
所以我們可以這樣來置換 vector table 裡的 SVC handler
寫一個 svc handler
__attribute__((irq)) void svc_handler(void)
{
Serial.println("I am IN SVC");
}
vector table 置換, 寫在 setup() 中
NewVectorTable[11] = (VECT_FUNC)svc_handler;
其他如同範例, 如這篇最底下:
執行結果 : 置換成功, 會看到 I am IN SVC
接下來, 我們把 svc_handler 換成範例中的寫法, 發現沒有成功...
也找到一個 rtenv 的 wiki
找了許久, 終於發現原本的範例有個問題. 修正如後.
之後結果正確 :
這邊的實驗是在解釋這樣的流程圖
複習一下 control register :
我們在 bit1 可以知道, 是用 0-MSP, 或 1-PSP
(1) 主程式一開始是在 "Privileged Thread" - MSP ( control = 0x0 )
(2) 第一次呼叫 activate() 時, 會設 control = 0x3 ( PSP)
接者跳到 usertask, 此時是在 "User Thread"
上述都和上一篇範例相同
(3) userstack 第一次 呼叫 syscall(), SVC 屬於一種 Exception
會跳去 vector table 註冊的 svc_handler,
此時是在 "privileged handler"
(4) 我們在 (2) activate() 一開始, 有把當時 lr , push 進去
(在設 control 前, 所以還是用 MSP)
在 svc_handler 時, 會把它 pop 出來, 因此這邊的 "bx lr"
會跳到 main() 呼叫 activate() 的返回位置, 也就是 印出
"Return to OS mode"
(5) 此時 main() 所看到的 control 值為 1, (MSP),
( bit0 指的是 thread mode 的是 privileged 或 unprivileged,
而非目前是 privileged/un-privileged )
以現在狀態仍處在 "privileged handler"
(6) 第二次呼叫 activate(), 我們在呼叫前把 stack[8] 印出來, 值是 0xFFFFFFFD
EXC_RETURN
所以在 "bx #0xFFFFFFFD", 會做 相對應動作, 返回 "User Thread"
而進出點的地方, 硬體會自動 push / pop 這幾個 registers 到 SP
( 用 MSP / PSP , 視呼叫 SVC 時所使用的 stack pointer )
這邊的問題是
發現在 Ameba 板子上, handler mode 中執行 activate(), control 值雖然設 3 (PSP),
但仍為 1 (MSP), 所以後面 pop 時會出錯, lr 得不到 EXC_RETURN
這時必須要修正成 底下寫法, 直接取出 psp pointer 值, 用 ldmia ,
( load 資料並位置值增加 - pop ) , 做完再把最後結果存回 psp
"mrs r0, psp \n\t"
"ldmia r0!, {r4, r5, r6, r7, r8, r9, r10, r11, lr} \n\t"
"msr psp, r0 \n\t"
(7) 程式在做 第二次 activate 前, 有把 stack 資料印出來.
stack[0] = 0x26 => R0
stack[1] = 0x40003014
stack[2] = 0xD
stack[3] = 0xC0000000
stack[4] = 0x20000000
stack[5] = 0x100036C9 => LR
stack[6] = 0x10003698 => PC
stack[7] = 0x21000000
我們可以用之前介紹產生 asm 的方法
" arm-none-eabi-objdump -D target.axf > target.asm "
PC 值 pop 出來時, 會跑到 syscall 的 nop 這行繼續往下,
此時的 lr 為 userstack() 呼叫 syscall() 的下一行, 所以會繼續往下印.
PS: function name 長相奇怪, 這是因為 arduino 寫的是 C++,
一個方法是在前頭用 extern "C" { .. } 把 function name 宣告一下,
就會是 C 的命名方式
(7) 第二次 syscall 時, 如第一次, 會一路跳到 main() 的 activate() 後, 印出訊息後
while loop 停住.
所以在 cortex-M, 雖然他的 clock 比較慢, 但很多原本 SW 的指令動作,
轉變成用 HW 方式來做, 節省不少時間, 可無形中增進效能.
----
typedef void (*VECT_FUNC)(void);
extern VECT_FUNC NewVectorTable[];
/*
__attribute__((irq)) void svc_handler(void)
{
Serial.println("I am IN SVC");
}
*/
__attribute__((naked, irq)) volatile void svc_handler()
{
asm volatile (
// save user state
"mrs r0, psp \n\t"
"stmdb r0!, {r4, r5, r6, r7, r8, r9, r10, r11, lr} \n\t"
// load kernel state
"pop {r4, r5, r6, r7, r8, r9, r10, r11, ip, lr} \n\t"
"msr psr, ip \n"
"bx lr \n\t"
);
}
__attribute__((naked)) volatile void syscall(void)
{
asm volatile (
"svc 0 \n\t"
"nop \n\t"
"bx lr \n\t"
);
}
__attribute__((naked)) int activate(unsigned int* pstack)
{
uint32_t value;
asm volatile (
// save kernel state
"mrs ip, psr \n\t"
"push {r4, r5, r6, r7, r8, r9, r10, r11, ip, lr} \n\t"
// switch to process stack
"msr psp, r0 \n\t"
"mov r0, #3 \n\t"
"msr control, r0 \n\t"
);
value = __get_CONTROL();
Serial.print("act - set - CONTROL : 0x");
Serial.println(value, HEX);
if (( value & 0x2 ) == 0 ) {
value = __get_PSP();
Serial.print("PSP : 0x");
Serial.println(value, HEX);
Serial.print("[8] : 0x");
Serial.println(((uint32_t*)value)[8], HEX);
asm volatile (
// load user state
"mrs r0, psp \n\t"
"ldmia r0!, {r4, r5, r6, r7, r8, r9, r10, r11, lr} \n\t"
"msr psp, r0 \n\t"
);
} else {
asm volatile (
"pop {r4, r5, r6, r7, r8, r9, r10, r11, lr} \n\t"
);
}
asm volatile (
// jump to user task
"bx lr \n\t"
);
}
void usertask(void)
{
uint32_t value;
value = __get_CONTROL();
Serial.print("usertask CONTROL-1 : 0x");
Serial.println(value, HEX);
Serial.println("usertask: 1st call of usertask!");
Serial.println("usertask: Now, return to kernel mode");
syscall();
value = __get_CONTROL();
Serial.print("usertask CONTROL-2 : 0x");
Serial.println(value, HEX);
Serial.println("usertask: 2nd call of usertask!");
Serial.println("usertask: Now, return to kernel mode\n");
syscall();
while(1);
}
void dump_stack(unsigned int *pstack)
{
int i;
for (i=0; i<16; i++) {
Serial.print("stack[");
Serial.print(i);
Serial.print("] = 0x");
Serial.println(pstack[i], HEX);
}
}
void setup() {
uint32_t value;
unsigned int usertask_stack[256];
unsigned int *usertask_stack_start = usertask_stack +256-16;
usertask_stack_start[8] = (unsigned int) &usertask;
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
NewVectorTable[11] = (VECT_FUNC)svc_handler;
// prints title with ending line break
Serial.println("OS: Starting...");
Serial.println("OS: Calling the usertask (1st time)");
value = __get_CONTROL();
Serial.print("main CONTROL-1 : 0x");
Serial.println(value, HEX);
usertask_stack_start = (unsigned int*)activate(usertask_stack_start);
Serial.println("OS: Return to the OS mode !");
value = __get_CONTROL();
Serial.print("main CONTROL-2 : 0x");
Serial.println(value, HEX);
Serial.println("OS: Calling the usertask (2nd time)");
Serial.print("usertask[8] = 0x");
Serial.println(usertask_stack_start[8], HEX);
dump_stack(&usertask_stack_start[9]);
usertask_stack_start = (unsigned int*)activate(usertask_stack_start);
Serial.println("OS: Return to the OS mode !");
Serial.println("OS: Going to infinite loop...");
while(1);
}
void loop() {
}
謝謝,你的解說很清楚。
回覆刪除