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

版本 17901996f30ca9ea7de6fc5ec5f27ac3829a1072

embedded/rtenv

Changes from 17901996f30ca9ea7de6fc5ec5f27ac3829a1072 to 777d7d96448fbb96b956e840a4084b75c19107a3

---
title: rtenv+
categories: embedded, arm, stm32, stm32f429, ncku, cortex-m, IoT
toc: no
...

協作者
------
* 2015 年春季:
  - `劉家瑄<https://github.com/goldmedal/>`_, `賴弈豪<https://github.com/harry191518>`_, `陳建霖<https://github.com/james33344>`_, `蘇容德<https://github.com/rita5022>`_, 李洋逸, 戴紹軒

* 2014 年春季:
  - `楊震<https://github.com/Omar002>`_, `丁士宸<https://github.com/StanleyDing>`_, `程政罡<https://github.com/marktwtn>`_, `李昆憶<https://github.com/LanKuDot>`_, `鄭聖文<https://github.com/shengwen1997/>`_

共筆
----
* 2015 年春季: `hackpad <https://hackpad.com/rtenv-wDf84ASaCcb#:h=Memory-Layout>`_
* 2014 年春季: `hackpad <https://embedded2014.hackpad.com/Rtenv-plus-0VHEYKBpr3D>`_

作業系統架構
---------------


Context Switch
===============

**Kernel Mode 與 User Mode 間的轉換**

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

  .. image:: /embdded/rtenv-plus/kernel_mode_vs_user_mode.PNG
注:在下方 ``fork原理`` 以 fork 為例,可以知道轉換細節。


**Execution modes**
-Operation modes:
1. Thread mode
2. Handler mode

-Main and Process Stacks
1. MSP:Main Stack Pointer
2. PSP:Process Stack Pointer

**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     ldmia r0!, {r4, r5, r6, r7, r8, r9, r10, r11, lr}
     46     ldmia r0!, {r7}
     47
     48     /* switch to process stack pointer */
     49     msr psp, r0
     50     mov r0, #3
     51     msr control, r0
     52     
     53     /* program breaking code
     54     pop {r4, r5, r6, r7, r8, r9, r10, r11, lr}
     55     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 裡。
  - L45:將 ``user_thread_stack`` 的 register 依序 pop 到 r4~r11 及lr,也是為何 ``user_thread_stack`` 的前9個 register 設計為 r4~r10、fp、_lr。
  - L46:再將 ``user_thread_stack`` 的 ``_r7`` pop 到 ``r7``。
  - L49:將 ``r0`` 所帶的值寫入到 ``psp`` (process stack pointer),注意呼叫 activate 所放的參數就是該 task 之 task_control_block 中 stack 的 address。
  - L50, 51:將 ``control`` register 的值設為 3,藉此可以將 stack pointer 轉為指向 process stack (使 sp 值為 psp)。所以藉由 ``sp`` 可以存取其 stack 的內容。

  - 所以除了 r0~r3 及 ip、sp、pc、cpsr 之外,都被換成 user-mode 的 register 了。

task
==========
rtenv-plus 中的 task 以 ``task_control_block`` 來呈現其資訊。

 .. code-block:: c

    struct task_control_block {
        struct user_thread_stack *stack; // 指到記憶體中 stack 的位置
        int pid; //記錄目前 task 的 pid
        int status; //記錄目前 task 的狀態
        int priority; //記錄目前 task 的優先度

        struct list list; 
    };
    
    // 其中 status 共有 5 種狀態被定義
    #define TASK_READY      0
    #define TASK_WAIT_READ  1
    #define TASK_WAIT_WRITE 2
    #define TASK_WAIT_INTR  3
    #define TASK_WAIT_TIME  4
    
    // 其中 priority 預設為 20,最低的 priority 為39
**GDB macro**
利用 macro 可以快速查看目前的 task 是哪一個,並顯示出目前所有 task 的狀態以及優先度
  
  ::

    define print_status
        if($arg0 == 0)
            printf "TASK_READY     "
        end
        if($arg0 == 1)
            printf "TASK_WAIT_READ "
        end
        if($arg0 == 2)
            printf "TASK_WAIT_WRITE"
        end
        if($arg0 == 3)
            printf "TASK_WAIT_INTR "
        end
        if($arg0 == 4)
            printf "TASK_WAIT_TIME "
        end
    end

    define ps
        printf "pid    status            priority\n"

        set $t = tasks
        set $i = 0
        while $i != task_count
            if ($i == current_task)
                printf "*%2d    ", $t[$i].pid
            else
                printf "%3d    ", $t[$i].pid
            end
            print_status $t[$i].status
            printf "%11d\n", $t[$i].priority
            set $i = $i + 1
        end
    end

  .. image:: /embdded/rtenv-plus/gdb_macro.png

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 的設計為 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;
    }


How  to 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 就開始運行了。

與 POSIX fork 差別
====================
不同於 POSIX fork 的使用是架構在 virtual memory 上,rtenv-plus 只是單純地將母 task 在記憶體中所使用的 stack 完完整整地複製一份到子 task 所對應的 stack。

在 file descriptor 方面,因為 rtenv-plus 並沒有所謂的 file descriptor table,所以如果母 task 有開檔的話,子 task 也只是單純繼承 file descriptor 的值而已。

rtenv-plus 的 fork 與在同樣沒有 virtual memory 支援下所使用的 vfork 也有非常大的不同處。

1. vfork 在產生子 task 後,會先將母 task suspend 直到子 task 結束為止。而 rtenv-plus 則是兩個 task 能夠同時存在。
2. 在記憶體使用方面,vfork 的兩個 task 會使用同一個實體記憶體位置,不同於 rtenv-plus 兩個 task 用的是不同的實體記憶體位置。

另外,在其它系統上,子 task 的 PPID 會是其母 task 的 PID,所以兩者之間可以有互動,例如母 task 等待子 task 結束。但在 rtenv-plus 上,因為沒有實作 PPID ,所以兩個 task 基本上是獨立運作而互不干擾的。

task
==========
rtenv-plus 中的 task 以 ``task_control_block`` 來呈現其資訊。

 .. code-block:: c

    struct task_control_block {
        struct user_thread_stack *stack; // 指到記憶體中 stack 的位置
        int pid; //記錄目前 task 的 pid
        int status; //記錄目前 task 的狀態
        int priority; //記錄目前 task 的優先度

        struct list list; 
    };
    
    // 其中 status 共有 5 種狀態被定義
    #define TASK_READY      0
    #define TASK_WAIT_READ  1
    #define TASK_WAIT_WRITE 2
    #define TASK_WAIT_INTR  3
    #define TASK_WAIT_TIME  4
    
    // 其中 priority 預設為 20,最低的 priority 為39
**GDB macro**
利用 macro 可以快速查看目前的 task 是哪一個,並顯示出目前所有 task 的狀態以及優先度
  
  ::

    define print_status
        if($arg0 == 0)
            printf "TASK_READY     "
        end
        if($arg0 == 1)
            printf "TASK_WAIT_READ "
        end
        if($arg0 == 2)
            printf "TASK_WAIT_WRITE"
        end
        if($arg0 == 3)
            printf "TASK_WAIT_INTR "
        end
        if($arg0 == 4)
            printf "TASK_WAIT_TIME "
        end
    end

    define ps
        printf "pid    status            priority\n"

        set $t = tasks
        set $i = 0
        while $i != task_count
            if ($i == current_task)
                printf "*%2d    ", $t[$i].pid
            else
                printf "%3d    ", $t[$i].pid
            end
            print_status $t[$i].status
            printf "%11d\n", $t[$i].priority
            set $i = $i + 1
        end
    end

  .. image:: /embdded/rtenv-plus/gdb_macro.png

list
=====

- 實作檔案:list.c
- 類型:cyclic double linked list

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

- 函式:
  - ``list_init``:初始化 node,prev 及 next 都指向自己。
  - ``list_empty``:如果 list 的 next 還是指向自己,代表 list 為空。
  - ``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`` 之類的 function 會把 list node pop 出來,導致搜尋的連結中斷,``list_for_each_safe`` 讓剩下的 node 可以繼續被搜尋。

scheduler
==========
rtenv-plus maintain 一個 global 的 list 陣列 ``ready_list``,為 scheduler 的資料結構,``ready_list`` 的每一個元素都對應到不同優先權。

  .. code-block:: c

    struct list ready_list[PRIORITY_LIMIT + 1];

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

在第一個 task 的初始化,fork 建立新的 task,或是利用 setpriority 設定 task 的優先權,
會使用 ``list_push`` 將 task  push 到對應優先權的 ``ready_list`` 中,多個 task 之優先權可能相同 

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

判別 ``ready_list`` 中的 element 是否為 empty,則觀察有沒有 task 被 push 到該 element 中

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

觸發 scheduler 選擇下一個執行的 task 有三種狀況

  - ``SysTick_Handler``:STM32F4 系統時鐘預設為 168MHz

  .. code-block:: c

    SysTick_Config(configCPU_CLOCK_HZ / configTICK_RATE_HZ);

configCPU_CLOCK_HZ 為 72MHz,
configTICK_RATE_HZ 為 100,
( 72M/100 ) /168M = 4.29m,每經過 4.29ms 就會觸發一次 SysTick_Handler

  - ``USART2_IRQHandler``:STM32F4 與週邊裝置或是連結的電腦傳輸資料時,usart2所觸發的中斷
  - ``SVC_Handler`` :system call 執行 ``svc 0`` 觸發

當 rtenv-plus 分配給 task 的執行時間到了,且正在執行的 task 之優先權所對應到的 ``ready_list`` 元素,最前端為正在執行的 task,
則將該 task 放到 ``ready_list`` 元素的最末端,讓其他有相同優先權的 task 能夠被執行。

  .. code-block:: c

    task = &tasks[current_task];
    if (timeup && ready_list[task->priority].next == &task->list)
        list_push(&ready_list[task->priority], &tasks[current_task].list);

每次 scheduler 在選擇下一個要執行的 task,會從 ``ready_list`` 中尋找優先權最大且最前端的 task。

  .. code-block:: c

    for (i = 0; list_empty(&ready_list[i]); i++); // 尋找非空的 ready_list。
    
    list = ready_list[i].next; // 將 list 指到 ready_list[i].next 所指的 `struct list`,這個 list 會指到某一個 struct task 裡頭的 struct list。
    task = list_entry(list, struct task_control_block, list); // 將 task 指到上面講的 struct task
    current_task = task->pid; // 記錄這個 task 的 pid

由上述可知道 rtenv-plus 是一個使用 round-robin 排程的 preemptive 作業系統

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
============

user mode 時,執行屬於 system call 的函式,將 system call 的代碼存入 ``r7`` 並觸發 SVC exception,執行 ``SVC_Handler`` 轉換成 kernel mode 後,在 ``main()`` 中實作 system call 功能。

system call 函數的參數以及原本 user mode push 進去的暫存器之值,皆可透過 tasks[current_task].stack 來傳遞,因為 tasks[current_task].stack 指向 task 在 user mode 用來 push 的 stack,且 system call 的函數變數會先儲存到暫存器 r0 ~ r3

``main()`` 中判斷 system call 的代號並執行對應功能

- ``0x1:fork``

``int fork();``

分支出一個新的子task,子task複製所有父task push 進去的 stack 資料,繼承優先權,並為子task設定 pid 為當前task的數量-1(第一個初始task之 pid 為0),也將子task.list push 進 ``ready_list``。父task回傳0,子task則回傳 pid 之值(不等於0)。

若task數量已達到上限,則無法分支出新的 task,回傳-1。

- ``0x2:getpid``

``int getpid();``

回傳該 task 之 pid,也就是 current_task。

- ``0x3:write``

``int write(int fd, const void *buf, size_t count);`` fd 是對應的 file descriptor,buf 則是要被寫入的地方,count 則是要寫入多少資料

實現行程之間的溝通以及檔案系統的實作。
根據不同檔案類型,用 ``writable(struct file*, struct file_request*, struct event_monitor *)`` 確認是否可寫入,若可以則執行定義好的 ``write(struct file*, struct file_request*, struct event_monitor *)`` ( 非system call )

- ``0x4:read``

``int read(int fd, void *buf, size_t count);``  fd 是對應的 file descriptor,buf 則是要被讀取的地方,count 則是要讀取多少資料

實現行程之間的溝通以及檔案系統的實作。
根據不同檔案類型,用 ``readable(struct file*, struct file_request*, struct event_monitor *)`` 確認是否可寫入,若可以則執行定義好的 ``read(struct file*, struct file_request*, struct event_monitor *)`` ( 非system call )

- ``0x5:interrupt_wait``

``void interrupt_wait(int intr);`` intr 為特定的 interrupt 編號

透過 ``NVIC_EnableIRQ(tasks[current_task].stack->r0);`` 啟動特定的 interrupt 使之後能夠被觸發,task 必須在 interrupt 被觸發之後才可以繼續執行,於是先使用 ``event_monitor_block`` 擋住 task,task的狀態變成TASK_WAIT_INTR。

- ``0x6:getpriority``

``int getpriority(int who);`` who 決定選擇哪個 task,0為 tasks[current_task]

回傳 task 的優先權。

- ``0x7:setpriority``

``int setpriority(int who, int value);`` who 決定選擇哪個 task,0為 tasks[current_task],value 則是優先權大小

設定 task 的優先權,優先權不小於 0 也不大於 PRIORITY_LIMIT,除了設定 current_task 的優先權之外,也可以更改其他 task 的優先權。

- ``0x8:mknod``

``int mknod(int fd, int mode, int dev);`` fd 是對應的 file descriptor,mode 不影響,dev 決定 file_operation 結構為哪種檔案類型

改變傳入對應參數 files[fd] 所指向的 file_operation 結構,結構包含write,writable,read,readable,lseek,lseekable 函式(不是 system call 的函式)。

- ``0x9:sleep``

``void sleep(unsigned int);`` unsigned int 表示需等待幾次 SysTick 發生

將 tasks[current_task] 擋住,狀態變成 TASK_WAIT_TIME,等待觸發 ``SysTick_Handler`` ,且觸發次數必須為 unsigned int 之值,才能繼續執行。

- ``0xa:lseek``

``void lseek(int fd, int offset, int whence);``  fd 是對應的 file descriptor,offset 是偏移量,whence 則是決定讀寫頭一開始會指到哪裡

實現檔案系統的實作。
根據不同檔案類型,用 ``lseekable(struct file*, struct file_request*, struct event_monitor *)`` 確認是否可寫入,若可以則執行定義好的 ``lseek(struct file*, struct file_request*, struct event_monitor *)`` ( 非system call )

- ``default:``

此狀況為因應其他非 SVC_Handler 的 interrupt,如SysTick_Handler與USART2_IRQHandler。
SysTick_Handler:timeup 設定為1,使之後進行選擇哪一個 task 來執行,並且釋放狀態為 TASK_WAIT_TIME 的 task(但要符合條件)。
USART2_IRQHandler:關掉特定的 interrupt 使之不能夠被觸發,並且釋放狀態為 TASK_WAIT_INTR 的 task。

File Descriptor
================
在 rtenv-plus 中提供四種檔案類型,分別為 fifo pipe ( S_IFIFO )、message queue ( S_IMSGQ )、register file ( S_IFREG )、block file ( S_IFBLK )。每一種檔案類型都擁有自己的資料結構以及處理函式,其中處理函式針對 read、write、lseek 這三個 system call 都有提供一個檢查函式 ( e.g. fifo_writable ) 以及一個或多個運作函式 ( e.g. fifo_write )[#]_ 。

----------------

檔案類型一覽表:

+----------------+----------+------------------------+--------------+--------------+
| 檔案類型        | type     | 資料結構                | 處理函式      | 初始化函式    |
+================+==========+========================+==============+==============+
| fifo pipe      | S_IFIFO  | struct pipe_ringbuffer | 沒有 lseek    | fifo_init   |
+----------------+----------+------------------------+--------------+--------------+
| message queue  | S_IMSGQ  | struct pipe_ringbuffer | 沒有 lseek   | mq_init      |
+----------------+----------+------------------------+--------------+--------------+
| register file  | S_IFREG  | struct regfile         | 皆有提供      | regfile_init |
+----------------+----------+------------------------+--------------+--------------+
| block file     | S_IFBLK  | struct block           | 皆有提供      | block_init   |
+----------------+----------+------------------------+--------------+--------------+

----------------

**初始化**

透過 system call ``int mknod(int fd, int mode, int dev)`` 來指定 file descriptor 的類型,每一種檔案類型都有提供一個初始化函式。初始化函式會:

- 從 memory pool 配置一塊記憶體給該檔案的資料結構
- 設置處理函式
- 將資料結構中的 file 元素存到 file descriptor array。
- 設置 event sensor


**Structures**

- file_request:task 對於 fd 的請求,或是 fd 對 fd 的請求

  .. code-block:: c

    struct file_request {
        struct task_control_block *task;
        char *buf;        // 請求所需的資料,可以是 buffer 或是 其他 request 的資料結構
        int size;         // *buf 的大小
        int whence;       // 用於 lseek,標記 lseek 的請求類型
    };


- file:file descriptor 的 fd number 以及所屬的處理函式

  .. code-block:: c

    struct file {
        int fd;          // 獨特的 fd number
        struct file_operations *ops;     // 指向所屬的處理函式結構
    };

- file_operations:利用 pointer to function 來設置處理函式

  .. code-block:: c

    struct file_operations {
        int (*readable)(struct file*, struct file_request*, struct event_monitor *);
        int (*writable)(struct file*, struct file_request*, struct event_monitor *);
        int (*read)(struct file*, struct file_request*, struct event_monitor *);
        int (*write)(struct file*, struct file_request*, struct event_monitor *);
        int (*lseekable)(struct file*, struct file_request*, struct event_monitor *);
        int (*lseek)(struct file*, struct file_request*, struct event_monitor *);
    };


**file descriptor 關係**

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

- 在初始化函式中,會將資料結構中的 file 元素存到 kernel 的 file descriptor array,所以只要在 file descriptor array 中拿到指定的 file 元素,透過 marco ``container_of``,就可以拿到該 file descriptor 的資料結構。


**pipe ( fifo pipe, message queue )**

- IPC 的實作方式之一。
- 資料結構

  .. code-block:: c

    struct pipe_ringbuffer {
        struct file file;      // 所屬的 fd 以及 file operations
        int start;             // ring buffer 目前的讀寫起點
        int end;               // ring buffer 目前的讀寫終點
        int read_event;        // 該 fd 所屬的 read event ID
        int write_event;       // 該 fd 所屬的 write event ID
        char data[PIPE_BUF];   // pipe 的 buffer,ring buffer
    };

- Event Handler
  - ``int pipe_read_release(struct event_monitor *monitor, int event, struct task_control_block *task, void *data)``:
    讓因為讀取而被 block 住的 task,重新送出讀取請求。
  - ``int pipe_write_release(struct event_monitor *monitor, int event, struct task_control_block *task, void *data)``:
    讓因為寫入而被 block 住的 task,重新送出寫入請求。

- Macros
  - ``PIPE_PUSH( pipe, src )``:從 src push 1 byte 的資料到 pipe 中。
  - ``PIPE_POP( pipe, dst )``:從 pipe pop 1 byte 的資料到 dst 中。
  - ``PIPE_PICK( pipe, dst, size_byte )``:從 pipe 中讀取指定 size 的資料到 dst,但不會將 pipe 的資料 pop 出來。
  - ``PIPE_LEN( pipe )``:回傳 pipe 中的資料量 in bytes。

- fifo pipe ( S_IFIFO )
  - 傳遞資料格式:| 資料 |
  - ``fifo_readable``
    - FILE_ACCESS_ERROR:請求讀取的大小超過 pipe buffer 的大小。
    - FILE_ACCESS_BLOCK:pipe 中的有效資料量未達要求讀取的大小,task 會被 push 到該資料結構的 read event list 中,進入 TASK_WAIT_READ 狀態。
    - FILE_ACCESS_ACCEPT:pipe 中的有效資料量大於或等於要求讀取的大小。

  - ``fifo_read``:1 byte 1 byte 的從 pipe POP 資料出來存到使用者提供的 buffer 裡。讀取完成後,發出 write pending 讓等待寫入同一個 file descriptor 的 task 可以寫入資料。
  - ``fifo_writable``
    - FILE_ACCESS_ERROR:請求寫入的大小超過 pipe buffer 的大小。
    - FILE_ACCESS_BLOCK:如果剩餘有效空間小於要求寫入大小的話,task 會被 push 到 write event list 中,進入 TASK_WAIT_WRITE 狀態。
    - FILE_ACCESS_ACCEPT:pipe 有足夠空間可以寫入。

  - ``fifo_write``:1 byte 1 byte 將資料從使用者提供的 buffer 寫入 pipe 裡。寫入完成後,發出 read pending 讓等待讀取同一個 file descriptor 的 task 可以讀取資料。

- message queue ( S_IMSGQ )
  - 傳遞資料格式:| 資料長度(4 bytes) | 資料 |
  - ``mq_readable``
    - FILE_ACCESS_BLOCK:message queue的資料傳輸格式一定帶有 4 bytes 的資料來指示後面所帶的資料長度,所以當 pipe 的有效資料未達 4 byte 時,代表還未傳輸完成,task 會被 push 到 read event list,進入 TASK_WAIT_READ 狀態。
    - FILE_ACCESS_ERROR:請求讀取長度大於這次傳輸的長度。透過"資料長度( 4 bytes )"來檢查。
    - FILE_ACCESS_ACCEPT:請求長度等於或小於這次的傳輸長度。

  - ``mq_read``:先從 pipe 中取出資料長度,再依照資料長度將 pipe 資料 pop 出來存到使用者提供的 buffer 裡。讀取完成後,發出 write event pending 讓等待寫入同一個 file descriptor 的 task 可以寫入資料。
  - ``mq_writable``
    - FILE_ACCESS_ERROR:寫入必須是 non-atomic,所以寫入長度( 包含 4 byte 的長度資訊 )不能大於 pipe 的大小。
    - FILE_ACCESS_BLOCK:pipe 沒有足夠的有效空間寫入,將 task push 到 write event list,進入 TASK_WAIT_WRITE 狀態。
    - FILE_ACCESS_ACCEPT:pipe 有足夠的有效空間

  - ``mq_write``:先將請求寫入的長度 push 到 pipe 裡,再將資料 push 到 pipe 裡。寫入完成後,發出 read event pending,讓等待讀取同一個 file descriptor 的 task 可以讀取資料。


**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,則反之。下圖為從輸入到輸出所牽涉的 task 以及使用的 pipe,其中箭頭的起點方執行 write,終點方為 read:

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


**Block File( S_IFBLK )**

- 特性:資料的讀寫以 block 為單位,大小不定,小至1,大至 fd 所提供的 buffer 大小。當需要從硬體讀取資料時,會將一整塊資料放入緩衝區裡,系統在從緩衝區裡取得資料;寫回時也會先將資料放在緩衝區,直到資料滿了在一次寫進硬體中。[#]_
- 資料結構

  .. code-block:: c

    struct block {
        struct file file;           // 所屬的 fd 以及 block file operations
        int driver_pid;              // 所屬 driver ( 就是將此 fd 註冊為 S_IFBLK 的 task ) 的 pid
        struct file *driver_file;   // 所屬 driver 的 fd
        int event;                   // block event ID

        /* request */
        int request_pid;            // 請求 access 此 fd 的 task pid,如果為 0 代表尚未對 driver 發出請求
        int buzy;                   // 為 1 時代表此 fd 正等待 driver 處理,否則為 0
        int pos;                    // block file 的讀寫頭位置
        char buf[BLOCK_BUF];        // buffer

        /* response */
        int transfer_len;           // lseek:driver 計算完的新的讀寫頭位置
    };

    struct block_request {
        int cmd;       // 請求 romdev_driver() 執行的指令
        int task;      // 請求 task 的 pid
        int fd;        // 請求 access 的 fd number
        int size;      // lseek 為指定的讀寫起點;read 和 write 則為讀寫大小
        int pos;       // 詳見 block_request_****able 條目
    };

- Event Handler: ``block_event_release(struct event_monitor *monitor, int event, struct task_control_block *task, void *data)``

  讓 task 重新發送一次請求,請求類別由 stack 中的 r7 值( 0x03: file_write, 0x04: file_read, 0x0a: file_lseek )判定,請求的 file descriptor 資料結構的 reference 存在 data 中。

- block 的請求有 read、write、lseek,分成兩大類,一個是外部對 fd 的請求 ( block_request_ 系列 ),另一個是 driver 對 fd 的請求 ( block_driver_ 系列 )。當外部想要對此 fd 請求時,會先被 block 住,等待 driver 對 fd 發出請求並處理完後,才會讓外部重新發出請求。

- ``block_request_****able``
  - 第一次請求( request_pid 為 0 ):先對 block 所屬的 driver 發出請求,如下表:

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


  將 block_request 的 reference 存到 file_request,透過 IPC 將 file_request 傳給 romdev_driver()。並將 fd 的 request_pid 設為自己的 pid,將 buzy 設為 1,代表對 driver 發出 access 此 fd 的請求。task 會被 push 到該 fd 的 event list 中,進入 TASK_WAIT_WRITE 狀態。

 - 第二次請求( request_pid 為請求 task 的 pid ):由 ``event_monitor_serve()`` 來幫助 task 再次發出請求。如果 driver 已經處理完請求的話 ( buzy 為 0 ) 就會回傳 ``FILE_ACCESS_ACCEPT``。否則就繼續 block 住。

- ``block_driver_****able``
  - FILE_ACCESS_ACCEPT:只有在有 task 想要 access block fd 時 ( buzy = 1 )
  - FILE_ACCESS_ERROR:如果沒有 task 請求 access 該 block fd


- ``block_response``:將指定資料傳送到指定的 block file descriptor,透過這些資訊 file descriptor 可以從 block file 取得資料。

  .. code-block:: c

    struct block_response response = {
        .transfer_len = len,  // 請求讀取的長度
        .buf = buf            // 請求資料的起點
    };


以下將以對 block file 執行 lseek、write、read 指令所牽涉的過程來探討:

- **lseek**:設定 block file 讀寫頭。回傳:新的讀寫頭位置。
  1. ``void lseek(int fd, int offset, int whence);``: **fd** 為對象 file descriptor 的 ID;**offset** 為距離檔案起點的位置( in bytes );**whence** 可以指定使用哪一種設置方式( SEEK_SET, SEEK_CUR, SEEK_END )。
  2. 透過 system call 將請求資訊放到 file_request,並傳送給對象 fd。

  .. code-block:: c

    struct file_request {
        struct task_control_block *task;   // 為請求的 task
        char *buf;     // NULL
        int size;      // offset
        int whence;    // whence:SEEK_SET、SEEK_CUR、SEEK_END
    };

  3. 由於請求的 task 非 driver,所以進行 block_request_lseekable。由於是第一次請求,所以透過 IPC 向 driver 送出 block_request ( 包在 file_request 中 ),並等待 driver 的處理。

  4. driver 收到 BLOCK_CMD_SEEK 指令後,計算好要設定的讀寫頭位置,使用 SEEK_SET ( 直接指定位置 ) 設定讀寫頭:

    + SEEK_SET:直接將讀寫頭設定在 ``offset`` 指定的位置
    + SEEK_END:將讀寫頭設定在 block file 結尾 (EOF) 後 ``offset`` bytes 的位置
    + SEEK_CUR:將讀寫頭設定在 block file 的目前讀寫頭後 ``offset`` bytes 的位置

  .. code-block:: c

    struct file_request {
        struct task_control_block *task;   // driver
        char *buf;     // NULL
        int size;      // 相對於 block file 起點的位置
        int whence;    // SEEK_SET
    };

  5. 現在是由 driver 發出 lseek 的請求,也確認此 fd 正有請求要處理後,執行 ``block_driver_lseek``。block_driver_lseek 會將欲設定讀寫頭的位置存在此 fd 的 transfer_len 欄位,並將 buzy 設回 0,確認處理外部 task 對此 block fd 的請求。送出 block event pending,讓等待處理的 task 繼續執行。
  6. ``event_monitor_serve`` 處理 block event pending 會執行 ``block_event_release``,透過 ``block_event_release`` 使得請求此 fd 的 task 再次 lseek 發出請求。
  7. 外部 task 對於此 fd 為第二次請求,而且 driver 也已經處理完請求,執行 ``block_request_lseek``。
  8. ``block_request_lseek`` 將 driver 所欲設定的值 ( 存在 transfer_len 欄位 ) 複製到此 fd 的 pos 欄位,完成設定讀寫頭的位置。
  9. 將此 fd 的 request_pid 設回 0,代表完成外部 task 的請求,請求 task 會被 push 回 ready list,回傳值為新的讀寫頭位置。

- **read**:以 fd 的讀寫頭為起點,一次讀取指定大小的資料。回傳:實際讀取的資料量
  1. 外部 task 對此 fd 作 read 的請求,執行 ``block_request_readable``。由於是第一次請求,所以透過 IPC 向 driver 送出 block_request,並等待 driver 的處理。
  2. driver 收到 request 後,執行 BLOCK_CMD_READ 指令。driver 會計算讀取的區塊是否有超過 block file 的檔案結尾,如果有就只會讀取到檔案結尾。driver 會將計算結果透過 ``block_response`` 來讓 fd 讀取 block file 的資料。

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

  3. ``block_response`` 使用 system call write 來傳輸資料給指定 fd。來自於 driver 的請求,``block_driver_writable`` 檢查該 fd 是否有收到外部請求,因為在 '1.' 時有發出請求,所以執行 ``block_driver_write``。
  4. ``block_driver_write``:將資料從 block file 複製到 fd 的 buffer 中,將寫入長度存到 fd 的 transfer_len 欄位中,將 buzy 設為 0,表示 driver 處理完請求,並發出 block event pending。
  5. 透過 block event handler ``block_event_release``,被 block 住的 task 再次送出 read 請求。由於 driver 已經處理完請求,所以執行 ``block_request_read``。
  6. ``block_request_read`` 將 fd buffer 中的資料複製到使用者提供的 buffer 中,並更新 fd 的讀寫頭位置。完成讀取 block file。

- **write**:block file 是唯讀的,無法寫入。回傳:-1。
  1. 如果外部對 block file 提出寫入請求,``block_request_write`` 會透過 IPC 傳送資料給 driver,而請求的 task 會被 push 到 block event list。
  2. driver 執行 BLOCK_CMD_WRITE 指令,由於 block file 是唯讀的,所以執行``block_response(fd, NULL, -1);``。
  3. 參考 **write** 指令中,``block_response``的執行過程,將不會讀取任何資料到 fd 的 buffer 中,而使用者的資料也不會被寫入到 block file 中。


**Register File( S_IFREG )**

- 透過 ``romfs_server()`` 註冊的檔案,類型為 S_IFREG ,皆由 ``romfs_server()`` 來管理。亦是一種 block file descriptor,從 block file 讀取資料。
- 資料結構:

  .. code-block:: c

    struct romfs_file {
        int fd;         // fd number
        int device;     // 該檔案資料所在的 fd number
        int start;      // 在檔案資料中的資料起點
        size_t len;     // 在檔案資料中的長度
    };        


  .. code-block:: c

    struct regfile {
        struct file file;           // 所屬的 fd number 以及 regfile operations
        int driver_pid;              // 所屬 driver ( 註冊此 fd 的 task ) 的 pid
        struct file *driver_file;   // 所屬 driver 的 fd file
        int event;                   // regfile event ID

        /* request */
        int request_pid;            // 請求 access 此 fd 的 task pid,如果為 0 代表尚未對 driver 發出請求
        int buzy;                   // 為 1 時代表此 fd 正等待 driver 處理,否則為 0
        int pos;                    // regfile 的讀寫頭位置
        char buf[REGFILE_BUF];        // buffer

        /* response */
        int transfer_len;           // 
    };

- Event Handler: ``int regfile_event_release(struct event_monitor *monitor, int event, struct task_control_block *task, void *data)``

  讓 task 重新發送一次請求,請求類別由 stack 中的 r7 值( 0x03: file_write, 0x04: file_read, 0x0a: file_lseek )判定,請求的 file descriptor 資料結構的 reference 存在 data 中。

- register file 對於 lseek、read、write 的請求一樣可以分成兩大類,一類是外部對於 register file descriptor 的請求 ( regfile_request_ 系列 ),一類是 block file device 對於 reigster file descriptor 的請求 ( regfile_driver_系列 )。但是請求過程與 block file 有些許不同,block file 是直接向 block device 發出請求,而 register file 則是間接透過 ``romfs_server()`` 來向 block device 發出請求。

- ``regfile_request_****able``
  - 第一次請求( request_pid 為 0 ):先對 ``romfs_server()`` 發出請求,如下表:

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


  將 fs_request 的 reference 存到 file_request,透過 IPC 將 file_request 傳給 ``romdev_driver()``。並將 fd 的 request_pid 設為自己的 pid,將 buzy 設為 1,代表對 driver 發出 access 此 fd 的請求。task 會被 push 到該 fd 的 event list 中,進入 TASK_WAIT_WRITE 狀態。

 - 第二次請求( request_pid 為請求 task 的 pid ):由 ``event_monitor_serve()`` 來幫助 task 再次發出請求。如果 driver 已經處理完請求的話 ( buzy 為 0 ) 就會回傳 ``FILE_ACCESS_ACCEPT``。否則就繼續 block 住。


- ``regfile_driver_****able``
  - FILE_ACCESS_ACCEPT:只有在有 task 想要 access regfile fd 時 ( buzy = 1 )
  - FILE_ACCESS_ERROR:如果沒有 task 請求 access 該 regfile fd


- ``regfile_response``:將指定資料傳送到指定的 register file descriptor,透過這些資訊 file descriptor 可以從 block device 取得資料。

  .. code-block:: c

    struct regfile_response response {
        int transfer_len;  // 請求讀取的長度
        char *buf;         // 請求資料的起點
    };


- **lseek**:``void lseek(int fd, int offset, int whence);``:設定檔案讀寫頭的位置,回傳:讀寫頭的新位置
  1. 由外部 task 發出請求,第一次請求存取 regfile fd。``regfile_request_lseekable`` 透過 IPC 將 fs_request 傳送到 ``romfs_server()``,task 會被 push 到 event list 上,進入 TASK_WAIT_WRITE 狀態,等待 driver 的處理。
  2. ``romfs_server()`` 執行 FS_CMD_SEEK 指令。先計算讀寫頭的位置,再使用 SEEK_SET 向請求的 fd 設置讀寫頭。

    - SEEK_SET:為 offset 所指定的位置
    - SEEK_CUR:目前讀寫頭的位置加上 offset
    - SEEK_END:資料終點的位置加上 offset

  3. 由 driver 發出的請求,而且該 fd 正有 task 要求存取,執行``regfile_driver_lseek()``。將已經計算過後新的讀寫頭位置存到該 fd 的 request_len。driver 完成處理,發出 pending 讓外部 task 重新發出請求。
  4. 透過 ``event_monitor_serve()`` ,外部 task 重新發出請求後,是第二次發出請求。``regfile_request_lseek()`` 將 driver 存放在 request_len 的值拿來更新 fd 的讀寫頭位置。
  5. task 再次回到 ready_list 中。

- **read**:讀取指定檔案的資料
  1. 外部 task 對 regfile fd 先發出第一次 read 請求,``regfile_request_readable()`` 透過 IPC 將 fs_request 傳送給 ``romfs_server()``,而外部 task 被 push 到 event list,進入 TASK_WAIT_READ 的狀態,等待 driver 的處理。
  2. ``romfs_server()`` 執行 FS_CMD_READ 指令。先計算在 block device 的讀取區間,然後設定 block fd 的讀寫頭,並透過 block fd 從 block device 讀取指定大小的資料。
  3. 將從 block device 讀取到的資料,寫入到外部請求讀取的 fd 中。由於是 driver 發出的寫入請求,所以執行 ``regfile_driver_write()``,會將 driver 暫存的讀取資料寫到 regfile fd 的 buffer 中,並將寫入長度存到 transfer_len 的欄位中,並發出 event pending。
  4. 透過 ``event_monitor_serve()`` ,外部 task 再度發出 read 請求,是第二次發出請求,所以執行 ``regfile_request_read()``,將存在 regfile fd buffer 的資料複製到使用者提供的 buffer,更新 regfile fd 的讀寫頭位置,並回傳讀取大小。

- **write**:檔案是 read only,所以一定回傳 -1。


File System
============
在 rtenv-plus 中,與檔案系統有關的 task 有三個:

- ``pathserver()``:檔案系統的最上層,負責管理已經註冊( register,或譯暫存 )的檔案路徑及 mount point。
- ``romfs_server()``:向 pathserver() 註冊 ROMFS_TYPE,處理對於擁有 ROMFS_TYPE 的 mount point 的請求。
- ``romdev_driver()``:檔案系統的底層,可以直接取得 block file 的資料。


**初始化**

- ``romfs_server()``:向 pathserver() 註冊 fs type - ROMFS_TYPE,使得 pathserver() 處理向註冊為 ROMFS_TYPE 的 mount point 請求時,可以透過 romfs_server() 來處理。
- ``romdev_driver()``:向 pathserver() 註冊所管理的檔案路徑 ROMDEV_PATH - "/dev/rom0",並將 path 所屬的 file descriptor 設為 block fd,以作為取得 block file 的資料之用。
- first() 中的 ``mount("/dev/rom0", "/", ROMFS_TYPE, 0)``:設置掛載點,對於尋找 ``/`` 目錄下的檔案,會往 ``/dev/rom0`` 來尋找,並透過 ``romfs_server()`` 以及 ``romdev_driver()`` 來取得所需的檔案內容。


**外部檔案資訊**

在 rtenv-plus 中類型為 block file,透過 block file descriptor 來存取。在 ``romdev_driver()`` 中有兩個指標 - ``&_sromdev`` 及 ``&_eromdev``,分別標記 block file 的資料起點及終點。

檔頭資訊的資料結構

.. code-block:: c

    struct romfs_entry {
        uint32_t parent;         // parent file entry 的 offset
        uint32_t prev;           // 同一個 parent 的前一個 child file entry 的 offset
        uint32_t next;           // 同一個 parent 的下一個 child file entry 的 offset
        uint32_t isdir;          // 標記是否為資料夾
        uint32_t len;            // 檔頭後面屬於此檔案的資料長度
        uint8_t name[PATH_MAX];  // 檔案名稱
    };

在 gdb 中可以藉由建立 marco 來取得 block file 所紀錄的資料:

.. code-block:: 

    (gdb) define xxd
    >dump binary memory dump.bin $arg0 $arg1
    >shell xxd dump.bin
    >end
    (gdb) xxd &_sromdev &_eromdev 


marco ``get_entry_at`` 可以從 block file 讀取檔頭資訊並顯示到 gdb 上


.. code-block:: 

    (gdb) define get_entry_at
    >set memcpy( &entry, &_sromdev+$arg0, sizeof(entry) )
    >print entry
    >end
    (gdb) get_entry_at  # arg0 為 offset


檔頭資訊與內容的關係如下圖:

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

如果是目錄,則其資料長度 (len 欄位) 會包含此目錄下的檔案以及子目錄的資訊,如果是檔案,則只會包含自己的檔案內容。


**開啟一個檔案**

``int open(const char *pathname, int flags);``,回傳該檔案的 fd number

1. 透過 IPC 將請求的檔名傳送到 ``pathserver()``。
2. ``pathserver()`` 先尋找已經儲存的檔案路徑,如果沒有則往 mount point 尋找。如果沒有則回傳 -1,有則透過 IPC 向 ``romfs_server()`` 請求開啟此檔案。
3. ``romfs_server()`` 開始找尋檔案資訊:

  1. ``romfs_open()`` 將先將 block fd 的讀寫頭設置在 block file 的資料起點,先讀取 root directory entry。
  2. ``romfs_open_recur()`` 會來取得指定的檔案檔頭。其尋找方式為,如果是目錄,則依次讀取子檔案的檔頭並比對,如果遇到子目錄,則在呼叫一次 ``romfs_open_recur()`` 繼續尋找想要的檔頭。
  3. 如果找到指定的檔案的檔頭會回傳其檔頭起點在 block file 的 offset,如果沒有則回傳 -1。

4. 如果有尋找到檔案,則將檔按路徑註冊到 ``pathserver()``,並且將其 file descriptor 設置為 S_IFREG,設置其在 block device 的讀寫起點,以及可讀取的資料範圍 ( 檔頭中 len 欄位所指的資料長度 ),並回傳該 register fd 的 fd number 給最初呼叫的 task。

  .. code-block:: c

    struct romfs_file {
        int fd;         // fd number
        int device;     // 該檔案資料所在的 fd ( block device ) number
        int start;      // 檔頭所表記的資料起點 ( offset )
        size_t len;     // 檔頭所標記的長度
    };

5. 第一次開啟檔案後,會將路徑存到 ``pathserver()``,讓第二次之後的開檔可以直接在 ``pathserver()`` 中找到並回傳 fd number,節省尋找時間。且第二次作 lseek 是透過 ``romfs_server()`` 而非 ``romdev_driver()``。

**讀取外部檔案資料**

外部檔案資料的 file descriptor 都會被註冊為 register file,所以執行 read 指令時,會透過 ``romfs_server()`` 向 ``romdev_driver()`` 存取 block device 的資料。每個 regfile fd 的讀寫頭起初都會被配置在其檔頭後面所帶的資料起點,如下圖:

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

讀取詳細過程可以參考 register file 條目的 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. 移除範例程式,使用原先硬體
 d. 修改系統參數 ``UART2`` -> ``UART1``
 e. 嘗試 make。
 f. 把不過的部份暫時拿掉,例如 ``romfs``
 
4. 程式死了 
 a. gdb 下去追
 b. 修掉架構相關的 bug
 c. 修改架構所需的常數

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

安裝與測試
----------------
1. 下載 rtenv-plus 程式碼

https://github.com/StanleyDing/rtenv-plus

或

https://github.com/Omar002/rtenv-plus

2. 安裝 st-link

http://github.com/texane/stlink.git

3. 安裝 screen

``sudo apt-get install screen``

4. 把線接好

.. image:: /embedded/rtenv-plus/st-link.jpg
.. image:: /embedded/rtenv-plus/pins.jpg

5. 燒錄 rtenv-plus

 a. 進入 rtenv-plus 資料夾
 b. ``make``
 c. ``make flash``

6. 開啟 screen

``screen /dev/ttyUSB0 115200 8n1``

7. 按下板子上的 reset 按鈕

gdb
=====

1. 如欲使用 gdb ,先安裝 arm toolchain

``sudo apt-get install gcc-arm-none-eabi``

2. 執行 st-util

``sudo st-util``

3. 執行 arm-none-eabi-gdb

 a. 到 rtenv-plus/build/
 b. ``$arm-none-eabi-gdb main.elf``

4. 設定 gdb 目標

 ``(gdb) target remote :4242``

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

.. [#] `Cortex-M3 Execution Modes<http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/ar01s02s06.html>`_

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

.. [#] `File,hackpad,廖健富<https://hackpad.com/RTENV-PLUS-note-8UW04eGdBtt#:h=File>`_

.. [#] `Block devices:drivers and files,hackpad,廖健富<https://hackpad.com/RTENV-PLUS-note-8UW04eGdBtt#:h=Block-Devices:-Drivers-and-Fil>`_