|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
+ l' Z/ r( w$ r0 V- k/ `1 rcortex m3具有用户级和特权级两种特权等级 以及 handle模式(处理中断时运行在handle模式)和线程模式 两种模式,其中用户级特权只能运行在线程模式。, d& u F+ b# |
" S3 ]! E P- h6 F9 k6 x* U如下图(摘自cortex m3权威指南):5 Z% N1 q+ n" b; r% E3 I9 b
; M r! x) o5 ^ V- c
3 N7 v2 e& {9 Y) T: f& m9 e) e. u* U4 u! d+ e
经过测试rtthread所有的代码都是运行在特权级下的,只不过开机启动的时候特权级线程模式使用的堆栈指针是MSP,调用rt_system_scheduler_start()函数之后 开始线程调度,这些线程包括用户自定义的线程,以及main()函数线程,以及空闲线程也都是特权级,只不过它们使用的堆栈指针是PSP,为了区分,我下面的博客把这种使用PSP寄存器的特权级线程,称之为用户级线程,但要注意实际上它们是特权级的(这个freertos不一样,freertos里面的这种线程都是运行在用户级的)。另外handle模式也就是中断处理函数都是特权级的无法改变,并且使用的都是MSP。证据如下:
8 a. f2 o, q0 y. u+ E1 P$ \( U4 k* h% J( m. H1 ?! b
, K0 P! B' b% s/ G: f, r# p8 y
/ L9 P3 y4 K5 U; `1 E0 p. `# U
' l5 B/ ?7 M0 i0 W2 q6 {' ?7 F$ v2 X
f7 @% E& O; f- _rt_system_scheduler_start()函数在触发PendSV中断之后,会通过BX LR语句,进行返回,返回后的处理器状态决定于LR寄存器中保存的EXC_RETURN的值,可以看到通过PendSV通过ORR指令把bit2 置1,也就是使用PSP,但并没有改变处理器运行的特权级。个人觉得如果想切换到用户级,需要在调用触发PendSV中断之前,手动修改CONTROL寄存器,但rtthread并没有修改。
/ b2 p) q, a! ]; s# O: M5 q: W* [: u
* u' w3 v/ a# ?; s; m, ]% l( _所以rtthread用户自定义的线程也可以访问所有的特殊寄存器,例如读写PRIMASK寄存器,关闭全局中断等,并且rtthread好像从来没有使用过SVC指令进行系统调用,因为不需要,它本来就是特权级。
5 h+ A3 p+ l, h
% l" V8 }1 X* x9 A+ }! J9 ]: i9 Pcortex-m3的soc启动之后(这里以stm32芯片为例),默认运行在特权级的线程模式,+ S* g/ s S( [/ W* D% _
2 N9 b- V% N# t% R9 o
+ r* t# I' z6 S3 c2 O4 c) Z, q, {
/ L% K) \$ R$ g! k图片来源rtthread官网
. C( j5 l* e/ k( f' O2 f% I9 Y6 }- u$ q/ s0 g# \! W# ?6 A' W
在芯片启动后运行完汇编启动文件(主要是初始化MSP堆栈寄存器,以及运行复位中断处理函数,完成初始化),会跳转到$Sub$$main()函数,并按照上图调用rtthread_startup()函数,注意这里全部运行在特权级的线程模式。其中rt_application_init()会初始化用户级线程也就是我们用户自己编写的main()函数,但是初始化之后并没有运行,知道rt_system_scheduler_start()函数调用之后,打开调度器,开始运行用户级线程,特权级线程至此结束,之后的特权级基本运行在handle模式而不是线程模式。
; ~. Q* c: u* ]+ @0 |$ f& A$ H9 |9 Q& _; N; E
下面我们来分析rt_system_scheduler_start()函数:
: } a1 t' Z* \
- c1 K0 ]! M5 I& b, g2 ]- void rt_system_scheduler_start(void)
- {
- register struct rt_thread *to_thread;
- rt_ubase_t highest_ready_priority;
- /* 其实到这里也就两个用户级线程,一个main函数,一个空闲线程,这里to_thread肯定是前面
- * rt_application_init()函数所初始化的main函数线程
- */
- to_thread = _get_highest_priority_thread(&highest_ready_priority);
- #ifdef RT_USING_SMP
- to_thread->oncpu = rt_hw_cpu_id();
- #else
- rt_current_thread = to_thread;
- #endif /*RT_USING_SMP*/
- rt_schedule_remove_thread(to_thread);
- to_thread->stat = RT_THREAD_RUNNING;
- /* switch to new thread */
- #ifdef RT_USING_SMP
- rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp, to_thread);
- #else
- /*最终调用了以下函数,这个函数好像只在这里调用了一次,由于首次调用,所以不用指定from线程
- *因为运行到这里仍然是特权级的线程模式,并没有用户级线程在运行,所以from线程还不存在,也不用
- *为from线程保存现场
- */
- rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);
- #endif /*RT_USING_SMP*/
- /* never come back */
- }
0 n% W: q% h% N$ d1 G! g7 s+ J6 K
3 P# L& k# h1 W( T3 g- ~! H7 v$ D% G( c: V# M
rt_hw_context_switch_to()函数是汇编函数如下:2 [' P: V, }3 E4 E- a5 m) E2 }: f
- @. g: o- b$ P8 \6 y0 W& S( T
- rt_hw_context_switch_to:
- LDR R1, =rt_interrupt_to_thread
- STR R0, [R1]
- /* set from thread to 0 */
- LDR R1, =rt_interrupt_from_thread
- MOV R0, #0
- STR R0, [R1]
- /* set interrupt flag to 1 */
- LDR R1, =rt_thread_switch_interrupt_flag
- MOV R0, #1
- STR R0, [R1]
- /* set the PendSV exception priority */
- LDR R0, =SHPR3
- LDR R1, =PENDSV_PRI_LOWEST
- LDR.W R2, [R0,#0] /* read */
- ORR R1, R1, R2 /* modify */
- STR R1, [R0] /* write-back */
- LDR R0, =ICSR /* trigger the PendSV exception (causes context switch) */
- LDR R1, =PENDSVSET_BIT
- STR R1, [R0]
- /* restore MSP */
- /*重置MSP寄存器为中断向量表中指定的值,因为特权级线程模式执行到这里也就到头了,永远也不会返回
- *了,所以可以重置MSP寄存器清空堆栈,供以后中断处理函数使用
- */
- LDR r0, =SCB_VTOR
- LDR r0, [r0]
- LDR r0, [r0]
- NOP
- MSR msp, r0
- /* enable interrupts at processor level */
- /* 打开中断之后会立即调到PendSV中断处理函数,用来切换到main函数线程*/
- CPSIE F
- CPSIE I
- /* never reach here! */ /* 根据官方注释可知,永远不会到达这里了 */- c/ k$ W/ m& q% K
8 V# M# F1 P* Y! q0 p2 |0 `
7 h8 n: z/ o! }9 I- A6 G0 i9 C9 m4 G7 [& S/ E3 o# r
PendSV_Handler中断函数如下:
# ?! E+ S" n( F3 F7 [1 X' A0 D7 d4 ~/ a+ r1 T
- PendSV_Handler:
- /* disable interrupt to protect context switch */
- MRS R2, PRIMASK
- CPSID I
- /* get rt_thread_switch_interrupt_flag */
- LDR R0, =rt_thread_switch_interrupt_flag
- LDR R1, [R0]
- CBZ R1, pendsv_exit /* pendsv aLReady handled */
- /* clear rt_thread_switch_interrupt_flag to 0 */
- MOV R1, #0
- STR R1, [R0]
- LDR R0, =rt_interrupt_from_thread
- LDR R1, [R0]
- CBZ R1, switch_to_thread /* skip register save at the first time */
- MRS R1, PSP /* get from thread stack pointer */
- STMFD R1!, {R4 - R11} /* push R4 - R11 register */
- LDR R0, [R0]
- STR R1, [R0] /* update from thread stack pointer */
- switch_to_thread:
- LDR R1, =rt_interrupt_to_thread
- LDR R1, [R1]
- LDR R1, [R1] /* load thread stack pointer */
- LDMFD R1!, {R4 - R11} /* pop R4 - R11 register */
- MSR PSP, R1 /* update stack pointer */
- pendsv_exit:
- /* restore interrupt */
- MSR PRIMASK, R2
- ORR LR, LR, #0x04 /* 这句话保证PendSV中断返回之后,自动运行在用户级线程模式,其实是特权级线程模式 */
- BX LR* a) a' Z E- s7 `2 ~
" V/ z% k+ X5 K4 e- {# ?! Q( C: _
3 g! n6 R; E2 H7 [! x! A' H$ M 可以看到执行完rt_hw_context_switch_to()函数,特权级线程模式到此结束,PendSV中断会返回用户级线程模式,开始main函数 用户级线程,在main()为用户自定义函数,用户可以在main()中初始化和启动其他用户级线程,之后系统会借助实时时钟的中断、以及rt_thread_sleep()、rt_mq_recv()等函数实现用户级线程切换,其实都是通过调用rt_schedule()实现线程切换的,rt_schedule()函数的实质也是通过出发PendSV中断,来实现线程切换的,也就是之后无论是在用户级线程模式(用户级线程主动调用rt_thread_sleep()、rt_mq_recv()等函数请求切换线程),还是特权级handle模式(实时时钟中断中请求切换线程),都通过触发PendSV中断来实现线程切换,也就是处理器好像再也没有回到特权级线程模式了,都是在特权级handle模式。7 A- D) P/ \/ J7 ?- `/ ^8 O0 l# ~3 F
9 V9 d4 q2 W9 _3 p6 V
这里贴出来rt_schedule()的代码:
8 a7 S( S8 m4 p! H3 Z
# h6 L2 A8 z. {! V- void rt_schedule(void)
- {
- rt_base_t level;
- struct rt_thread *to_thread;
- struct rt_thread *from_thread;
- /* disable interrupt */
- level = rt_hw_interrupt_disable();
- /* check the scheduler is enabled or not */
- if (rt_scheduler_lock_nest == 0)
- {
- rt_ubase_t highest_ready_priority;
- if (rt_thread_ready_priority_group != 0)
- {
- /* need_insert_from_thread: need to insert from_thread to ready queue */
- int need_insert_from_thread = 0;
- to_thread = _get_highest_priority_thread(&highest_ready_priority);
- if ((rt_current_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING)
- {
- if (rt_current_thread->current_priority < highest_ready_priority)
- {
- to_thread = rt_current_thread;
- }
- else
- {
- need_insert_from_thread = 1;
- }
- }
- if (to_thread != rt_current_thread)
- {
- /* if the destination thread is not the same as current thread */
- rt_current_priority = (rt_uint8_t)highest_ready_priority;
- from_thread = rt_current_thread;
- rt_current_thread = to_thread;
- RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));
- if (need_insert_from_thread)
- {
- rt_schedule_insert_thread(from_thread);
- }
- rt_schedule_remove_thread(to_thread);
- to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);
- /* switch to new thread */
- RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
- ("[%d]switch to priority#%d "
- "thread:%.*s(sp:0x%08x), "
- "from thread:%.*s(sp: 0x%08x)\n",
- rt_interrupt_nest, highest_ready_priority,
- RT_NAME_MAX, to_thread->name, to_thread->sp,
- RT_NAME_MAX, from_thread->name, from_thread->sp));
- #ifdef RT_USING_OVERFLOW_CHECK
- _rt_scheduler_stack_check(to_thread);
- #endif
- if (rt_interrupt_nest == 0)
- {
- extern void rt_thread_handle_sig(rt_bool_t clean_state);
- rt_hw_context_switch((rt_ubase_t)&from_thread->sp,
- (rt_ubase_t)&to_thread->sp);
- /* enable interrupt */
- rt_hw_interrupt_enable(level);
- #ifdef RT_USING_SIGNALS
- /* check signal status */
- rt_thread_handle_sig(RT_TRUE);
- #endif
- goto __exit;
- }
- else
- {
- RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
- rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp,
- (rt_ubase_t)&to_thread->sp);
- }
- }
- else
- {
- rt_schedule_remove_thread(rt_current_thread);
- rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);
- }
- }
- }
- /* enable interrupt */
- rt_hw_interrupt_enable(level);
- __exit:
- return;
- }
% f0 ? O W' G/ Z( v; X
$ o% ]# R: ?7 N& i0 J: a+ _6 C5 e! e2 s# u% M6 U9 h3 Q8 S
& Y. w3 z e k, _/ t: U2 A/ l2 _7 o9 l1 L
|
|