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

版本 3789153f93bcdfcbf03a6a885d928d6c2e6c0d74

embdded/rtenv

Changes from 3789153f93bcdfcbf03a6a885d928d6c2e6c0d74 to 458eac718d435a0dbe81e60788a8b1519e237ba9

---
title: rtenv-plus
categories: embedded, arm, stm32, stm32f429
toc: yes
...

`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, PSR`` 及 ``msr 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>    // 由此進入 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

  .. image:: /embedded/rtenv-plus/double_linked_list.PNG

- 函式:
  - ``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. 將舊的函式庫移除
 a. 註解硬體相關的函式庫
 b. 嘗試編譯
 c. 找出相依的 function

.. image:: /embedded/rtenv-plus/comment_header.png
.. image:: /embedded/rtenv-plus/dependency.png
2. 加入新的函式庫
 a. 在檔案中加入標頭檔
 b. 修改 Makefile 中的 include path
  
.. image:: /embedded/rtenv-plus/makefile_port.png
3. 確認硬體是能動的
 a. 跑範例程式
 b. 將範例程式丟入作業系統看是否能正常運作
 c. 修改系統參數


硬體驅動原理
----------------
* `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>`_