打印

stm32移植日志之六----上下文切换

[复制链接]
4795|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
djyos|  楼主 | 2009-9-4 22:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 djyos 于 2009-9-5 07:45 编辑


看本文请参考《都江堰操作系统与嵌入式系统设计》第15章,该书可在www.djyos.com下载。
写这一篇的时候,djyosstm32上已经跑起来了,只是串口驱动还没有写,不能输出而已。从之一到之五,东扯西扯了很多东西,包括移植方法,中断系统实现方法,上下文设计、开发工具等,除了在之三列出了初始化代码外,其他几篇都没有代码。这是因为代码一直在调试中,变化很大,写出来也是白搭,这不,之三列出的代码,就是针对gcc环境的,在mdk下全改了。写移植日志,如果没有代码的话,原理讲得再多,也是纸上谈兵,现在系统跑起来了,底层代码基本不会变了,我们就结合代码来讲解一下。

移植djyos,需要修改的代码主要有:中断相关代码、cpu硬件初始化代码、上下文操作代码等。djyos代码在所有可能需要在移植时修改的代码中,均有“移植关键”字样的注释。
我们先对比arm7来讲解一下cm3中上下文切换。
1、设置初始上下文

线程的上下文指线程执行环境的当前状态,其实也就是影响该线程执行的cpu寄存器的状态,在cm3中,这些寄存器一共有17个:R0-R15R15PC)和xpsr,但从之四上的那张图上,我们并没有看到R13(即SP),那是因为上下文保存在当前线程的栈的当前位置,该位置是变化的,而恢复上下文又必须先得到SP才可以,所以SP只能保存在某一个固定的位置,而不能保存在栈中。djyosSP保存在当前线程虚拟机的线程控制块中。
线程执行之前,我们必须为它准备好上下文,也就是初始上下文,djyos中,所有线程都是由函数__djy_vm_engine发动的,该函数有一个参数,即线程所属事件的处理函数指针,根据aapcs规定,该参数保存在R0中。因此,初始上下文中只有R0PC是有效的,xpsr应该给一个合法的初始值外,其他寄存器可以用任意值,所以,初始上下文设置为:
R0:线程所属事件的处理函数指针。
PC__djy_vm_engine函数的地址
xpsr0x01000000thumb状态,清掉其他所有标志。
其他寄存器:任意。
__asm_reset_thread函数完成线程初始上下文设置,意为复位线程到初始状态。函数本身注释已经很明白,就不再解释了。
;函数原型:void * __asm_reset_thread(void (*thread_routine)(struct event_script *),
;                                        struct  thread_vm  *vm);
;-----------------------------------------------------------------------------
__asm_reset_thread  PROC
    EXPORT  __asm_reset_thread
    ldr     r2,[r1,#4]          ;取虚拟机栈顶指针
    mov     r4,#0x01000000      ;xpsr的初始值
    ldr     r3,=__djy_vm_engine ;取虚拟机引擎指针
    stmfd   r2!,{r3,r4}         ;pcxpsr
    sub     r2,r2,#14*4         ;后退14个寄存器,初始状态r0-r12中,除r0外均无意义,
                                ;__vm_engine函数不返回,lr也无意义
    ;存在r0中的thread_routine__vm_engine的参数,切换上下文时,thread_routine
    ;恢复到r0中,根据调用约定,r0的值就是__vm_engine函数的参数。
    str     r0,[r2,#8*4]        ;保存 thread_routine指针到r0的位置.
    str     r2,[r1]             ;保存vm的当前栈指针到vm->stack
    bx      lr

       ENDP
沙发
djyos|  楼主 | 2009-9-4 22:42 | 只看该作者
本帖最后由 djyos 于 2009-9-5 07:46 编辑

2、上下文切换

上下文切换即中止正在处理的事件(线程),转而处理另一个事件,在下列情形下,将引发上下文切换:

a、正在执行的线程因故被阻塞,比如请求使用设备,但该设备正忙。
b、有高优先级的线程就绪,比如释放了信号量,而使正在等待信号量的高优先级事件就绪。
上下文切换要完成两个操作:保存被中止的线程的上下文;恢复待切入的线程的上下文。在cm3中,上下文切换函数如下:
;函数原型: void __asm_switch_context(struct  thread_vm *new_vm,struct  thread_vm *old_vm);
;-----------------------------------------------------------------------------
__asm_switch_context    PROC
    EXPORT  __asm_switch_context
    mrs     r4,xpsr
           orr          r4,#0x01000000      ;xpsrT标志读不出来,得手工置位。
    push    {r4}                ;保存xpsr
    push    {lr}                ;保存PC,从其他线程切换回来时,相当于原线程调用
                                ;本函数返回,故用lr替代pc
    push    {r0-r3,r12,lr}      ;保存r0-r3,r12,lr
    push    {r4-r11}
    str     sp,[r1]             ;保存旧上下文栈指针到old_vm->stack


    ldr     sp,[r0]             ;取得新上下文指针
    bl      int_restore_asyn_signal ;对应done函数开头的 int_save_asyn_signal 调用
    svc     0
           ENDP
__asm_switch_context函数完成的只是保存上下文,而新线程上下文的切入是在0svc调用中完成的,0svc调用的代码如下:
    add     r0,#32              ;R0保存的是psp指针值
    ldmfd   r0!,{r4-r11}          ;手工弹出r4-r11
    msr     psp,r0              ;psp指向待切入的上下文
    bx      lr                  ;返回,实际弹出的将是带切入上下文
在线程栈中,最底下的8个字是执行svc 0时自动压栈的,我们并不需要,因此第一句直接加32,跳过这8个字。此时,r0指向的是待切入的线程的上下文了。
随后是手工弹出r4-r11,而r0-r3r12lrpcxpsr则是从svc返回时走法律程序自动恢复,因为此时psp已经指向待切入的线程的上下文,故svc将直接返回到新线程,至此,上下文切换完成。
这跟arm7版本不一样,在arm7版本中,是直接手工恢复新线程的上下文,具体可参阅djyos for 44b0的代码。那为什么cm3版本需要这么严格的程序,动用svc才能实现切换呢?问题就出在ICI位上,cm3为了保证中断响应速度,对需要很长执行时间的多寄存器加载指令ldm和多寄存器存储指令stm以及IT指令,可在执行过程中中断,利用ICI为保存执行进度,中断恢复时从被中断处继续执行该指令。然而ICI位是只读的,因此,手工恢复寄存器是不可能恢复这些位的,只有从真正的异常返回才能根据ICI位的状态继续被中断的指令执行。
有人要问了,上下文切换不是通过调用__asm_switch_context函数实现的吗?调用该函数时,不可能正在执行ldmstmIT指令啊?是的,没错,但是,保存上下文除了主动调用上下文切换函数外,还可能是中断。如果在中断服务例程中使得高优先级的线程就绪,中断返回将直接返回到高优先级线程,被中断打断的低优先级线程的上下文就被保留了,直到该线程变成最高优先级线程才被切回。中断在任何时候都有可能发生,当然也可能在正在执行stmldmIT指令时发生,所以,中断保存的上下文,必须模拟异常返回才能确保恢复执行被中断的半截子指令。

使用特权

评论回复
板凳
djyos|  楼主 | 2009-9-4 22:42 | 只看该作者
本帖最后由 djyos 于 2009-9-5 07:46 编辑

3、启动多事件调度

在初始化完成后,切入第一个线程,标志着多事件调度的开始。cm3版本中,初始化程序工作在handler状态,使用msp作为栈指针,而事件在特权状态的thread模式处理,使用psp为栈指针。因此,启动多事件调度要完成以下工作:

a、  切换到特权模式的thread状态。

b、  切入第一个需要处理的事件的线程的上下文。

c、  因初始化函数的任务已经终结,无需保存其上下文。

下面是

;函数原型: void __asm_start_thread(struct  thread_vm  *new_vm);

;-----------------------------------------------------------------------------

__asm_start_thread PROC

    EXPORT  __asm_start_thread

    mov     r1,#2

    msr     control,r1          ;切换到特权线程模式,栈指针用psp

    ldr     sp,[r0]             ;取得新上下文指针

    bl      int_restore_asyn_signal ;对应done函数开头的 int_save_asyn_signal 调用

    svc     0                   ;切入新上下文

       ENDP

4、切入线程

__asm_turnto_context函数不保存当前线程的上下文,直接把新线程的上下文恢复到cpu中。当一条事件处理完毕,该事件的线程可能被销毁,这种情况下,就无需保存当前线程的上下文,直接切入新线程的上下文即可。__asm_turnto_context函数只做一件事:调用svc 0恢复新线程上下文:

;函数原型: void __asm_turnto_context(struct  thread_vm  *new_vm);

;-----------------------------------------------------------------------------
__asm_turnto_context PROC
    EXPORT  __asm_turnto_context
    ldr     sp,[r0]             ;取得新上下文指针
    bl      int_restore_asyn_signal ;对应done函数开头的 int_save_asyn_signal 调用
    svc     0                   ;切入新上下文
       ENDP

使用特权

评论回复
地板
djyos|  楼主 | 2009-9-4 22:43 | 只看该作者
本帖最后由 djyos 于 2009-9-5 07:47 编辑

5、复位旧线程,切入新线程

当一条事件处理完成如果系统调度发现该事件的线程应该予以保留就调用__asm_reset_switch复位该线程,然后切入新的最高优先级的就绪线程。__asm_reset_switch函数是__asm_reset_thread__asm_turnto_context的结合体。代码如下:

;函数原型:void __asm_reset_switch(void (*thread_routine)(struct event_script *),
;                           struct  thread_vm *new_vm,struct  thread_vm *old_vm);
;-----------------------------------------------------------------------------
__asm_reset_switch PROC
    EXPORT  __asm_reset_switch
    ldr     sp,[r2,#4]          ;取老虚拟机栈顶指针

    mov     r12,#0x01000000      ;xpsr的初始值

    ldr     r11,=__djy_vm_engine    ;取虚拟机引擎指针

    push    {r11,r12}           ;pc,xpsr

    sub     sp,sp,#14*4         ;后退14个寄存器,初始状态r0-r12中,除r0外均无意义,

                                ;__vm_engine函数不返回,lr也无意义

                                ;至此,完成老虚拟机复位

    str     r0,[sp,#8*4]        ;保存 thread_routine指针.至此,完成老线程重置

    str     sp,[r2]             ;保存当前栈指针到old_vm->stack



    ldr     sp,[r1]             ;取得新上下文指针

    bl      int_restore_asyn_signal ;对应done函数开头的 int_save_asyn_signal 调用

    svc     0                   ;切入新上下文

       ENDP

6、中断服务例程中的上下文切换

最后一级异步信号(包括systick,由于djyos for cm3把所有异步信号优先级设为相同,故不会嵌套)返回前,即将返回线程模式时,系统将检查在中断服务例程的执行过程中是否有更高优先级的事件(线程)就绪。如果有,则调用__asm_switch_context_int函数执行上下文切换,该函数完成2个操作:
a、把当前PSP保存在被ISR中断的线程虚拟机的线程控制块中。
b、从高优先级的事件的线程控制块中取出SP,放到PSP寄存器中。
函数代码如下:
;函数原型: void __asm_switch_context_int(struct thread_vm *new_vm,struct thread_vm *old_vm);
;-----------------------------------------------------------------------------
__asm_switch_context_int    PROC
    EXPORT  __asm_switch_context_int
    mrs     r2,psp      ;取被中断线程的psp
    str     r2,[r1]       ;psp写入虚拟机数据结构中
    ldr     r2,[r0]       ;取待切入线程的psp
    msr     psp,r2      ;待切入线程的当前栈指针写入psp,中断返回恢复上下文将以此为准
    bx      lr
       ENDP
7、一点小经验

cm3中从异常返回必须用bx lr,不能用mov pc,lr,否则会发生存储器管理fault(取址违例)
读函数名得到奇数地址。
中断压栈的PC是偶数地址。
中断返回时,无论栈中的PC值是奇数还是偶数,都能正确返回。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

454

帖子

1

粉丝