版本 ed2499d026ed42b16640147b9e6fb547a386ea75
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, 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 了。
- L42,43:將
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()
後繼續執行的指令位址。::
[ 從 user_thread_stack pop 到 general-purpose registers ] user_thread_stack -> |r4 |r5 |r6 |r7 |r8 |r9 |r10|fp |_lr|… V 依 pop 順序到 register -> |R4 |R5 |R6 |R7 |R8 |R9 |R10|R11| LR|…
利用
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()
,同時會 processor 會將 xPSR、PC、LR、R12、R3、R2、R1、R0[#]_依序 push 到目前的 stack 中 ( process stack ),被 push 到 process stack 的資訊中含有離開fork()
後繼續執行的指令位址。其中,在register 交換,原本的
LR
含有 exception return ( 0xfffffffd ) 的資訊,也會一併被 push 到 process stack 中儲存。::
[ 因為 Exception 所發生的 push register 到 stack ] user_thread_stack -> …|_r7|r0|r1|r2|r3|ip |lr|pc|xpsr|stack… 低位址 高位址 依 push 順序到 stack -> …….|R0|R1|R2|R3|R12|LR|PC|xPSR| ( _r7 存的是在 syscall 中被暫存的 R7 值 )
SVC_Handler()
中會將目前的 user state ( push 到 process stack ) 與 kernel state ( 從 main stack pop 出來 ) 作交換,此時LR
擁有的位址為離開 activate() 後要執行的指令位址。所以離開SVC_Handler()
後,程式會轉往執行 while loop,而在SVC_Handler()
中,也將在fork()
中存入 R7 的值 push 到 process stack 中了。所以接著會判定要進行 fork 動作。
進行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。Exception return:當 EXC_RETURN 值為 0xfffffffd 時,
- processor 會轉回 thread mode
- 從 process stack 取回 exception 時所 push 進去的 registers
- 使用 PSP 為當下的 SP
也就是說,進行 exception return 後,
PC
會擁有之前 exception 發生的的下一行指令位址,至於LR
則為離開 fork() 而回到 first() 繼續執行的位址。::
[ 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()。
子task
- 在
activate()
與 exception return 的行為與母 task 相同,只是在if(!fork())
判定中為true (因為回傳值為 0),所以就會進行 if 下的函式,於是新的 task 就開始運行了。
System Call
- 功能:將 syscall 代碼存入
R7
,觸發 SVC exception,轉換成 Kernel Mode 後,在main()
中處理 system call。
硬體驅動原理
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>
_