Chinaunix首页 | 论坛 | 博客
  • 博客访问: 181916
  • 博文数量: 20
  • 博客积分: 125
  • 博客等级: 入伍新兵
  • 技术积分: 985
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-08 13:48
个人简介

热爱开源,喜欢分析操作系统架构

文章分类

全部博文(20)

文章存档

2013年(17)

2012年(3)

分类: 嵌入式

2013-04-26 15:12:35

对于任何一个多任务系统而言,由于涉及效率和安全性,其任务调度都是一个非常关键的部分。一般而言,由于涉及底层硬件的中断机制,这部分的代码都是用汇编完成的,MQX也不例外。这部分的程序可以在dispatch.s中找到。

在提具体程序之前先要说两个异常,第一个是SVC,另一个是PendSV。这两个异常都拿来给用户自定义用的,前一个一般用于系统调用,后一个一般用于任务切换。首先说一下为什么要有系统调用这么一个玩意:arm规定了两个特权级:Privileged和Unprivileged。这个我之前“中断机制(中)”就已经说过了,忘记的同学可以回过去看一下。不过我之前说的有错误,那就是MQX并不是一直运行在Privileged下的,只要你定义了#define MQX_ENABLE_USER_MODE,就可以利用MQX_USER_TASK来创建Unprivileged级别的任务。一旦任务运行在Unprivileged下面,有些事情就不能做了。比如MPU会限制你访问末段区域,NVIC寄存器不能操作等等。这些限制手段都是用来防止一些非法操作导致系统崩溃的。但是如果是一个Unprivileged级别的任务想在某种合法的模式下完成一些只有Privileged级别才能完成的操作的话,那就只能依靠系统调用了。

系统调用依赖的是一个异常,这个异常能够在Unprivileged下面主动触发,使得系统进入Privileged的异常服务里面。最著名的系统调用莫过于x86的int 80中断,不过这个中断并非天生就是可以在Unprivileged下能触发的,而是要在启动的时候通过参数设置,我们这里讨论的是arm就不多说了。arm下面的SVC异常是专门针对系统调而设计的异常,无需涉及就能在Unprivileged下触发,触发指令非常简单只要一个svc n(n是一个8位数)就可以了。在SVC异常中,系统根据之前的n,选择相应的系统调用,非常的方便。

而PendSV是arm为了多任务系统专门设计的用于任务切换的异常。具体的说明详见宋岩的《Cortex-M3 权威指南》,里面说的很详细了。

我看过stm32的ucos和rt thread任务切换的汇编码,它们没有使用SVC的系统调用机制,切换机制都是直接使用PendSV的。这是因为它们都没有Unprivileged级别的任务,试想如果一个Unprivileged级别的任务想要放弃cpu,但它没有权限触发PendSV(触发PendSV的寄存器是ICSR,Unprivileged级别下无法访问。),那它如何完成任务的切换呢?这个对于MQX就非常简单了,MQX只需要设置一个相应的系统调用,在这个系统调用中触发这个PendSV就可以了。事实上,MQX采取的就是这样的机制。

涉及任务调度的SVC和PendSV流程图如下:

切换_页面_1       其实SVC一共有四个功能外,除了系统调用之外,剩下的3个的都是和任务调度相关的。SVC_RUN_SCHED是用来启动任务调度机制的,SVC_TASK_BLOCK功能是阻塞一个任务使其不再被调度,而SVC_TASK_SWITCH就是进行正常的任务调度,具体流程如上图所示。要注意虽然SVC_RUN_SCHED和SVC_TASK_BLOCK、SVC_TASK_SWITCH后面部分大同小异,但是我还是将其分列出来了。因为前者是在SVC异常完成这个操作的,而后者是在SVC触发了PendSV,之后在PendSV异常处理中完成相关操作的。至于为什么SVC_RUN_SCHED没有使用PendSV,是因为它并不需要“将r2-r11,lr压入当前任务堆栈”这个步骤,这个我之后会在堆栈切换部分解释。还要注意一点,一旦系统优先级队列中没有就绪的任务,MQX会通过wfi指令进入休眠,当然在休眠之前要开中断,不然再也醒不过来了。一旦被中断唤醒后(一般系统会执行中断的服务程序修改优先级队列后再回到这里,不然没有任何意义,所以对应中断的优先级要高于SVC和PendSV。),MQX会再次重复检查最高优先级任务的工作,一旦找到就切换至最高优先级任务。

现在来说堆栈的切换。其实切换任务就是切换堆栈,之前说的arm会将中断或是调SVC异常之前的上下文环境存在之前的任务堆栈,所以一旦切换的相应的任务堆栈,通过arm从异常中返回机制就可以恢复我们要切换任务的上下文环境,进而来达成切换任务的目的。但是一个新创建的任务在没有调度之前,由于根本没有发生中断或异常,arm没有为其自动堆栈完成上下文环境,所以这需要我们手工完成这个第一次堆栈。

切换_页面_2




在这里要要道个歉,上篇博文“任务数据结构”最后一个关于任务堆栈的贴图不太完整的,我漏掉了PENDSVPRIOR~LR2这部分内容,在这里我做出更正。这部分堆栈一些内容已经赋值,是在_psp_build_stack_frame函数里。先看流程图中的“从该任务堆栈弹出r2-r11,lr”语句,当前任务堆栈是指向PENDSVPRIOR,这个操作也就是把r2=PENDSVPRIOR,r3=BASEPRI,r4=r4…lr=LR2=0xfffffffd。之前不是说了,启动任务调度SVC_RUN_SCHED没有调用PendSV异常,因为PendSV异常开始会替当前任务堆这11的字的栈,但如果是第一次运行的话这个栈是事先堆好的,如果再堆就重复了。通过这个操作,我们达成了:1)获取任务的BASEPRI;2)更新了LR=0xfffffffd;3)设置PSP指向R0,为从异常切换到任务做好准备。

QQ截图20130426145021

arm会根据LR的内容,选择从异常返回到哪里。这里LR=0xfffffffd,就是说明返回状态是thread,不再是中断或是异常了,而使用堆栈是SP=PSP,也就是我们图中的那个堆栈,当前PSP指向R0。根据arm的规定,返回时从SP中依次弹出R0~R3、R12、LR、PC、PSP。图中所注明的手工堆栈指的是通过指令完成的堆栈,而自动堆栈是arm自动完成的堆栈(虽然只是弹出是自动的)。这样之后PC=(template_ptr->TASK_ADDRESS) | 1,这里TASK_ADDRESS是任务的执行函数,而最后一位为1是注明此为thumb指令。而R0是任务的执行函数的参数create_parameter,这个函数的返回地址也准备好了在LR中,为_task_exit_function_internal,是任务退出的代码。于是乎MQX就这样轻松的完成了从异常到任务的切换。

阅读(3688) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

shaomengchao2013-10-28 15:34:37

lt6210925:楼主那张图中没有找到的那部分,其实MQX是这样做的,并不是休眠。MQX有一个空闲任务,在一直的执行着FOR循环,该任务为最低优先级,当没有任务的时候该任务在一直运行。在任务模板列表中最后的那个0.

空闲任务是可选编译的,如果没有编译空闲任务,就会在这里休眠

回复 | 举报

lt62109252013-09-02 23:00:29

楼主那张图中没有找到的那部分,其实MQX是这样做的,并不是休眠。MQX有一个空闲任务,在一直的执行着FOR循环,该任务为最低优先级,当没有任务的时候该任务在一直运行。在任务模板列表中最后的那个0.