No.56247 作者:speedan 邮件:speedan@sohu.com ID:137669 登陆:1次 文章数:1篇 最后登陆IP:119.131.120.247 最后登陆:2010/4/5 15:37:21 注册:2010/4/5 14:26:48 财富:105 发帖时间:2010/4/5 14:28:49 发贴者IP:119.131.120.247 标题:speedan:[原创]μC/OS-Ⅱ的移植要点分析 摘要:No.56247[原创]μC/OS-Ⅱ的移植要点分析 μC/OS-Ⅱ移植的要点在哪里?初始化任务堆栈时有两个返回地址是怎么回事?其中一个永远用不到,可以省略吗?…… 这些问题是移植μC/OS-Ⅱ的初学者常会遇到的问题,我也是μC/OS-Ⅱ的初学者,因需要曾两次移植μC/OS-Ⅱ,能体会遇到这些问题时的茫然,现在基本上明白了移植时遇到的一些问题和一点通用原理,就想把这些东西尽可能用通俗易懂的方式写下来,希望有助于跟我一样的广大初学者学习μC/OS-Ⅱ。因本人是计算机专业的学生,描述自然也是从计算机的角度进行的,所以可能更适合学计算机的一起参考。既然是初学者,文中错漏肯定不少,请大家多多指正,先谢过啦!^_^ 补充: 1.这次的移植是为了做学校的毕业设计,为了避免到时学校有人在网上看到这篇文章还反而说我是抄别人的,请大家如果想转载的话,务必把这段话也一起转过去,我在图片里也加了水印,望理解,谢谢^_^ 2.因为我打算把这篇文章发到多个论坛,所以若有更正我只会在博客那里更正,其他地方请恕不加更正了,博客地址为 。 [size=4][B][center]μC/OS-Ⅱ的移植要点分析[/center][/B][/size]μC/OS-Ⅱ在移植过程中最难理解也是最重要的地方就是任务的“上下文”的处理,这个过程一般是与堆栈的处理相结合的。下边从一般的操作系统开始,逐渐引入μC/OS-Ⅱ,以说明μC/OS-Ⅱ对任务的“上下文”的处理过程,再逐个说明μC/OS-Ⅱ各个需要移植的函数,特别是其中移植时较难理解和需要注意的地方,最后再说明如何让μC/OS-Ⅱ在MSP430上的移植能支持用C语言编写中断处理函数。 目录 μC/OS-Ⅱ的移植要点分析(by gch) 1 一、引入 1 二、μC/OS-Ⅱ在移植过程中对任务上下文环境的处理 2 三、μC/OS-Ⅱ各个需要移植函数的说明 4 (1) Os_cpu_c.c文件中的OSTaskStkInit()函数 4 (2) Os_cpu_a.asm文件中的OSStartHighRdy ()函数: 8 (3) Os_cpu_a.asm文件中的OSCtxSw()函数: 8 (4) 中断服务函数OSTickISR() 8 (5) Os_cpu_a.asm文件中的OSIntCtxSw ()函数 9 四、让μC/OS-Ⅱ在MSP430上的移植支持用C语言编写中断处理程序 9 一、引入 从微机原理的课程中可以知道,计算机指令是在CPU中执行的,在执行过程中,CPU是通过寄存器存取数据和指令的,因此每个CPU都有一套内部寄存器协助其工作,这套寄存器我们一般称之为“上下文环境”(Context,下边简称为“上下文”),一般包括程序计数器(PC,Program Counter的缩写)、状态寄存器(SR,Status Register)其他一些通用寄存器(General-Purpose Registers),其中PC存放下一条将要执行的指令在代码段的地址,任务切换时把PC的值保存在当前任务的堆栈中,才能在任务切换回来后从这个地址继续执行。 从操作系统的课程中可以知道,多任务OS(操作系统)要求做到每个独立的任务从自己的角度看,都会觉得自己独占CPU,因此每个任务都会有自己的“上下文环境”,一般保存在任务的堆栈中,当该任务运行时,就会从堆栈中弹出来,放入对应的寄存器中。当一个运行中的任务A因为某种原因要切换到其他任务去运行时,A必须保存自己的上下文,以保证以后再次切换回A运行时,能从已保存的上下文中恢复回来。这样当A再次运行时,对A而言上下文根本没有变化过,好像从来没有进行过任务切换一样,其他任务的切换也是这样。这就是多任务切换的一个过程。 μC/OS-Ⅱ也是一个多任务OS,也得有这样一个任务切换的过程,即也得保存用到的所有寄存器,这样就得直接操纵寄存器。但是μC/OS-Ⅱ是用C语言写的(为了可移植性等),而C语言是不能直接操纵寄存器的,只有汇编才能直接操纵寄存器。因此,在任务切换过程中必须想办法从C语言中调用汇编语言,以通过汇编来操纵寄存器。但是,不同的编译器对从C语言调用汇编这个方面有不同的规定和实现: 1. 有的编译器是允许直接在C语言中内嵌汇编的,所以可以直接在C语言中内嵌汇编来操纵寄存器; 2. 有的编译器不允许直接在C语言中内嵌汇编,但允许直接从C语言中调用汇编子程序(当然被调用的汇编子程序是放在汇编文件中的)。这样就可以在汇编子程序中操纵寄存器,再在C语言中进行调用; 3. 有的编译器既不允许在C语言中内嵌汇编,也不允许从C语言中调用汇编子程序,但可以实现软中断(即软件中断),所以可以用软中断的方式进入汇编子程序,再在汇编子程序中操纵寄存器。 这就是从C语言调用汇编的一般的三种实现方法。μC/OS-Ⅱ在不同编译器下也是用这三种方法实现从C语言中进入汇编,以实现任务切换的。 二、μC/OS-Ⅱ在移植过程中对任务上下文环境的处理 正如上边所说的,在任务切换时,OS得保存任务中用到的所有寄存器,而μC/OS-Ⅱ为了通用性考虑,一般情况下都是直接保存所有寄存器,而不管这些寄存器是否用到,一般的移植方案也是这样做的。 任务的上下文在切换时一般都是保存在堆栈中的。根据堆栈“先入后出(FILO)”的特性,保存寄存器的顺序和恢复寄存器的顺序必须是对应反过来的。比如:假设总共有R1、R2、R3三个寄存器,保存上下文时应该把这3个寄存器的内容(即数据,或称为“值”)按照以下顺序保存到堆栈中:R1-> R2-> R3,那恢复上下文时就应该按照以下顺序从堆栈中把对应寄存器的内容弹出到寄存器中:R3-> R2-> R1。 观察μC/OS-Ⅱ的移植要求,可以发现μC/OS-Ⅱ会处理到任务的上下文(这里的“上下文”只包括用到的所有寄存器,不包括通过堆栈传递的参数,这个之后再讨论)的地方有5个: a) (Os_cpu_c.c文件中的OSTaskStkInit()函数)μC/OS-Ⅱ中每个任务都有自己的堆栈,新建立一个任务时,必须初始化该任务的上下文,这些被初始化的上下文放在该任务的堆栈中(任务建立时堆栈中除了放初始化的上下文,还会放其他东西,后边会讲到),将会在任务开始运行时从堆栈中弹出来放到对应的寄存器中。但由于任务未曾运行过,所以一般情况下,除了状态寄存器(它的中断位决定着任务开始运行时中断是否开启)和程序计数器(放置任务的起始地址)必须认真设置之外,任务的上下文被初始化成什么值是无所谓的。但是由于μC/OS-Ⅱ会给新建立的任务传递一个参数,而有的编译器会把前几个参数通过寄存器传递,所以也会涉及到上下文的初始化。 b) (Os_cpu_a.asm文件中的OSStartHighRdy ()函数)μC/OS-Ⅱ总是运行当前处于就绪状态的最高优先级的任务(Highest Priority Task,后边简称为HPT)。系统启动时,需要运行第一个任务,这个任务也必须是当前的HPT,而启动这个HPT的工作是由函数OSStart()执行的。 OSStart()函数先找出当前的HPT,再调用OSStartHighRdy (),在OSStartHighRdy ()中把HPT的上下文从HPT的堆栈中弹出到对应的寄存器中,以启动HPT的运行,即μC/OS-Ⅱ中第一个运行的任务。 c) (Os_cpu_a.asm文件中的OSCtxSw()函数)这个函数执行任务切换的工作,函数中先把当前任务的上下文保存到该任务的堆栈中,再把HPT的上下文弹出到对应的寄存器中,任务切换就完成了。 d) (所有的中断服务函数,即ISR(Interrupt Service Routine))中断处理过程中:○1先保存当前运行任务A的上下文,○2进行具体的中断处理,○3把HPT的上下文弹出到对应的寄存器中,以转入任务HPT运行【注意当A是当前最高优先级任务时,A和HPT是相同的】。 e) (Os_cpu_a.asm文件中的OSIntCtxSw ()函数)这个函数是第(3)点中的○3处调用的,用来检查A是否HPT:○1如果A不是HPT就把HPT的上下文弹出到对应的寄存器中,以转入HPT运行;○2如果A是HPT就返回调用处(即ISR中),在ISR中会把A的上下文弹出到对应的寄存器中,以转入任务A运行。 从a)~e)可以看出,只要这5个地方做到上下文的保存和弹出顺序完全一致(即全部对应反过来),基本上就不会出什么问题。 那现在问题就在于上下文保存的“顺序”由什么决定?a)~e)中,有两个因素决定了这个“顺序”,这是不同的处理器所支持的汇编指令、中断和软中断的处理过程不一样所引起的: (1) c)的中断处理与硬件相关。有的在发生中断或软中断时,系统会默认保存所有寄存器,即把所有寄存器压入堆栈中,当然压入是按一定的顺序进行的(这样如果用软中断实现任务切换时,我们就不用再保存所有的寄存器了,因为系统已经帮我们做了这个工作);但有的系统默认只保存一部分寄存器,这样我们就必须在中断服务函数中保存其他还没保存的寄存器; (2) 有些处理器支持可以用一条汇编指令从堆栈中保存或恢复好几个寄存器,而保存或恢复是有固定顺序的,这个顺序也是硬件决定的。有的处理器支持用一个汇编指令就可以保存所有的寄存器到堆栈中,同样用一个汇编指令也可以从堆栈中弹出所有寄存器;有的就不支持,这时就得用多条指令,逐个保存或弹出寄存器。在汇编程序中如果使用了某些特殊汇编指令,就得按照这些指令规定的顺序保存和恢复上下文。如果处理器支持这样的汇编指令,一般也是主要用于在进入和退出中断时用来保存和恢复多个寄存器,所以应该与(1)中的顺序一致。 三、μC/OS-Ⅱ各个需要移植函数的说明 接下来结合μC/OS-Ⅱ在80x86处理器和MSP430单片机的移植实例具体说明每个需要移植的函数,但主要讲与堆栈有关的部分,其他由μC/OS-Ⅱ系统规定的须在移植函数中做的事情,比如调用各个OS**Hook()函数之类的细节就暂且略过,因为《嵌入式实时操作系统μC\OS-II(第2版)》(邵贝贝译)中已经讲得很清楚。另外由于本人对80x86处理器并不熟悉,所以对80x86处理器部分的讲解主要是根据《嵌入式实时操作系统μC\OS-II(第2版)》中的例子进行的。 (1) Os_cpu_c.c文件中的OSTaskStkInit()函数 在这个函数中,我将会重点讲一下前边提到的传递参数的问题。 OSTaskStkInit()函数的功能是为新建立的任务准备一个初始化好的堆栈,假设这个任务为MyTask(void *pdata)。那现在的问题是初始化这个堆栈时得在堆栈中放置什么东西呢?这就得考虑到MyTask(void *pdata)的二重身份的要求了: (1) 在编译器眼中,MyTask(void *pdata)只是一个函数,也是以编译一个函数的方式为MyTask(void *pdata)生成目标代码的,所以初始化堆栈时,就得考虑到编译器在调用一个函数时对堆栈的要求。编译器调用一个函数时最重要的有两点,一是函数的返回地址要保存到堆栈中,以便函数执行完后能从这个地址返回,但MyTask(void *pdata)并不是真的从C语言里进行调用的,而是从汇编子程序OSStartHighRdy()、OSCtxSw()和OSIntCtxSw ()三个中的一个或者从ISR中进入的,也永远不会返回,所以这个地址是什么无关紧要;另一点是参数的传递,不同编译器传递参数的方法是不同的:○1有的编译器把所有的参数都通过堆栈进行传递,这样参数就必须放到堆栈中存储参数的单元中;○2有的编译器把前一个或多个参数通过寄存器传递,其他参数才通过堆栈传递,同样,这些参数也必须放到相应的寄存器或堆栈单元中。当在MyTask(void *pdata)中用到这个参数时,就会到编译器保存参数的地方去取,因此,在初始化新建的任务的堆栈时,得按照编译器的规定把传递的参数(即pdata)放到对应的堆栈单元或寄存器中。当然,这两种情况下建立的任务堆栈所占用的空间大小上有区别。 (2) 在μC/OS-Ⅱ中,MyTask(void *pdata)则是一个任务。按照前边说的,任务运行时得有自己的上下文,μC/OS-Ⅱ要求一个新建立的任务把上下文(即全部的寄存器)放在该任务的堆栈中。 为了满足上边2点要求,μC/OS-Ⅱ把堆栈初始化成发生一个事件后的堆栈的情况,这个事件就是:当从某个地方调用MyTask(void *pdata),并准备进入My ......
>>返回讨论的主题
|