為什麼這系列文章是名為 process switch, 而不是 context switch 呢? 我有自己的想法, 有興趣的朋友可以猜猜看。
雖然基本原理是一樣的, 但細節還是有所不同。這個範例保存所有暫存器, 沒有保存 stack (其實已經可以保存 stack, 不過為了簡單, 還是一步一步來), 而且不談細節。
先來看看 process frame 的資料結構, 從 psr ~ r4 這塊區域是 process frame, 而最上方則是這個 process 的 stack 區域, 由於 stack 會在 proc a 執行時一直變動, 所以 process frame 的位址也會跟著改變, 這是和 x86 有著硬體輔助記住 stack 的機制有所區別。
高 - 高位址
低 - 低位址
process frame 資料結構
高 stack top proc stack addr
proc 可操作的 stack 區域 ...
...
process frame from here psr
pc
lr
r12
r3
r2
r1
r0
r11
r10
r9
r8
r7
r6
r5
低 stack poniter r4
黑色部份在中斷發生時, arm cm3 會自動保存, 綠色部份要靠自己的程式碼負責存起來, 保存到哪裡呢? 目前 sp 指到的位址。
在初始化一個 process 時, 設定好 psr, pc 這兩個欄位即可, ref L31 ~L53。
process_2.S
1 # not process switch, this is context switch
2
3 @.equ STACK_TOP, 0x20000800
4 .text
5 .global _start
6 .code 16
7 .syntax unified
8 _start:
9 .word STACK_TOP, start
10 .type start, function @ let lsb to 1
11
12 .word int_isr+1
13 .word hardf_isr+1
14 .word mmf_isr+1
15 .word int_isr+1
16 .word int_isr+1
17 .word int_isr+1
18 .word int_isr+1
19 .word int_isr+1
20 .word int_isr+1
21 .word svc_isr+1 @ svc isr
22 .word int_isr+1
23 .word int_isr+1
24 .word int_isr+1
25 .word int_isr+1
26 .word int_isr+1
27 .word int_isr+1
28 .word int_isr+1
29
30 start:
31 @ init a proc
32 ldr r0, =proc_a_stack
33
34 @ setup proc a psr
35 mov r1, #0x21000000
36 str r1, [r0, #(-4)]
37
38 @ setup proc a pc
39 ldr r1, =proc_a
40 str r1, [r0, #(-8)]
41
42 @@@@@@@@@@@@@@@@@@@
43
44 @ init b proc
45 ldr r0, =proc_b_stack
46
47 @ setup proc b psr
48 mov r1, #0x21000000
49 str r1, [r0, #(-4)]
50
51 @ setup proc b pc
52 ldr r1, =proc_b
53 str r1, [r0, #(-8)]
54
55 @@@@@@@@@@@@@@@@@@@
56
57 ldr r0, =proc_a_sp
58 ldr r1, =proc_a_stack
59 sub r1, #(16*4)
60 str r1, [r0]
61
62 ldr r0, =proc_b_sp
63 ldr r1, =proc_b_stack
64 sub r1, #(16*4)
65 str r1, [r0]
66
67 ldr r0, =cur_proc
68 mov r1, #0
69 str r1, [r0]
70
71 ldr r1, =proc_a_sp
72 ldr r0, [r1]
73
74 @ ref: scmrtos-code/Ports/CortexM3/GCC/OS_Target_asm.S
75 LDR r4, [r0, #(4 * 14)] @ Load process entry point into R4
76 add r0, #(4 * 16) @ emulate context restore
77 mov sp, r0
78 @ dsb
79 isb @ Insert a barrier
80
81 bx r4 @ run proc_a
82
83 .type proc_a, function @ let lsb to 1
84 proc_a:
85 movs r0, #0x10
86 movs r1, #0x11
87 movs r2, #0x12
88 movs r3, #0x13
89 movs r4, #0x14
90 movs r5, #0x15
91 movs r6, #0x16
92 movs r7, #0x17
93 movs r8, #0x18
94 movs r9, #0x19
95 movs r10, #0x1a
96 movs r11, #0x1b
97 movs r12, #0x1c
98 svc 0
99 nop
100 b proc_a
101
102 .type proc_b, function @ let lsb to 1
103 proc_b:
104 movs r0, #0x20
105 movs r1, #0x21
106 movs r2, #0x22
107 movs r3, #0x23
108 movs r4, #0x24
109 movs r5, #0x25
110 movs r6, #0x26
111 movs r7, #0x27
112 movs r8, #0x28
113 movs r9, #0x29
114 movs r10, #0x2a
115 movs r11, #0x2b
116 movs r12, #0x2c
117 nop
118 svc 0
119 b proc_b
120
121
122 .type del_func, function @ let lsb to 1
123 del_func:
124 mov r5, #5
125 b del_func
126
127 int_isr:
128 mov r0, #67
129 bx lr
130
131 hardf_isr:
132 mov r0, #0x3b
133 bx lr
134 @ memory manage fault
135 mmf_isr:
136 mov r0, #0x3a
137 bx lr
138
139 svc_isr:
140 cpsid i @Prevent interruption during context switch
141
142 push {r4-r11}
143
144 @ choice another process
145 ldr r1, =cur_proc
146 ldr r0, [r1]
147
148
149 cmp r0, #1
150 beq.n 1f
151 @ current proc a, switch to proc b
152 ldr r2, =proc_a_sp
153 str sp, [r2] @ save cur process sp
154
155 mov r3, #1
156 str r3, [r1]
157
158 ldr r0, =proc_b_sp
159
160 b 2f
161 1:
162 @ current proc b, switch to proc a
163 ldr r2, =proc_b_sp
164 str sp, [r2] @ save cur process sp
165
166 mov r3, #0
167 str r3, [r1]
168
169 ldr r0, =proc_a_sp
170
171 2:
172
173 ldr r0, [r0]
174
175 mov sp, r0
176 pop {R4-R11} // Restore r4-11 from new process stack
177
178 cpsie i
179 bx lr
180
181
182 .data
183
184 proc_a_sp:
185 .word 1
186 proc_b_sp:
187 .word 2
188
189 proc_a_ptr:
190 .space 0x300, 0
191 proc_a_stack:
192
193 proc_b_ptr:
194 .space 0x300, 0
195 proc_b_stack:
196
197
198 cur_proc:
199 .word
200
201 .space 0x300, 0
202 STACK_TOP:
再來要記住 process frame 的 stack poniter (proc_a_sp, proc_b_sp 用來記住 r4 那個欄位的位址), ref: L57 ~ L65。
(gdb) p/x proc_a_sp
$1 = 0x200002c8
(gdb) p/x proc_b_sp
$2 = 0x200005c8
L74 ~ L81 在設定 proc a 的 stack (L76, 把 stack frame pointer 加上 4*16 即可), 並跳到 proc_a 執行。
process 切換時都是在操作 stack pointer, 保存目前 process 的 stack pointer, 取用另外一個 process stack pointer, pop r4 ~ r11, 這樣在 bx lr 而之後, 就會跳到 stack frame 的 pc 欄位裡頭的位址。
在 proc_a 執行 svc 0 之後, 跳到 svc_isr 後, stack 變化如下:
0x20000308 -> (svc 0) 0x200002e8 -> (push {r4-r11) 0x200002c8
最後 proc a process frame 內容, 把所有暫存器內容保存起來 (保存到 process frame 那個資料結構), 等著下次輪到 proc a 執行之後回復。
0x200002c8 : 0x14 0x00 0x00 0x00 0x15 0x00 0x00 0x00
0x200002d0 : 0x16 0x00 0x00 0x00 0x17 0x00 0x00 0x00
0x200002d8 : 0x18 0x00 0x00 0x00 0x19 0x00 0x00 0x00
0x200002e0 : 0x1a 0x00 0x00 0x00 0x1b 0x00 0x00 0x00
0x200002e8 : 0x10 0x00 0x00 0x00 0x11 0x00 0x00 0x00
0x200002f0 : 0x12 0x00 0x00 0x00 0x13 0x00 0x00 0x00
0x200002f8 : 0x1c 0x00 0x00 0x00 0xff 0xff 0xff 0xff
0x20000300 : 0xc0 0x00 0x00 0x00 0x00 0x00 0x00 0x01
0x20000308 : 0x08 0x03 0x00 0x20
綠色部份是 r4 ~ r11, 紅色部份是 r0~ r3, r12, lr, pc, psr。
再來載入 proc b stack poniter, 離開 svc_isr 後, 會執行 proc_b, 再來就是一樣的故事了, 只是主角換成 proc b。
在 proc_b svc 0 之後, 跳到 svc_isr 後, stack 變化如下:
0x20000608 -> (svc 0) 0x200005e8 -> (push {r4-r11) 0x200005c8
手工示意圖
我可能沒有寫得很好, 真要體會, 請在 arm cortex m3 平台跑一次, 才算真懂, qemu 似乎無法模擬 svc 的行為, 需要在真的硬體上測試。
「
下一篇 」會說明最後的動作, 保存 stack, 這才算是功德圓滿。
source code:
https://github.com/descent/stm32f4_prog
git commit: b3ed8870ce3dbc50d55909a81f7b4e5af50f9a7d
Note
When changing the stack pointer, software must use an ISB instruction immediately after
the MSR instruction. This ensures that instructions after the ISB instruction
execute using the new stack pointer. See ISB
感謝 Wen:
Cortex-M4 Devices Generic User Guide
參考資料:
STM32F207 高性能网络型 MCU 嵌入式系统设计 (15.2.2)。
scmrtos source code, 以下所列的程式碼為我所參考的部份。
為什麼我不去看較為流行的 freertos 而要看 scmrtos 呢? 除了該書的原因之外, 還因為 scmrtos 是 c++ 寫的, 我打算用 c++ 實作 simple_os for cm3 版本, 並用上 c++11 的新特性, 晤 ... 難度不低。
descent@descent-u:scmrtos-code-git$ git log
commit c8e243cfc666ed7230a874b951f2e719e8f9335c
Author: evgenynest <evgenynest@25b526ac-3511-40f3-916a-959e9aa89f3e>
Date: Thu Aug 22 05:32:42 2013 +0000
git-svn-id: svn://svn.code.sf.net/p/scmrtos/code/trunk@581 25b526ac-3511-40f3-916a-959e9aa89f3e
這是幾個重點 function:
158 void os_start(stack_item_t* sp)
053 void TBaseProcess::init_stack_frame( stack_item_t * Stack
612 stack_item_t* OS::TKernel::context_switch_hook(stack_item_t* sp)
119 PendSVC_ISR:
OS_Target_asm.S
1 //******************************************************************************
2 //*
3 //* FULLNAME: Single-Chip Microcontroller Real-Time Operating System
4 //*
5 //* NICKNAME: scmRTOS
6 //*
7 //* PROCESSOR: ARM Cortex-M3
8 //*
9 //* TOOLKIT: ARM GCC
10 //*
11 //* PURPOSE: Target Dependent Low-Level Stuff
12 //*
13 //* Version: 4.00
14 //*
15 //* $Revision$
16 //* $Date:: $
17 //*
18 //* Copyright (c) 2003-2012, Harry E. Zhurov
19 //*
20 //* Permission is hereby granted, free of charge, to any person
21 //* obtaining a copy of this software and associated documentation
22 //* files (the "Software"), to deal in the Software without restriction,
23 //* including without limitation the rights to use, copy, modify, merge,
24 //* publish, distribute, sublicense, and/or sell copies of the Software,
25 //* and to permit persons to whom the Software is furnished to do so,
26 //* subject to the following conditions:
27 //*
28 //* The above copyright notice and this permission notice shall be included
29 //* in all copies or substantial portions of the Software.
30 //*
31 //* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 //* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 //* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
34 //* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
35 //* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
36 //* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
37 //* THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 //*
39 //* =================================================================
40 //* See http://scmrtos.sourceforge.net for documentation, latest
41 //* information, license and contact details.
42 //* =================================================================
43 //*
44 //******************************************************************************
45 //* Ported by Andrey Chuikin, Copyright (c) 2008-2012
46 //* Ported to GCC Ivan A-R <ivan@tuxotronic.org> (l) 2008
47 //* gcc port by Anton B. Gusev aka AHTOXA, Copyright (c) 2009-2012
48
49 #include "scmRTOS_TARGET_CFG.h"
50
51 //-----------------------------------------------------------------------------
52 // CODE GENERATION DIRECTIVES
53 //
54 // RSEG CODE:CODE(2)
55 .cpu cortex-m3
56 .fpu softvfp
57 .syntax unified
58 .thumb
59 .text
60 .align 4
61
62
63 //-----------------------------------------------------------------------------
64 // EQUATES
65 //
66 .equ NVIC_SYSPRI14 , 0xE000ED22 // System priority register (priority 14).
67 .equ NVIC_PENDSV_PRI , 0xFF // PendSV priority value (lowest).
68 .equ NVIC_SYSPRI15 , 0xE000ED23 // System priority register (priority 15).
69 .equ NVIC_ST_PRI , 0xFF // SysTick priority value (lowest).
70
71 .equ NVIC_ST_CTRL , 0xE000E010 // SysTick Ctrl & Status Reg.
72 .equ NVIC_ST_RELOAD , 0xE000E014 // SysTick Reload Value Reg.
73 .equ NVIC_ST_CTRL_CLK_SRC , 0x00000004 // Clock Source.
74 .equ NVIC_ST_CTRL_INTEN , 0x00000002 // Interrupt enable.
75 .equ NVIC_ST_CTRL_ENABLE , 0x00000001 // Counter mode.
76
77
78 //-----------------------------------------------------------------------------
79 // PUBLIC FUNCTIONS
80 //
81 .section .text,"ax"
82 .code 16
83
84 .extern os_context_switch_hook
85
86 .global PendSVC_ISR
87 .global os_start
88
89 //-----------------------------------------------------------------------------
90 // HANDLE PendSV EXCEPTION
91 // void PendSVC_ISR(void)
92 //
93 // Note(s) : 1) PendSV is used to cause a context switch. This is a recommended method for performing
94 // context switches with Cortex-M3. This is because the Cortex-M3 auto-saves half of the
95 // processor context on any exception, and restores same on return from exception. So only
96 // saving of R4-R11 is required and fixing up the stack pointers. Using the PendSV exception
97 // this way means that context saving and restoring is identical whether it is initiated from
98 // a thread or occurs due to an interrupt or exception.
99 //
100 // 2) Pseudo-code is:
101 // a) Get the process SP
102 // b) Save remaining regs r4-r11 on process stack;
103 // c) Call os_context_switch_hook for save current task SP and get new task SP;
104 // d) Restore R4-R11 from new process stack;
105 // e) Perform exception return which will restore remaining context.
106 //
107 // 3) On entry into PendSV handler:
108 // a) The following have been saved on the process stack (by processor):
109 // xPSR, PC, LR, R12, R0-R3
110 // b) Processor mode is switched to Handler mode (from Thread mode)
111 // c) Stack is Main stack (switched from Process stack)
112 //
113 // 4) Since PendSV is set to lowest priority in the system (by os_start() below), we
114 // know that it will only be run when no other exception or interrupt is active, and
115 // therefore safe to assume that context being switched out was using the process stack (PSP).
116 //
117
118 .thumb_func
119 PendSVC_ISR:
120 CPSID I // Prevent interruption during context switch
121 MRS R0, PSP // PSP is process stack pointer
122 STMDB R0!, {R4-R11} // Save remaining regs r4-11 on process stack
123 // At this point, entire context of process has been saved
124
125 PUSH {LR} // we must save LR (exc_return value) until exception return
126 BL os_context_switch_hook // call os_context_switch_hook();
127
128 // R0 is new process SP;
129 LDMIA R0!, {R4-R11} // Restore r4-11 from new process stack
130 MSR PSP, R0 // Load PSP with new process SP
131 CPSIE I
132 POP {PC} // Return to saved exc_return. Exception return will restore remaining context
133
134 .align 2
135
136 //-----------------------------------------------------------------------------
137 // Initialize system timer.
138 // void init_system_timer()
139 // Perform systick timer initialization.
140 //
141 .weak __init_system_timer
142 __init_system_timer:
143
144 LDR R1, =NVIC_SYSPRI15 // Set the SysTick exception priority (lowest)
145 LDR R2, =NVIC_ST_PRI
146 STRB R2, [R1]
147
148 LDR R1, =NVIC_ST_RELOAD // Setup SysTick
149 LDR R2, =(SYSTICKFREQ/SYSTICKINTRATE-1)
150 STR R2, [R1]
151 LDR R1, =NVIC_ST_CTRL // Enable and run SysTick
152 LDR R2, =(NVIC_ST_CTRL_CLK_SRC | NVIC_ST_CTRL_INTEN | NVIC_ST_CTRL_ENABLE)
153 STR R2, [R1]
154 BX LR
155
156 //-----------------------------------------------------------------------------
157 // START MULTITASKING
158 // void os_start(stack_item_t* sp)
159 //
160 // Note(s) : 1) os_start() MUST:
161 // a) Setup PendSV and SysTick exception priority to lowest;
162 // b) Setup SysTick (reload value);
163 // c) Enable interrupts (tasks will run with interrupts enabled).
164 // d) Jump to exec() function of the highest priority process.
165 //
166 .thumb_func
167 os_start:
168 LDR R1, =NVIC_SYSPRI14 // Set the PendSV exception priority (lowest)
169 LDR R2, =NVIC_PENDSV_PRI
170 STRB R2, [R1]
171
172 LDR R4, [R0, #(4 * 14)] // Load process entry point into R4
173 ADD R0, #(4 * 16) // emulate context restore
174 MSR PSP, R0 // store process SP to PSP
175 MOV R0, #2 // set up the current (thread) mode: use PSP as stack pointer, privileged level
176 MSR CONTROL, R0
177 ISB // Insert a barrier
178
179 BL __init_system_timer // run system timer
180
181 CPSIE I // Enable interrupts at processor level
182
183 BX R4 // Jump to process exec() function
184
185 .end
Ports/CortexM3/GCC/OS_Target_cpp.cpp
47
48
49 #include <scmRTOS.h>
50
51 using namespace OS;
52
53 void TBaseProcess::init_stack_frame( stack_item_t * Stack
54 , void (*exec)()
55 #if scmRTOS_DEBUG_ENABLE == 1
56 , stack_item_t * StackBegin
57 #endif
58 )
59 {
60 // ARM Architecture Procedure Call Standard [AAPCS] requires 8-byte stack alignment:
61 StackPointer = (stack_item_t*)((uintptr_t)Stack & 0xFFFFFFF8);
62 *(--StackPointer) = 0x01000000L; // xPSR
63 *(--StackPointer) = reinterpret_cast<uint32_t>(exec); // Entry Point
64 StackPointer -= 14; // emulate "push R14,R12,R3,R2,R1,R0,R11-R4"
65
66 #if scmRTOS_DEBUG_ENABLE == 1
67 for (stack_item_t* pDst = StackBegin; pDst < StackPointer; pDst++)
68 *pDst = STACK_DEFAULT_PATTERN;
69 #endif // scmRTOS_DEBUG_ENABLE
70 }
71
72 #pragma weak SystemTimer_ISR = Default_SystemTimer_ISR
73
74 namespace OS
75 {
76 extern "C" void Default_SystemTimer_ISR();
77 }
78
79 //------------------------------------------------------------------------------
80 OS_INTERRUPT void OS::Default_SystemTimer_ISR()
81 {
82 scmRTOS_ISRW_TYPE ISR;
83
84 #if scmRTOS_SYSTIMER_HOOK_ENABLE == 1
85 system_timer_user_hook();
86 #endif
87
88 Kernel.system_timer();
89
90 #if scmRTOS_SYSTIMER_NEST_INTS_ENABLE == 0
91 DISABLE_NESTED_INTERRUPTS();
92 #endif
93 }
94 //------------------------------------------------------------------------------
95
Common/OS_Kernel.h
607 //------------------------------------------------------------------------------
608 #ifndef CONTEXT_SWITCH_HOOK_CRIT_SECT
609 #define CONTEXT_SWITCH_HOOK_CRIT_SECT()
610 #endif
611
612 stack_item_t* OS::TKernel::context_switch_hook(stack_item_t* sp)
613 {
614 CONTEXT_SWITCH_HOOK_CRIT_SECT();
615
616 ProcessTable[CurProcPriority]->StackPointer = sp;
617 sp = ProcessTable[SchedProcPriority]->StackPointer;
618
619 #if scmRTOS_CONTEXT_SWITCH_USER_HOOK_ENABLE == 1
620 context_switch_user_hook();
621 #endif
622
623 CurProcPriority = SchedProcPriority;
624 return sp;
625 }
626 //------------------------------------------------------------------------------
627 #endif // scmRTOS_CONTEXT_SWITCH_SCHEME
628
629 //-----------------------------------------------------------------------------
630 bool OS::TBaseProcess::is_sleeping() const
631 {
632 TCritSect cs;
633 return this->Timeout != 0;
634 }
635 //-----------------------------------------------------------------------------
636 bool OS::TBaseProcess::is_suspended() const
637 {
638 TCritSect cs;
639 return (Kernel.ReadyProcessMap & get_prio_tag(this->Priority)) == 0;
640 }
641
642 //-----------------------------------------------------------------------------
643 INLINE void OS::run()
644 {
645 stack_item_t *sp = Kernel.ProcessTable[pr0]->StackPointer;
646 os_start(sp);
647 }
648
649 #if scmRTOS_OBSOLETE_NAMES == 1
650 namespace OS
651 {
652 INLINE void Sleep(timeout_t t = 0) { sleep(t); }
653
654 INLINE bool IsProcessSleeping(const TBaseProcess& p) { return p.is_sleeping(); }
655 INLINE bool IsProcessSuspended(const TBaseProcess& p) { return p.is_suspended(); }
656 } // namespace OS
657 #endif
658
659 #include <OS_Services.h>
660
661 #endif // OS_KERNEL_H
也可以參考這個檔案:scmrtos-code-git/Ports/CortexM4F/GCC/OS_Target_cpp.cpp
沒有留言:
張貼留言
使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。
我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。