查看: 968|回复: 0
注册时间最后登录阅读权限1积分218精华0帖子
侠客, 积分 218, 距离下一级还需 82 积分
& & μC/OS-Ⅱ移植的要点在哪里?初始化任务堆栈时有两个返回地址是怎么回事?其中一个永远用不到,可以省略吗?……
这些问题是移植μC/OS-Ⅱ的初学者常会遇到的问题,我也是μC/OS-Ⅱ的初学者,因需要曾两次移植μC/OS-Ⅱ,能体会遇到这些问题时的茫然,现在基本上明白了移植时遇到的一些问题和一点通用原理,就想把这些东西尽可能用通俗易懂的方式写下来,希望有助于跟我一样的广大初学者学习μC/OS-Ⅱ。因本人是计算机专业的学生,描述自然也是从计算机的角度进行的,所以可能更适合学计算机的一起参考。既然是初学者,文中错漏肯定不少,请大家多多指正,先谢过啦!^_^
1.这次的移植是为了做学校的毕业设计,为了避免到时学校有人在网上看到这篇文章还反而说我是抄别人的,请大家如果想转载的话,务必把这段话也一起转过去,我在图片里也加了水印,望理解,谢谢^_^
2.因为我打算把这篇文章发到多个论坛,所以若有更正我只会在博客那里更正,其他地方请恕不加更正了,博客地址为。
Ⅱ的移植要点小谈
μC/OS-Ⅱ在移植过程中最难理解也是最重要的地方就是任务的“上下文”的处理,这个过程一般是与堆栈的处理相结合的。下边从一般的操作系统开始,逐渐引入μC/OS-Ⅱ,以说明μC/OS-Ⅱ对任务的“上下文”的处理过程,再逐个说明μC/OS-Ⅱ各个需要移植的函数,特别是其中移植时较难理解和需要注意的地方,最后再说明如何让μC/OS-Ⅱ在MSP430上的移植能支持用C语言编写中断处理函数。
μC/OS-Ⅱ的移植要点小谈... 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()
(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语言调用汇编这个方面有不同的规定和实现:
有的编译器是允许直接在C语言中内嵌汇编的,所以可以直接在C语言中内嵌汇编来操纵寄存器;
有的编译器不允许直接在C语言中内嵌汇编,但允许直接从C语言中调用汇编子程序(当然被调用的汇编子程序是放在汇编文件中的)。这样就可以在汇编子程序中操纵寄存器,再在C语言中进行调用;
有的编译器既不允许在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-Ⅱ中第一个运行的任务。
(Os_cpu_a.asm文件中的OSCtxSw()函数)这个函数执行任务切换的工作,函数中先把当前任务的上下文保存到该任务的堆栈中,再把HPT的上下文弹出到对应的寄存器中,任务切换就完成了。
(所有的中断服务函数,即ISR(Interrupt Service Routine))中断处理过程中:1先保存当前运行任务A的上下文,2进行具体的中断处理,3把HPT的上下文弹出到对应的寄存器中,以转入任务HPT运行【注意当A是当前最高优先级任务时,A和HPT是相同的】。
(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)中,有两个因素决定了这个“顺序”,这是不同的处理器所支持的汇编指令、中断和软中断的处理过程不一样所引起的:
c)的中断处理与硬件相关。有的在发生中断或软中断时,系统会默认保存所有寄存器,即把所有寄存器压入堆栈中,当然压入是按一定的顺序进行的(这样如果用软中断实现任务切换时,我们就不用再保存所有的寄存器了,因为系统已经帮我们做了这个工作);但有的系统默认只保存一部分寄存器,这样我们就必须在中断服务函数中保存其他还没保存的寄存器;
有些处理器支持可以用一条汇编指令从堆栈中保存或恢复好几个寄存器,而保存或恢复是有固定顺序的,这个顺序也是硬件决定的。有的处理器支持用一个汇编指令就可以保存所有的寄存器到堆栈中,同样用一个汇编指令也可以从堆栈中弹出所有寄存器;有的就不支持,这时就得用多条指令,逐个保存或弹出寄存器。在汇编程序中如果使用了某些特殊汇编指令,就得按照这些指令规定的顺序保存和恢复上下文。如果处理器支持这样的汇编指令,一般也是主要用于在进入和退出中断时用来保存和恢复多个寄存器,所以应该与(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)的二重身份的要求了:
在编译器眼中,MyTask(void *pdata)只是一个函数,也是以编译一个函数的方式为MyTask(void *pdata)生成目标代码的,所以初始化堆栈时,就得考虑到编译器在调用一个函数时对堆栈的要求。编译器调用一个函数时最重要的有两点,一是函数的返回地址要保存到堆栈中,以便函数执行完后能从这个地址返回,但MyTask(void *pdata)并不是真的从C语言里进行调用的,而是从汇编子程序OSStartHighRdy()、OSCtxSw()和OSIntCtxSw ()三个中的一个或者从ISR中进入的,也永远不会返回,所以这个地址是什么无关紧要;另一点是参数的传递,不同编译器传递参数的方法是不同的:1有的编译器把所有的参数都通过堆栈进行传递,这样参数就必须放到堆栈中存储参数的单元中;2有的编译器把前一个或多个参数通过寄存器传递,其他参数才通过堆栈传递,同样,这些参数也必须放到相应的寄存器或堆栈单元中。当在MyTask(void *pdata)中用到这个参数时,就会到编译器保存参数的地方去取,因此,在初始化新建的任务的堆栈时,得按照编译器的规定把传递的参数(即pdata)放到对应的堆栈单元或寄存器中。当然,这两种情况下建立的任务堆栈所占用的空间大小上有区别。
在μC/OS-Ⅱ中,MyTask(void *pdata)则是一个任务。按照前边说的,任务运行时得有自己的上下文,μC/OS-Ⅱ要求一个新建立的任务把上下文(即全部的寄存器)放在该任务的堆栈中。
为了满足上边2点要求,μC/OS-Ⅱ把堆栈初始化成发生一个事件后的堆栈的情况,这个事件就是:当从某个地方调用MyTask(void *pdata),并准备进入MyTask()运行时突然发生了中断,如图1所示。从某个地方调用MyTask(void *pdata)时,会把函数返回地址和传递的参数pdata放置好;进入ISR运行前,会把当前的上下文保存到堆栈中。这样堆栈就初始化完成了。堆栈中被初始化的单元,可能被以下3个函数的任何一个在被调用时从堆栈中弹出:OSStartHighRdy()、OSCtxSw()和OSIntCtxSw (),也可能在ISR中切换到HPT时,新任务作为HPT而使其堆栈单元被弹出,以转入该任务运行。
& &下边分别在80x86处理器和MSP430单片机的环境下对堆栈情况进行说明:
【注意:80x86处理器和MSP430单片机对寄存器的名字称呼是不同的:我们平常所说的程序计数器,在80x86中称为IP(Instruction Pointer),而在MSP430上称为PC(Program Counter);状态寄存器在80x86中称为SW(Status Word),在MSP430上称为SR(Status Register);其他通用寄存器也类似,因为用不到,就不多加说明了。
1 80x86的堆栈是满递减堆栈,函数调用时所有的参数都是通过堆栈传递的。当调用一个函数时,函数堆栈的情况如图2所示;OSTaskStkInit()函数初始化后的堆栈情况如图3所示,图中的“函数返回地址”虽然永远不会用到,但堆栈中还是得为它空出一个单元,因为参数pdata放在堆栈中,更具体的下边会讲到。
2 MSP430的堆栈也是满递减堆栈,函数调用时第1-4个参数通过寄存器R12-R15进行传递,其他参数则通过堆栈传递。当调用一个函数时,函数堆栈的情况如图4所示;OSTaskStkInit()函数初始化后的堆栈情况如图5所示,图中的“函数返回地址”永远不会用到,而且参数pdata放在寄存器R12中,所以OSTaskStkInit()函数在初始化堆栈时可以不为“函数返回地址”留出空间,即可以省略掉,更具体的下边会讲到。
此外,把任务的上下文和参数放到堆栈中,还要考虑两个问题:
一个是状态寄存器(即图中的处理器状态字)的中断位是否置位,以决定任务开始运行时中断是否开启,而是否开启是由用户自己决定的,但要注意这将影响到所有的任务,包括OSTaskIdle()和OSTaskStat()任务,这两个任务运行时必须要开启中断,关于这个问题的讨论请参见《嵌入式实时操作系统μC\OS-II(第2版)》(邵贝贝译)P343页;
另一个问题是,就是上边第3点提到的那样,堆栈中放置任务上下文的“顺序”是由中断处理函数保存寄存器的顺序和使用的一些特殊汇编指令决定的,所以在堆栈中初始化任务的上下文时,也得按照这个顺序进行。
[ 本帖最后由 speedan 于
15:39 编辑 ]
Powered by|
|
|
|
|
只需一步,快速开始
查看: 1875|回复: 9
不愧为年度最佳,这游戏超碉啊!那么问题来了,告示板上的任务必须取下才会激活吗?我直接去找相关NPC可不可以?
主题帖子积分
高级玩家, 积分 527, 距离下一级还需 73 积分
高级玩家, 积分 527, 距离下一级还需 73 积分
我还没溜达出果园村呢,就被铁匠铺的纵火案纠结死了,搞得我的强迫症又犯了,这以后肯定少不了这样的选择啊.我的问题如标题,请各位路过的猎人解疑.
主题帖子积分
游戏狂人, 积分 1954, 距离下一级还需 46 积分
游戏狂人, 积分 1954, 距离下一级还需 46 积分
对,必须看告示牌,而且你都去下来之后过一阵告示牌又会贴满别的告示
主题帖子积分
超级玩家, 积分 751, 距离下一级还需 249 积分
超级玩家, 积分 751, 距离下一级还需 249 积分
主题帖子积分
高级玩家, 积分 527, 距离下一级还需 73 积分
高级玩家, 积分 527, 距离下一级还需 73 积分
谢谢!原来会刷新啊!那为了看新告示我就全揭了.
主题帖子积分
游戏精英, 积分 4161, 距离下一级还需 4839 积分
游戏精英, 积分 4161, 距离下一级还需 4839 积分
有些任务用不着看告示板一样可以直接做
主题帖子积分
中级玩家, 积分 184, 距离下一级还需 66 积分
中级玩家, 积分 184, 距离下一级还需 66 积分
可以直接做的。你直接找到那个NPC就能接的。
主题帖子积分
高级玩家, 积分 527, 距离下一级还需 73 积分
高级玩家, 积分 527, 距离下一级还需 73 积分
谢谢各位,刚试了一个狩魔任务,直接找NPC的话,对话开头会有些不一样(NPC问白狼是不是看到告示后来帮忙的,白狼说我压根不知道什么事,我就是过路的),但是别的都没区别.而任务结束后告示板上原来的任务委托会消失,换成别的内容.
主题帖子积分
游戏狂人, 积分 1709, 距离下一级还需 291 积分
游戏狂人, 积分 1709, 距离下一级还需 291 积分
我是去到一个新的告示栏 二话不说 全部撕掉 然后等任务一个个跳出来。
这个游戏的剧情和世界观都是超赞的 如果你玩的是V1.11有石之心的话 等你通关主线后会自动跳出资料片石之心 也是超赞
主题帖子积分
高级玩家, 积分 552, 距离下一级还需 48 积分
高级玩家, 积分 552, 距离下一级还需 48 积分
必须取下才会激活有时候公告板上又有新招贴了,不过取下就没新任务了,如果地图上公告板图标是***的话表示有新任务,白色表示则没有
话说这游戏以后让你纠结的破事多的一比,你一个小小的纵火案就别纠结了
主题帖子积分
有任务的告示有标志的.还有有些告示是为以后触发任务做铺垫的
Powered by