版本 65d14e75e542be5db36b7c80e01bbdebd22ac475
Changes from 65d14e75e542be5db36b7c80e01bbdebd22ac475 to f8be48fc529ad2a2ae34d2859f17852058e90c00
---
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
.. image:: /embdded/rtenv-plus/kernel_mode_vs_user_mode.PNG
注:在下方 ``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 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 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 就開始運行了。
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``:cortex-M4 系統時鐘為 180MHz
.. code-block:: c
SysTick_Config(configCPU_CLOCK_HZ / configTICK_RATE_HZ);
configCPU_CLOCK_HZ 為 72MHz,
configTICK_RATE_HZ 為 100,
( 72M/100 ) * 180M = 4m,每經過 4ms 就會觸發一次 SysTick_Handler
- ``USART2_IRQHandler``
- ``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
============
- 功能:將 syscall 代碼存入 ``R7``,觸發 SVC exception,將代碼存入 Processor stack,並轉換成 Kernel Mode 後,在 ``main()`` 中處理 system call。
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 )**
- 資料結構
.. 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 時代表有 task 正在請求 access,否則為 0
int pos; // 等等我補一下
int pos; // block file 的讀寫頭位置
char buf[BLOCK_BUF]; // buffer
/* response */
int transfer_len; // 有著不同的意義XD
};
struct block_request {
int cmd; // 請求 romdev_driver() 執行的指令
int task;
int fd;
int size;
int pos;
};
- 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 )判定,請求的 fd 透過 data 來存取。
- block 的請求有 read、write、lseek,分成兩大類,一個是外部對 fd 的請求,另一個是 driver 對 fd 的請求。當外部想要對此 fd 請求時,會先被 block 住,等待 driver 對 fd 發出請求並處理完後,才會讓外部重新發出請求。
- ``block_request_****able``
- 第一次請求( request_pid 為 0 ):先對 block 所屬的 driver 發出請求,如下表:
.. image:: /embedded/rtenv-plus/block_request.PNG
File System
============
在 rtenv-plus 中,與檔案系統有關的 task 有三個:
- ``pathserver()``:檔案系統的最上層,負責管理已經註冊( register,或暫存 )的檔案路徑及 mount point。
- ``romfs_server()``:向 pathserver() 註冊 ROMFS_TYPE,處理對於擁有 ROMFS_TYPE 的 mount point 的請求。
- ``romdev_driver()``:檔案系統的底層,取得檔案的資訊與資料。
**初始化**
- ``romfs_server()``:向 pathserver() 註冊 fs type - ROMFS_TYPE,使得 pathserver() 處理向註冊為 ROMFS_TYPE 的 mount point 請求時,可以透過 romfs_server() 來處理。
- ``romdev_driver()``:向 pathserver() 註冊所管理的檔案路徑 ROMDEV_PATH,並設置該 path 所屬的 file descriptor,以作為取得檔案資訊之用。
- first() 中的 ``mount("/dev/rom0", "/", ROMFS_TYPE, 0)``:設置掛載點,對於尋找 ``/`` 目錄下的檔案,會往 ``/dev/rom0`` 來尋找,並透過 ``romfs_server()`` 以及 ``romdev_driver()``。
移植過程
-----------
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>`_
.. [#] `WikiPedia: Event Monitoring<http://en.wikipedia.org/wiki/Event_monitoring>`_
.. [#] `File,RTENV-PLUS note,hackpad,廖健富<https://hackpad.com/RTENV-PLUS-note-8UW04eGdBtt#:h=File>`_