分享到plurk 分享到twitter 分享到facebook

版本 1bc5ba951a5ddab8cf4fdf5e067bc901a2e039e8

rtenv-plus

HackPad 共筆<https://embedded2014.hackpad.com/Rtenv-plus-0VHEYKBpr3D>_

組員

楊震 / <Omar002<https://github.com/Omar002/rtenv-plus/>_>

丁士宸 / <Stanley Ding<https://github.com/StanleyDing/rtenv>_>

程政罡 / <marktwtn<https://github.com/marktwtn/rtenv-plus>_>

李昆憶 / <LanKuDot<https://github.com/LanKuDot/rtenv-plus>_>

鄭聖文 / <Shengwen<https://github.com/shengwen1997/>_>

作業系統架構

Context Switch

Kernel Mode 與 User Mode 間的轉換

kernel mode — activate() —> user mode — syscall or interrupt —> exception — exception handler —> kernel mode

注:在下方 fork原理 以 fork 為例,可以知道轉換細節。

activate

  • 功能:從 kernel mode 轉換成 user mode

.. code-block:: c

activate:
 41     /* save kernel state */
 42     mrs ip, psr
 43     push {r4, r5, r6, r7, r8, r9, r10, r11, ip, lr}
 44     
 45     /* switch to process stack pointer */
 46     msr psp, r0
 47     mov r0, #3
 48     msr control, r0
 49     
 50     /* load user state */
 51     pop {r4, r5, r6, r7, r8, r9, r10, r11, lr}
 52     pop {r7}
 53 
 54     bx lr
  • 指令介紹:mrs Rd, PSRmsr PSR, Rd[#]_:Rd 為 general-purpose registers,PSR 可以為 psr、cpsr、apsr、msp、psp 等。 mrs Rd, PSR 可以將 PSR 的值寫到 Rd,而 msr PSR, Rd 則是將 Rd 值寫到 PSR 裡。

  • 運作:

    • L42,43:將 psr(program status register) 的值保存到 ip (r12) 裡,然後一同 push 到 main stack 裡。
    • L46:將 r0 所帶的值寫入到 psp (process stack pointer),注意呼叫 activate 所放的參數就是該 task 之 task_control_block 中 stack 的 address。
    • L47,48:將 control register 的值設為 3,藉此可以將 stack pointer 轉為指向 process stack (使 sp 值為 psp)。所以藉由 sp 可以存取其 stack 的內容。
    • L51:將 user_thread_stack 的 register 依序 pop 到 r4~r11 及lr,也是為何 user_thread_stack 的前9個 register 設計為 r4~r10、fp、_lr。
    • L52:再將 user_thread_stack_r7 pop 到 r7
    • 所以除了 r0~r3 及 ip、sp、pc、cpsr 之外,都被換成 user-mode 的 register 了。

init_task

  • 功能:將系統初始函式 first() 的位址放置到 process stack 的 lr 位置。藉由 activate 置換 process state 上來,可讓程式執行 first()

  • 運作:

.. code-block:: c

/* 傳入的參數為:欲執行 first() 的 task 的 stack位址 以及 first() 的位址 */
unsigned int *init_task(unsigned int *stack, void (*start)())
{
    /* 由於 stack 的設計為 full descendent stack,
     * 所以 stack pointer 一開始必須指向最高位址。
     * 觀察 user_thread_stack 的設計:r4 是最低位址,處在 stack 的底部
     * 而預期將 first() 的位址存到 _lr 中,所以必須 push 9個 word
     */
    stack += STACK_SIZE - 9;
    /* 利用 pointer arithmetic,可以將 first() 的位址存到 _lr 中:
     * user_thread_stack -> |r4 |r5 |r6 |r7 |r8 |r9 |r10|fp |_lr|...
     *             stack -> |[0]|[1]|[2]|[3]|[4]|[5]|[6]|[7]|[8]|...
     */
    stack[8] = (unsigned int)start;
    /* 回傳新的 sp 給該 task */
    return stack;
}

fork 原理

第一次進到 while loop

::

[ File: main.elf ]
while (1) { 
    tasks[current_task].stack = activate(tasks[current_task].stack);
333c:   f240 72a8   movw    r2, #1960   ; 0x7a8
...
3354:   681b        ldr r3, [r3, #0]
3356:   4618        mov r0, r3
3358:   f00e f8e2   bl  11520 <activate>    // 由此進入 activate,所以 LR 存的值是 0x335d
335c:   f240 72a8   movw    r2, #1960   ; 0x7a8
3360:   f2c2 0200   movt    r2, #8192   ; 0x2000
...
  • 進到 activate() 後,藉由 pop user state 到 register,將預先存好的 first() 的位址存到 LR 中。而原本的 LR 被 push 到 main stack 中,存有離開 activate() 後繼續執行的指令位址。Core registers 及 stacks 的狀態變化如下圖:

    .. image:: /embedded/activate.PNG

    利用 bx lr 使得程式轉往執行 first()

  • 進到 first() 後,程式執行第一行的 fork()

    ::

    [ 程式執行 fork() ] void first() { 2f84: b580 push {r7, lr} 2f86: af00 add r7, sp, #0 if (!fork()) setpriority(0, 0), pathserver(); 2f88: f00e fb22 bl 115d0 // 由此進入 fork(),LR存的值為 0x2f8d 2f8c: 4603 mov r3, r0 2f8e: 2b00 cmp r3, #0

  • 在 syscall (這裡是fork) 中,會觸發 svc exception,程式轉往執行 SVC_Handler()

    .. image:: /embedded/SVC_Handler.PNG

    • 左圖:同時 processor 會將 xPSR、PC、LR、R12、R3、R2、R1、R0[#]_依序 push 到目前的 stack 中 ( process stack ),被 push 到 process stack 的資訊中含有離開 fork() 後繼續執行的指令位址。
    • 中圖:將目前的 state 存到 process stack。原本的 LR 含有 exception return ( 0xfffffffd ) 的資訊,也會一併被 push 到 process stack 中儲存。
    • 右圖:將 kernel state 從 main stack 中 pop 出來,此時 LR 擁有的位址為離開 activate() 後要執行的指令位址(之前在進入 activate() 時所存的 )。所以離開 SVC_Handler() 後,程式會轉往執行 main() 的 while loop,也就是 kernel mode。
  • 目前的 process stack 狀態,會發現與 user_thread_stack 的設計一致,以及不同時期被 push 進去的 R7:

    .. image:: /embedded/user_thread_stack.PNG

    • 在離開 activate() 函式會回傳新的 process stack pointer 給 TCB 的 stack pointer,也就是取得 R0 的值,所以 TCB 的 stack pointer 會得到正確的 process stack 的正確位址。

進行fork

  • 開始執行 kernel mode 後,藉由 tasks[current_task].stack->r7,可以取得在 fork() 傳入的值。因此 kernel 判定要執行 fork 動作,將母 task 的 stack 內容複製到子 task 的 stack 中,但是母 task 的 r0 存的是目前產生的 task 數量,而子 task 則是 0。

母task

  • 進到 activate() 後,再次將 kernel state 與 user state 作交換。此時,LR 含有 EXC_RETURN[#]_的值 0xfffffffd,則當 processor 執行 bx lr 時必須進行 exception return。

    .. image:: /embedded/activate_again.PNG

  • Exception return:當 LR 值為 EXC_RETURN 之一: 0xfffffffd 時,

    1. processor 會轉回 thread mode
    2. 從 process stack 取回 exception 時所 push 進去的 registers
    3. 使用 PSP 為當下的 SP
  • 也就是說,進行 exception return 後,PC 會擁有之前 exception 發生的的下一行指令位址,至於 LR 則為離開 fork() 而回到 first() 繼續執行的位址。

    .. image:: /embedded/activate_again_exc_return.PNG

    ::

    [ fork() ] .global fork fork: push {r7} 115d0: b480 push {r7} mov r7, #0x1 115d2: f04f 0701 mov.w r7, #1 svc 0 115d6: df00 svc 0 // <- Exception 在這裡發生,所以 PC 被存的值為 0x115d8 nop 115d8: bf00 nop // <- 藉由 exception return 使的程式回到這裡繼續執行 pop {r7} 115da: bc80 pop {r7} bx lr 115dc: 4770 bx lr // <- 回到 first 繼續執行

  • 由於在 kernel mode 中,已經將 fork() 所應回傳的值放到 process stack 的 r0 中,藉由 exception return 將這個值 pop 到 R0。則當程式離開 fork() 時,會回傳 task_count (R0)。 因此,母 task 在 if(!fork()) 為 false,則繼續執行下一個 fork()。

    .. code-block:: c

    void first() { if (!fork()) setpriority(0, 0), pathserver(); // 子 task 的 if 判定為 true,於是執行 pathserver() if (!fork()) setpriority(0, 0), romdev_driver(); // 母 task 的 if 判定為 false,於是繼續執行下一行 …

子task

  • activate() 與 exception return 的行為與母 task 相同,只是在 if(!fork()) 判定中為true (因為回傳值為 0),所以就會進行 if 下的函式,於是新的 task 就開始運行了。

List

  • 實作檔案:list.c

  • 類型:cyclic double linked list

  • 函式:

    • list_init:初始化 node,prev 及 next 都指向自己。
    • list_empty
    • list_remove:將指定的 node 從 linked list 中移除。
    • list_unshift:將 new 從原本的 linked list 中移除,再將其 push 到 list 的 next。
    • list_push:將 new 從原本的 linked list 中移除,再將其 push 到 list 的 prev。
    • list_shift:將 list 的 next 從 linked list 中 pop 出來。
  • Macro:

    • list_entry:取得該 list node 所屬的 structure 或 union variable 的位址。

    .. code-block:: c

    #define list_entry(list, type, member)
    (container_of((list), type, member)) #define container_of(ptr, type, member)
    ((type )(((void )ptr) - offsetof(type, member)))

    Macro offsetof( type, member ) 會以 bytes 的形式回傳指定 member 在 type 指定的 structure 或 union 的位置。如:

    .. code-block:: c

    struct foo { int a; int b; };

    則 offsetof( foo, a ) 會回傳 0,offsetof( foo, b ) 會回傳 4。看到 TCB 的設計:

    .. code-block:: c

    struct task_control_block { struct user_thread_stack *stack; int pid; int status; int priority;

      struct list list;

    };

    使用 task = list_entry(curr, struct task_control_block, list); 傳入 list node 的位址減去 list node 在 structure 中的 byte 位置,就會得到該 structure 第一個元素的起始位址,同時也是該 structure 變數的位址。

    • list_for_each:以 list 為起點,搜尋所有 node。
    • list_for_each_safe:以 list 為起點,但考慮到有些因為呼叫如 list_shift 之類的 method 被 pop 出去的 list node 也可以被搜尋到。

Event Monitoring

  • 實作檔案:event_monitor.c
  • 功能:負責接受來自 event sources 所發出的 event occurrences,並將這些 pending 轉給 handlers 來處理 event。[#]_
  • 特性:
    • Event Collection:kernel 擁有一個 event monitor 來收集 occurrences 並轉送給 handlers。
    • Event Sensor:透過 event_monitor 中的 events 來幫 handler “裝上 Sensor”。而每個 event sensor 都有 pending 用以暫存發出 occurrence 的 task。
  • 函式:
    • event_monitor_init:初始化 event_monitor
    • event_monitor_find_free:找尋有無尚未被使用的 sensor
    • event_monitor_register:為 handler 裝上 sendor
    • event_monitor_block:將發出 event occurrence 的 task 從 ready_list 取出放到對應 event 的 pending list 裡,使 task 等待 handler 的處理
    • event_monitor_release:標記有 occurrence pending
    • event_monitor_serve:檢查 pending,並讓 handler 處理 pending。處理完後將 task 從 pending list 取出到 ready list 裡

System Call

  • 功能:將 syscall 代碼存入 R7,觸發 SVC exception,將代碼存入 Processor stack,並轉換成 Kernel Mode 後,在 main() 中處理 system call。

Pipe 的 Read 與 Write

serialout()rs232_xmit_msg_task() 為例。/dev/tty0/out 為 fifo pipe,而 rs232_xmit_msg_task() 寫入資料到 pipe 中,serialout() 從這個 pipe 讀出資料。

.. image:: /embedded/rtenv-plus/Read_and_write.png

以 write 的部分為例,在 FILE_ACCESS_ACCEPT 的 case 中會送出 read_event pending。執行完 write 的工作後,kernel 會繼續執行 event_monitor_serve(),檢查有沒有 event pending,此時,剛剛送出的 read_event 就會被處理,檢查有沒有在 read 的時候被 push 到 read_event list 的 task。如果有,則執行 read_event 的 handler - pipe_read_release()pipe_read_release() 會重新呼叫 file_read(),讓 task 再次嘗試讀取 pipe,流程判斷如上圖,但是不同的 case 有不同的處理方式:

  • FILE_ACCESS_ACCEPT:值為1,代表讀取成功。event_monitor_serve() 會將該 task 從 read_event 的 list 中 pop 出來,並重新 push 回 ready_list 裡,task 的狀態也被改為 TASK_READY。
  • FILE_ACCESS_BLOCK:值為0,代表仍舊未達指定讀取量,則會被繼續 BLOCK 住。
  • FILE_ACCESS_ERROR:不會發生。因為在第一次嘗試讀取時,如果發生 FILE_ACCESS_ERROR,該 task 不會被 push 到 read_event 的 list 中。

至於 read,則反之。

移植過程

  1. 將舊的函式庫移除
  1. 註解硬體相關的函式庫
  2. 嘗試編譯
  3. 找出相依的 function
  1. 加入新的函式庫
  1. 在檔案中加入標頭檔
  2. 修改 Makefile 中的 include path
  1. 確認硬體是能動的
  1. 跑範例程式
  2. 將範例程式丟入作業系統看是否能正常運作
  3. 修改系統參數

硬體驅動原理

  • USB OTG</embedded/OTG>_

效能表現

參考資料

.. [#] mrs指令<http://infocenter.arm.com/help/topic/com.arm.doc.dui0489i/Cihjcedb.html>_ 、 msr指令<http://infocenter.arm.com/help/topic/com.arm.doc.dui0489i/Cihibbbh.html>_

.. [#] Cortex-M3 Exception Entry<http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Babefdjc.html>_

.. [#] Cortex-M3 Exception Return<http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Babefdjc.html>_

.. [#] WikiPedia: Event Monitoring<http://en.wikipedia.org/wiki/Event_monitoring>_