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");
}


vector table 置換, 寫在 setup() 中

  NewVectorTable[11] = (VECT_FUNC)svc_handler;

其他如同範例, 如這篇最底下: 

執行結果 :  置換成功, 會看到 I am IN SVC


接下來, 我們把 svc_handler 換成範例中的寫法, 發現沒有成功... 

也找到一個 rtenv 的 wiki

找了許久, 終於發現原本的範例有個問題. 修正如後. 

之後結果正確 : 




這邊的實驗是在解釋這樣的流程圖




複習一下 control register : 


Table 2.10. CONTROL register bit assignments
BitsNameFunction
[31:2]-Reserved.
[1]SPSEL
Defines the currently active stack pointer: In Handler mode this bit reads as zero and ignores writes. The Cortex-M3 updates this bit automatically on exception return.
0 = MSP is the current stack pointer
1 = PSP is the current stack pointer.
[0]nPRIV
Defines the Thread mode privilege level:
0 = Privileged
1 = Unprivileged.


我們在 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() {
}

留言

張貼留言

熱門文章