搜索
bottom↓
回复: 16

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

[复制链接]

出0入0汤圆

发表于 2009-9-4 22:37:32 | 显示全部楼层 |阅读模式
看本文请参考《都江堰操作系统与嵌入式系统设计》第15章,该书可在www.djyos.com下载。
    写这一篇的时候,djyos在stm32上已经跑起来了,只是串口驱动还没有写,不能输出而已。从之一到之五,东扯西扯了很多东西,包括移植方法,中断系统实现方法,上下文设计、开发工具等,除了在之三列出了初始化代码外,其他几篇都没有代码。这是因为代码一直在调试中,变化很大,写出来也是白搭,这不,之三列出的代码,就是针对gcc环境的,在mdk下全改了。写移植日志,如果没有代码的话,原理讲得再多,也是纸上谈兵,现在系统跑起来了,底层代码基本不会变了,我们就结合代码来讲解一下。
    移植djyos,需要修改的代码主要有:中断相关代码、cpu硬件初始化代码、上下文操作代码等。djyos代码在所有可能需要在移植时修改的代码中,均有“移植关键”字样的注释。
    我们先对比arm7来讲解一下cm3中上下文切换。
1、设置初始上下文
    线程的上下文指线程执行环境的当前状态,其实也就是影响该线程执行的cpu寄存器的状态,在cm3中,这些寄存器一共有17个:R0-R15(R15即PC)和xpsr,但从之四上的那张图上,我们并没有看到R13(即SP),那是因为上下文保存在当前线程的栈的当前位置,该位置是变化的,而恢复上下文又必须先得到SP才可以,所以SP只能保存在某一个固定的位置,而不能保存在栈中。djyos把SP保存在当前线程虚拟机的线程控制块中。
    线程执行之前,我们必须为它准备好上下文,也就是初始上下文,djyos中,所有线程都是由函数__djy_vm_engine发动的,该函数有一个参数,即线程所属事件的处理函数指针,根据aapcs规定,该参数保存在R0中。因此,初始上下文中只有R0和PC是有效的,xpsr应该给一个合法的初始值外,其他寄存器可以用任意值,所以,初始上下文设置为:
R0:线程所属事件的处理函数指针。
PC:__djy_vm_engine函数的地址
xpsr:0x01000000,thumb状态,清掉其他所有标志。
其他寄存器:任意。
__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}         ;存pc,xpsr
    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
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      ;xpsr的T标志读不出来,得手工置位。
    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函数完成的只是保存上下文,而新线程上下文的切入是在0号svc调用中完成的,0号svc调用的代码如下:
    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-r3、r12、lr、pc、xpsr则是从svc返回时走法律程序自动恢复,因为此时psp已经指向待切入的线程的上下文,故svc将直接返回到新线程,至此,上下文切换完成。
    这跟arm7版本不一样,在arm7版本中,是直接手工恢复新线程的上下文,具体可参阅djyos for 44b0的代码。那为什么cm3版本需要这么严格的程序,动用svc才能实现切换呢?问题就出在ICI位上,cm3为了保证中断响应速度,对需要很长执行时间的多寄存器加载指令ldm和多寄存器存储指令stm以及IT指令,可在执行过程中中断,利用ICI为保存执行进度,中断恢复时从被中断处继续执行该指令。然而ICI位是只读的,因此,手工恢复寄存器是不可能恢复这些位的,只有从真正的异常返回才能根据ICI位的状态继续被中断的指令执行。
    有人要问了,上下文切换不是通过调用__asm_switch_context函数实现的吗?调用该函数时,不可能正在执行ldm或stm或IT指令啊?是的,没错,但是,保存上下文除了主动调用上下文切换函数外,还可能是中断。如果在中断服务例程中使得高优先级的线程就绪,中断返回将直接返回到高优先级线程,被中断打断的低优先级线程的上下文就被保留了,直到该线程变成最高优先级线程才被切回。中断在任何时候都有可能发生,当然也可能在正在执行stm、ldm或IT指令时发生,所以,中断保存的上下文,必须模拟异常返回才能确保恢复执行被中断的半截子指令。
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
4、复位旧线程,切入新线程
    当一条事件处理完成,如果系统调度发现该事件的线程应该予以保留,就调用__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
5、中断服务例程中的上下文切换
    最后一级异步信号(包括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
6、一点小经验
cm3中从异常返回必须用bx lr,不能用mov pc,lr,否则会发生存储器管理fault(取址违例)
读函数名得到奇数地址。
中断压栈的PC是偶数地址。
中断返回时,无论栈中的PC值是奇数还是偶数,都能正确返回。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2009-9-5 06:31:11 | 显示全部楼层
很诡异的切换方式。

建议重新梳理下,下面的似乎不太符合CM3的精神:
1.
__asm_switch_context    PROC
...
push    {r0-r3,r12,lr}      ;保存r0-r3,r12,lr
...

新线程上下文的切入是在0号svc调用中完成的

2.
最后一级异步信号(包括systick,由于djyos for cm3把所有异步信号优先级设为相同,故不会嵌套)返回前

中断不能支持嵌套也就失去CM3当初设计的初衷了,而r0 - r3, r12, lr这些也不需要手工压栈的。

另外,
GCC应该能比较好的支持CM3的,否则就搞大了。估计还是你当时移植的时候有些细节的地方没做对,有时间RT-Thread倒是会做一版GCC的移植出来,毕竟RT-Thread也是从GCC起家的。

出0入0汤圆

发表于 2009-9-5 07:30:14 | 显示全部楼层
楼上两位都是高人,我没看懂,呵呵。顶一下。

出0入0汤圆

 楼主| 发表于 2009-9-5 07:40:46 | 显示全部楼层
谢谢楼上指点。
关于第一个问题,不用这种方式切换的话,怎么解决xpsr中ICI位的问题?

第二个问题,djyos的中断分为异步信号和实时中断,只有异步信号是不支持嵌套的。实时中断支持嵌套且永不关中断。

第三个问题,我解决不了gcc for cm3中C程序调用汇编函数的问题,即使我自己的代码中完全避开,newlib中也有大量的汇编函数。gcc调用这些函数也是用 “blx  偶数地址”的方式调用的。

r0 - r3, r12, lr这几个寄存器,我倒是想过用svc来压栈,但好像没什么必要,手工压既直观又高效。弹栈用svc,是被ICI位缠得没有办法才这样的。

出0入0汤圆

发表于 2009-9-5 08:19:43 | 显示全部楼层
难怪。RT-Thread/GCC的版本是不依赖于GCC带的newlib库的,而是自己移植的newlib。类似的,RT-Thread/GCC也不依赖于gcc的libgcc.a库,可以说是一个非常纯粹、干净的版本。

出0入0汤圆

发表于 2009-9-5 13:08:09 | 显示全部楼层
曲高和寡。。。仅顶

出0入0汤圆

发表于 2009-9-5 20:37:50 | 显示全部楼层
上面两位不知是起得早还是睡得晚啊

出0入0汤圆

发表于 2009-9-9 15:40:46 | 显示全部楼层
顶楼上的所有人

另:建议【1楼】 ffxz 高手转向djyos,为广大工程师做贡献,哈哈

RT-Thread是别人的,djyos可是自己的哦

出0入0汤圆

发表于 2009-9-9 16:00:53 | 显示全部楼层


什么时候RT-Thread改国籍了,建议ls先好好了解清楚再下“RT-Thread是别人的”定论。

出0入0汤圆

发表于 2009-9-9 16:51:13 | 显示全部楼层
哦,我看到是英文的名字,以为是老外搞的:)

都江堰一看就知道是中国的呢,嘿嘿

“RT-Thread RTOS ,这是一款由国内RT-Thread工作室开发的开源实时操作系统。起初RT-Thread是一个实时的内核(全抢占优先级调度,调度器时间复杂度O(1)),但在发展过程中,RT-Thread实时操作系统得到了来自全国嵌入式开发工程师的鼎力支持……”

出0入0汤圆

发表于 2009-9-9 16:52:59 | 显示全部楼层
1楼是不是就是RT-Thread RTOS 的主创人员啊,厉害!

出0入0汤圆

 楼主| 发表于 2009-9-9 20:52:19 | 显示全部楼层
建议RTT取个响亮的中文名字,就不会有人误会了。

出0入0汤圆

发表于 2009-9-10 06:58:47 | 显示全部楼层
那就干脆直译好了:实时线程操作系统

文中说的,“实时中断支持嵌套且永不关中断。”这个是什么意思?只是在中断响应的时刻不关中断?有不有那种,在OS运行的过程中都不关中断(不管任何时刻)?

出0入0汤圆

发表于 2009-11-2 08:53:29 | 显示全部楼层
建议两位做操作系统重点攻ARM9上

出0入0汤圆

发表于 2009-11-2 19:32:53 | 显示全部楼层
没ARM9的环境,有PXA910的平台,但是外面又没有,发布等于不发布。

出0入0汤圆

 楼主| 发表于 2009-11-2 19:55:38 | 显示全部楼层
djyos有2410、2440版本的,在主页上可以下载。

出0入0汤圆

发表于 2011-11-1 22:59:35 | 显示全部楼层
第三个问题,我解决不了gcc for cm3中C程序调用汇编函数的问题,即使我自己的代码中完全避开,newlib中也有大量的汇编函数。gcc调用这些函数也是用 “blx  偶数地址”的方式调用的。

这个问题是怎么解决的哈?
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 08:27

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表