有任务优先级Task1和任务优先级Task2,任务优先级Task1的优先级为3,任务优先级Task2的优先级为7

任务管理_百度百科
声明:百科词条人人可编辑,词条创建和修改均免费,绝不存在官方及代理商付费代编,请勿上当受骗。
任务管理,是一款安卓系统的处理效率软件。
任务管理BC
论述内容包括任务状态迁移、任务控制块、中各种队列、调度算法和内核时钟等内容。在内核的设计过程中,最先应考虑的是任务的状态以及迁移时序,然后根据此状态设计相应的队列,如就绪队列、等。内核时钟也依赖任务的状态。可以看出,任务管理实现的核心和基础是任务状态和迁移时序。
任务管理其他
4.4.1任务状态及转换时序
在上面的章节中,描述了任务的三种基本状态,一般在实现时会基于这三种转态添加新的状态。图4-4描述了实际实现的任务状态转换图。在给定的时刻,任务的状态一定处在这六种状态之一,下面的论述只是对本系统实现的描述,不同的对这些部分的实现有很大差异,但基本原理不变。
图4-4在描述任务状态迁移的同时,也描述了任务的生存周期,任务的从新建态时开始直到结束态时结束。在不同的操作系统中,这些状态的实现是有差异的,有的内核还有其他状态。新建状态是指任务被创建的过程,在这个过程中主要工作有:为任务分配TCB和栈空间以及其他资源。当任务创建完成以后,任务就具备运行的能力了,与此同时,任务进入,并等待调度器为它分配运行的机会。当任务得到运行的机会,任务开始执行。处于运行态的任务会在任意时刻由运行态进入休眠态、就绪态或结束状态。其中进入休眠态是任务的主动过程,这主要是任务调用了内核提供的休眠函数,任务在,如果没有其他任务唤醒它,它将永远休眠下去直到系统关闭,这种方式也可用于任务同步。等待状态主要由两种原因引起,一种是等待某事件的发生,如等待;第二种为任务主动等待多少个tick。最后,任务可以将自己杀死进入结束态。
4.4.2任务控制
任务控制块(TCB)唯一地描述了一个任务的属性。一旦任务建立了,任务控制块中的各个值将被赋值。任务控制块是一个数据结构,当任务的CPU使用权被剥夺时,TCB保存了该任务的状态和其他信息。当任务重新得到CPU使用权时,TCB能确保任务从被中断的点丝毫不差地继续执行。TCB全部驻留在RAM中。TCB在任务初始化的时候被建立。任务控制块数据结构如下所示:
typedef struct task_ctrl_blk{
uword_t slice_
uword_t exe_
word_t delay_
list_t task_
·pstack:指向当前任务的栈顶。每个任务有自己的栈,尤为重要的是,每个任务的栈的容量可以是任意的。有些商业要求所有任务栈的容量都一样,除非用户写一个复杂的来改变之。这种限制浪费了RAM,当各任务需要的栈空间不同时,也得按任务中预期栈容量需求最多的分配栈空间。pstack是TCB数据结构中唯一一个能用汇编语言来处置的变量(在任务切换段的代码之中使用)把pstack放在数据结构的最前面,使得从汇编语言中处理这个变量时较为容易;
·pstk:指向任务的栈顶,在任务结束而回收任务栈空间时使用,这主要由内存管理部分的缺陷所引起的;
·link:用于连接任务控制块。在运行时,除了任务控制块外,系统中存在很多类型的链表,比如链表。为了对这些链表有一个统一的操作,所以定义了list_t类型来统一这些操作。如果不使用list_t,TCB链表操作需要实现一组链表操作函数,信号量需要另外一组链表操作函数,这样使程序变得冗长;
·id:任务的ID号,用于唯一标识一个任务。每个任务都有一个唯一的ID号,需要在任务创建的时候指定ID,如果指定的ID号已经存在,则此任务不能被创建;
·prio:任务的优先级,此值范围为0~63,值越小代表优先级越高。将尽力保证高优先级的任务优先运行,并且允许任务可以是相同的优先级;
·slice_time:表示任务应该运行的时间片数。虽然内核保证高优先级的任务优先得到运行的机会,但对于相同优先级的任务来说,时间片方式是比较好的调度策略;
·exe_time:保存了任务已经运行的时间片个数。这个变量在每次系统时钟中断产生时被累加1,如果exe_time的值达到slice_time,则说明该任务已经运行了给定时间片的时间,这时,内核将把运行机会让给其他的,且优先级等于此任务的其他任务。如果此优先级上没有其他任务,且此任务没有自己放弃运行机会,此任务将继续运行;
·delay_time:用于记录任务等待的时间片数,每个中断产生时,此值自减1,如果delay_time的值为0,说明该任务的等待时间已经超时。将此任务从中删除,并移动就绪队列中,这样该任务就会被调度器在适当的时候调度;
·status:指示了任务的运行状态,目前,此值表示的含义有就绪,休眠,等待和阻塞,在任务状态转换图4-4中的运行态未能表示出来,这是因为在实现时,就绪态同时也表示了运行态;
·task_link:用于将系统中所有的任务连接成循环。
4.4.3 ByCore中的各种队列
在图4-4中描述的每个状态都对应一个或一组队列。如处于中的就绪队列,处于等待态中的等待队列等等。
4.4.3.1 就绪队列
就绪队列中的任务已经得到除CPU以外的所有资源。调度器也将在它们中按照优先级和时间片结合的策略选择一个就绪任务获得CPU。在实现中,任务被分成64(0~63)种优先级,且不同的任务又会有相同优先级。将相同优先级的任务组成一个双链表。为了在调度过程中能快速的检索出最高优先级的任务队列,将整个就绪队列用一个全局数组list_t ptask[MAX_PRIO](其中MAX_PRIO=64)来作为不同优先级就绪队列的队头,如ptask为优先级是i的就绪队列的队头。整个就绪队列如图4-5所示。
4.4.3.2 等待和休眠队列
当任务处于等待或休眠态时,必须将该任务的TCB从就绪队列中删除,然后插入到等待或者休眠队列。在当前的实现中,内核只分别维持一个和休眠队列,这两个队列不像就绪队列按照优先级的高低被分组,换句话说,等待队列和休眠队列将所有的任务TCB连成一个。
pdelay和psleep分别为等待队列和休眠队列的对头。这两个队列的组织虽然一样,但是它们各自队列中的任务被激活的时机却不同,pdelay所指队列中的任务会被内核的tick激活,而处在psleep队列中的任务只能由其他的任务将其唤醒。利用这两种队列配和等任务同步、通信机制可以实现较为复杂、灵活的任务控制机制。
当任务处在等待态时,任务还可能处在另外的队列中,这个队列就是为等待某个信号量而组织成的队列。这个队列将在信号量实现的内容中论述。
4.4.4调度器实现
在整个任务管理中,任务调度无疑是系统的核心,任务调度通常由中的调度器实现。调度器的实现与任务运行状态迁移,任务队列有密切的联系,可以说任务运行状态迁移和任务队列决定了调度器的实现。调度器的主要作用是在就绪队列中选择优先级最高的任务运行,如果优先级最高的任务不止一个,则选择队头的任务运行。虽然整个调度器的功能可以用上面的几句话概括,但调度器的实现远远没有那么简单,主要困难来源下面的原因:
1.确定调度器运行的时机;
2.程序完了后,是执行当前任务,还是马上调度;
3.调度器的性能;
4.调度中伴随着任务上下文的切换,尤其对处理器架构有关的上下文,应该设计良好的接口以便移植。
以上这些基本问题都是应该考虑的,随着功能的扩充和完善,调度器可能会在原先没涉及到的地方被调用,虽然在这些新地方不要求能正确调度,但至少不能引起系统崩溃。对于来说,中断处理程序执行完毕后,应该马上执行调度,这是因为中断常常伴随着有新的任务处于就绪队列中,在这些任务中可能会有高优先级的任务就绪,所以在实时内核中要求必须支持在中断后马上进行任务调度。不管是在实时系统,还是在其他系统中,调度器性能显得非常重要,常常要求调度器的至少应该为线性,当然常数是最好的。对于不同的处理器架构,其提供的寄存器,都有很大的区别,调度器应该留出良好的接口给不同的处理器,以便以后方便移植。
在实现调度器时,基本上考虑了上面的几个基本问题。根据上两节论述的任务状态迁移、队列等方面的内容,在byCore中实现了一个叫scheduler( )的调度程序。在scheduler( )中调用几个与硬件相关的函数,这几个函数主要用于实现任务硬件上下文的切换,这部分代码用汇编完成,并且与处理器有关。在现代操作系统中,会有很少一部分使用汇编语言实现,这是因为各种处理器架构的都没有被映射到可见的位置,也即象C这样的高级语言不能直接对其操作,然而,在任务切换时,硬件上下文会保存到任务中,这种操作使得高级语言无能为力。
该调度程序的算法非常简单,首先,在允许调度的情况下,如果有高优先级任务就绪,则进行任务切换。任务切换会发生在两种处理器模式下,一种是处理器处于正常的运行态,另一种发生在中断态中。因此,使用两组函数分别处理这两种情况。在两种处理器状态下都有“启动新任务”和“新旧任务切换”函数接口实现最后的任务切换工作,这两组函数与处理器有关,并由汇编实现。在后面的内核移植一节将详细论述这些函数接口的实现。
启动新任务的主要功能是将任务的初始上下文复制给处理器的各个寄存器,这包括、堆栈指针寄存器、和指令指针寄存器等。这些初始值在新任务创建时被初始化。启动新任务发生的时机有两种情况,第一种情况是初始化完毕后,启动第一个任务;第二种情况为任务主动结束后,当前任务指针被置位NULL时。
任务切换发生在两个任务之间,一个是被换切换出去的任务,另一个是将要执行的任务。任务切换函数也由汇编代码实现。它所要完成的工作主要有两个,第一是将旧任务(被换切换出去的任务)的上下文保存到自己的栈中,第二是新任务(将要执行的任务)将保存在栈中的上下文复制到处理器的相关中。任务切换的发生时机有:
· 当前任务执行时间到;
· 当前任务被高优先级任务抢占;
· 当前任务休眠,或等待某事件发生。
由于任务切换与处理器关系紧密,本章只介绍与处理器无关部分的实现,与处理器有关的部分将在移植一章中详细论述。
4.4.5 内核时钟实现
在内核时钟一节中,论述了内核时钟的作用以及功能。但在当前实现中,根据实际的情况对内核时钟的功能做了裁减,内核时钟功能主要由systick( )函数实现。
4.4.6 任务管理API实现
任何内核都应该提供一组丰富的API函数供用户使用。像UNIX、Linux、Windows这些大型操作系统提供了大量的API。当然这些API的数量、种类,用法等都会随着系统的不同而不同。但在任务管理方面下面几个API是必不可少的:任务创建、撤销、休眠、等待和唤醒等操作。下面将描述各个API的实现算法。
4.4.6.1 任务创建
当用户调用任务创建函数时,应该完成哪些工作呢?这和内核的实现方式,复杂程度密切相关。当前任务管理实现中,提供两个任务创建函数osInitTask( )和osCreateTask( )。这两个函数的原型如下所示:
void osInitTask(void (*pTask)(), uword_t TaskID, uword_t Prio, uword_t Time, uword_t StkSize);
void osCreateTask(void (*pTask)(), tcb_t *pTcb, uword_t TaskID, uword_t Prio, uword_t Time, stk_t *pStk, uword_t StkSize);
这两个函数的主要区别为任务需要的TCB和栈空间是否为动态创建。osInitTask( )函数只需要传递任务起始地址((*pTask)()),任务ID(TaskID),优先级(Prio),运行时间片(Time)和栈大小(StkSize),任务的栈和TCB空间都为动态创建,栈和TCB空间处于系统的堆区。osCreateTask( )函数除了以上的参数外还格外需要*ptcb和*pstk两个参数,这两个参数分别指向任务的TCB起始地址和栈起始地址,这个函数的空间需要在编译时制定,栈和TCB空间属于区。虽然它们需要的参数不同,但它们的实现算法是相同的。
在描述算法之前需要对任务栈做简单的论述,栈的作用是保证任务正常运行,它保存了任务中各个函数的调用轨迹和返回地址。对于处理器来说都提供一个独立的寄存器或者其他空间保存着栈顶的位置,各种处理器架构对栈顶和栈底的定义也不相同,这主要有两种,一是栈顶的地址值大于栈底,其二相反。第一种伴随着栈往下增长,第二种栈往上增长。为了便于移植内核,内核应该处理这两种情况。除了这两种情况,栈还分为满栈和空栈两种,所以必须考虑这几种栈方式。因此在实现中提供一组宏来应对这些情况,如下所示:
#define UP 1
#define DOWN 0
#define FULL 1
#define EMPTY 0
#define STACK DOWN
#define STACK_STYLE FULL
UP和DOWN定义了栈的增长方向,FULL和EMPTY说明了是满栈还是空栈。最后用STACK和STACK_STYLE联合说明真正的栈工作方式。
论述完了任务创建方面需要注意的一些问题,下面论述任务创建的算法。任务创建过程主要包含初始化TCB和栈区,如果调用osCreateTask( )函数,在初始化前还需要向申请TCB和栈空间。图4-9为osInitTask( )函数创建新任务的流程图。
4.4.6.2 任务撤销
每个任务都有一个生命周期,包括任务创建、运行与撤销。任务撤销也可称为在多任务系统中,任务也可以被任何用户杀死,也可以有特殊用户杀死。比如,杀死任务。任务撤销的方式有很多种实现方式。一般情况下,任务可以被内核杀死。在Linux下有些任务可以被任何用户杀死,有些则只能由root用户杀死。在单用户系统中,用户任务能被杀死,也可以被其他用户任务杀死,但后种情况不多见。根据实际的情况,当前对任务撤销的实现为只有任务自己主动杀死自己。
在当前实现中,任务撤销的函数为osKill( ),如果当前任务完成了自己的使命,可以调用该函数。osKill( )会释放掉该任务的相关资源,如TCB和栈空间等。osKill( )只释放掉内核分配的资源,如果任务的运行过程中申请了其他资源,应该在调用osKill( )前释放掉这些资源。任务在创建时有两个创建函数osInitTask( )和osCreateTask( ),osKill( )只能释放osInitTask( )的资源,而osCreateTask( )的资源会被保留下来。这是因为osCreateTask( )所使用的空间属于,而不属于系统动态内存管理的堆区,这部分区域没有相关的数据结构管理,一旦释放系统就会崩溃。根据上面的描述可以设计出osKill( )的算法,该算法如图4-10所示。
4.4.6.3 任务休眠与唤醒
当任务需要等待某些资源的时候,可以将自己设为,把运行的机会让给其他任务,当所等待的资源或者事件发生时,任务再被唤醒继续运行。这种方式也是解决任务同步的一种办法,如任务A与任务B合作完成某项任务,且A完成后B才能运行,休眠与唤醒机制可以很容易地解决此问题。实现了两个函数分别完成这两项工作,他们是osSleep( )和osWakeUp( ),osSleep( )是任务的主动行为,因此不需要参数,osWakeUp( )需要一个参数TaskID,该参数指定了需唤醒任务的ID号。
当任务调用osSleep( )后,该任务的TCB从就绪队列中删除,并插入到休眠队列(如图4-6所示),然后重新调度。如果任务A需要唤醒正在休眠的任务B,那么A可以调用osWakeUp( )函数,并传入B的ID。osWakeUp( )就会查找休眠队列,如果找到任务B,则将它的状态置为就绪,并从休眠队列删除插入就绪队列。
4.4.6.4 任务等待
任务等待与任务休眠的实现原理都一样。任务在等待一段时间后再获得运行的机会,这个时候它所等待的事件或者资源有可能不可用,这点和任务休眠是有差异的。例如任务A需要与串口I/O通信,由于串口速度相对较慢,任务A大部分时间都需要等待,如果任务A在没有数据传输的时候进入等待状态,将会显著提高CPU利用率。
提供了osWait( )函数来实现此功能,该函数接受一个时间参数,该参数说明当前任务等待时间长短,该时间以系统tick为单位。当前任务调用此函数后,任务状态被置为等待态,TCB从就绪队列中删除,并插入到,最后调度scheduler( )。等待队列与休眠队列相同,见图6-7所示。osWait( )函数的流程图与osSleep( )算法相似,这里不再赘述。
每次系统tick发生中断时,内核处理程序更新等待队列上任务的等待时间域,也就是任务控制块TCB的delay_time域作减1操作,当此域减少到0时,表示该任务的等待时间已到,这时它将从等待队列中删除,并插入到就绪队列中。这些工作也是时钟中断当前唯一需要做的事情。
任务管理iOS系统
软件名称:任务管理
英文名称:Any To Do
软件类型:效率
软件版本:1.28
软件语言:英文,中文等
软件大小:7.60 MB
软件现价:¥40.00 (请以iTunes实时价格为准)
支持系统:需要iOS 5.0 或更高版本
支持终端:iPhone、iPod touch、iPad兼容,已针对 iPhone 5 进行优化。
任务管理软件介绍
《任务管理》是一款待办事项类的效率软件,只要输入您的待办事项,它就会立即快速整理,高效率地按排好您的公事与私事。 它不只是协助您安排好您的任务,而且还会帮助您把每项活动安排得漂漂亮亮、井井有条。
任务管理功能特征
? 安排好您的时间而不是您的To-Dos(待办事项)  ? 与Evernote同步 - “记住所有的活动”  ? 通过Evernote与Any To Do 的iPad/ iPhone版无线同步  ? 可以创建您的全部私事与公事To-Dos(待办事项)列表并可单独或同时在四个象限内查看具体内容。  ? 可以把您的To-Dos(待办事项)添加到您的日历中  ? 可以设置提醒和重复提醒  ? 有完整的屏幕定制功能及提醒语音  ? 可以把您的To Dos (待办事项)发布或Tweet 到您的Facebook涂鸦墙或Twitter帐户上  ? Any To Do适用于英语、中文、日语、西班牙语和意大利语, 德和法国。
任务管理WP系统
应用类别 :工具
应用版本 :1.0.0.0
应用大小 :1 MB
应用语言 :中文
支持平台 :WP8,WP8.1
更新日期 :
任务管理软件介绍
这是一个任务管理应用。它能帮助管理各种任务,还能为重要任务设置闹钟。
任务管理功能特征
任务管理  将任务固定到桌面
为任务设置闹钟  改变任务状态  标记重要任务  任务排序  共享任务信息。据外媒报道,上周三在社交媒体上出现了一些针对苹果开发……
最近迷上了一款小学生玩的游戏&&王者农药,……
关于招聘这件大事,硅谷企业正利用AI给出求职者客观评价……
随着人工智能风潮的兴起,AI芯片应该具备什么样的功能就……
工业物联网正从概念和试点项目迅速演变为大规模的厂级部……
演讲人:杨正龙时间: 10:00:00
演讲人:胡志涛时间: 10:00:00
演讲人:杨熙时间: 10:00:00
预算:¥10,000-¥50,000预算:小于¥10,000
教你构造一个51单片机的实时操作系统
[导读]系统设置容量为8个任务,用户实际可用任务为7个,能够满足一般需求,也达到了在小容量芯片中应用的开发要求。由于没有采用占先式的任务调度,除开全程相关的个别任务的一些局部变量外,其他局部变量已经不存在覆盖关系,由于是任务主动放弃CPU控制权,对于个别需要保护的变量单独进行处理也变得容易。在系统中,全程不需要反复地开关中断,实时性能也很好。
目前,大多数的产品开发是在基于一些小容量的单片机上进行的。51系列单片机,是我国目前使用最多的单片机系列之一,有非常广大的应用环境与前景,多年来的资源积累,使51系列单片机仍是许多开发者的首选。针对这种情况,近几年涌现出许多基于51内核的扩展芯片,功能越来越齐全,速度越来越快,也从一个侧面说明了51系列单片机在国内的生命力。
  多年来我们一直想找一个合适的实时操作系统,作为自己的开发基础。根据开发需求,整合一些常用的嵌入式构件,以节约开发时间,尽最大可能地减少开发工作量;另外,要求这个实时操作系统能非常容易地嵌入到小容量的芯片中。毕竟,大系统是少数的,而小应用是多数而广泛的。显而易见,&C/OS&II是不太适合于以上要求的,而Keil C所带的RTX Tiny不带源代码,不具透明性,至于其FULL版本就更不用说了。
  1 Keil C51与重入问题
  说到实时操作系统,就不能不考虑重入问题。对于PC机这样的大内存处理器而言,这似乎并不是一个很麻烦的问题,借用&C/OS&II RTOS的说法,即要求在重入的函数内,使用局部变量。但5l系列单片机堆栈空间很小,仅局限在256字节之内,无法为每个函数都分配一个局部堆空间。正是由于这个原因,Keil C51使用了所谓的可覆盖技术:
  ①局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
  ②在编译链接时,即已经完成局部变量的定位;
  ③如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
  正是由于以上的原因,在Keil C51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重人的。那么在Keil C5l环境下,如何使其函数具有可重人性呢?下面分析在实时操作系统下面,任务的基本结构与模式:
  vold TaskA(void*ptr){
  UINT8 vaL_a;
  //其他一些变量定义
  //实际的用户任务处理代码
  }while(1);
  void TaskB(void*ptr){
  UINT8 vaLb;
  //其他一些变量定义
  Funcl();
  //其他实际的用户任务处理代码
  )while(1);
  void Funcl(){
  UlNT8 v al_
  //其他变量的定义
  //函数的处理代码
  在上面的代码中,TaskA与TaskB并不存在直接或间接的调用关系,因而其局部变量val_a与val_b便是可以被互相覆盖的,即其可能都被定位于某一个相同的RAM空间。这样,当TaskA运行一段时间,改变了val_a后,TaskB取得CPU控制权并运行时,便可能会改变val_b。由于其指向相同的RAM空间,导致TaskA重新取得CPU控制权时,val&a的值已经改变,从而导致程序运行不正确,反过来亦然。另一方面,Funcl()与TaskB有直接的调用关系,因而其局部变量val_fa与val_b不会被互相覆盖,但也不能保证其局部变量val_fa不会与TaskA或其他任务的局部变量形成可覆盖关系。
  将val_a、val_b以及val_fa等局部变量定义为静态变量(加上static指示符)可以解决这一问题。但问题是,定义大量的static类型变量,将导致RAM空间的大量占用,有可能直接导致RAM空间不够用。尤其是在一些小容量的单片机内,一般只有128或256字节,大量的静态变量定义,在如此小的RAM资源状况下显然就不太合适了。由此而有了另一种的解决方法,如下代码所示:
  void TaskC(void){
  UINT8 x,v;
  whlk(1){
  OS_ENTER_CRITICAL();
  x=GetX(); (1)
  y=GetY(); (2)
  //任务的其他代码
  OS_EXIT_CRITICAL(); (3)
  0SSleep(100); (4)
  以上代码TaskC中使用了临界保护的方法来保护代码不被中断占先,确实有效地解决了RAM空间太小,不宜大量定义静态变量的问题。然而如果每个任务都采用此种结构,任务一开始,就关闭中断,将使实时性得不到保证。事实证明,这种延时是相当可观的。用一个实例来说明,如果想在系统中使用一个动态刷新的LED显示器,就难以保证显示的稳定与连续,哪怕在系统中是使用一个单独的定时器来做这一工作(进入临界区后,EA=0)。其次,这种结构事实上将占先的任务调度转化为非占先的任务调度。实际上如果在(3)与(4)之间没有碰巧发生中断并导致一个任务调度,那就可以理解为是任务主动放弃CPU的控制。如果在(3)和(4)之间碰巧产生了一个中断并导致了一个任务调度,只是执行了一次多余的任务调度而已,而且并不希望在(3)之后发生2次甚至多次的任务调度,相信读者也有这一愿望。
  除此之外,还可以发现任务的一个特点:当任务从(1)重新开始时,局部变量x和y是一个什么值并不在乎,即x和y即使在(3)之后改变了,也已经不再重要,不会影响程序的正确性。其实这一特点也是大部分任务,至少是太部分任务的大部分局部变量的一个共性&&如果任务在整个执行过程中,不会(被占先)放弃CPU控制权,则其局部变量大多数并不需要进行特别的保护,即其作用域只是任务的当次执行,针对上面的代码,就是临界保护区内的代码区域。
  目前,大多数的产品开发是在基于一些小容量的单片机上进行的。51系列单片机,是我国目前使用最多的单片机系列之一,有非常广大的应用环境与前景,多年来的资源积累,使51系列单片机仍是许多开发者的首选。针对这种情况,近几年涌现出许多基于51内核的扩展芯片,功能越来越齐全,速度越来越快,也从一个侧面说明了51系列单片机在国内的生命力。
  多年来我们一直想找一个合适的实时操作系统,作为自己的开发基础。根据开发需求,整合一些常用的嵌入式构件,以节约开发时间,尽最大可能地减少开发工作量;另外,要求这个实时操作系统能非常容易地嵌入到小容量的芯片中。毕竟,大系统是少数的,而小应用是多数而广泛的。显而易见,&C/OS&II是不太适合于以上要求的,而Keil C所带的RTX Tiny不带源代码,不具透明性,至于其FULL版本就更不用说了。
  1 Keil C51与重入问题
  说到实时操作系统,就不能不考虑重入问题。对于PC机这样的大内存处理器而言,这似乎并不是一个很麻烦的问题,借用&C/OS&II RTOS的说法,即要求在重入的函数内,使用局部变量。但5l系列单片机堆栈空间很小,仅局限在256字节之内,无法为每个函数都分配一个局部堆空间。正是由于这个原因,Keil C51使用了所谓的可覆盖技术:
  ①局部变量存储在全局RAM空间(不考虑扩展外部存储器的情况);
  ②在编译链接时,即已经完成局部变量的定位;
  ③如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。
  正是由于以上的原因,在Keil C51环境下,纯粹的函数如果不加处理(如增加一个模拟栈),是无法重人的。那么在Keil C5l环境下,如何使其函数具有可重人性呢?下面分析在实时操作系统下面,任务的基本结构与模式:
  vold TaskA(void*ptr){
  UINT8 vaL_a;
  //其他一些变量定义
  //实际的用户任务处理代码
  }while(1);
  void TaskB(void*ptr){
  UINT8 vaLb;
  //其他一些变量定义
  Funcl();
  //其他实际的用户任务处理代码
  )while(1);
  void Funcl(){
  UlNT8 v al_
  //其他变量的定义
  //函数的处理代码
  在上面的代码中,TaskA与TaskB并不存在直接或间接的调用关系,因而其局部变量val_a与val_b便是可以被互相覆盖的,即其可能都被定位于某一个相同的RAM空间。这样,当TaskA运行一段时间,改变了val_a后,TaskB取得CPU控制权并运行时,便可能会改变val_b。由于其指向相同的RAM空间,导致TaskA重新取得CPU控制权时,val&a的值已经改变,从而导致程序运行不正确,反过来亦然。另一方面,Funcl()与TaskB有直接的调用关系,因而其局部变量val_fa与val_b不会被互相覆盖,但也不能保证其局部变量val_fa不会与TaskA或其他任务的局部变量形成可覆盖关系。
  将val_a、val_b以及val_fa等局部变量定义为静态变量(加上static指示符)可以解决这一问题。但问题是,定义大量的static类型变量,将导致RAM空间的大量占用,有可能直接导致RAM空间不够用。尤其是在一些小容量的单片机内,一般只有128或256字节,大量的静态变量定义,在如此小的RAM资源状况下显然就不太合适了。由此而有了另一种的解决方法,如下代码所示:
  void TaskC(void){
  UINT8 x,v;
  whlk(1){
  OS_ENTER_CRITICAL();
  x=GetX(); (1)
  y=GetY(); (2)
  //任务的其他代码
  OS_EXIT_CRITICAL(); (3)
  0SSleep(100); (4)
  以上代码TaskC中使用了临界保护的方法来保护代码不被中断占先,确实有效地解决了RAM空间太小,不宜大量定义静态变量的问题。然而如果每个任务都采用此种结构,任务一开始,就关闭中断,将使实时性得不到保证。事实证明,这种延时是相当可观的。用一个实例来说明,如果想在系统中使用一个动态刷新的LED显示器,就难以保证显示的稳定与连续,哪怕在系统中是使用一个单独的定时器来做这一工作(进入临界区后,EA=0)。其次,这种结构事实上将占先的任务调度转化为非占先的任务调度。实际上如果在(3)与(4)之间没有碰巧发生中断并导致一个任务调度,那就可以理解为是任务主动放弃CPU的控制。如果在(3)和(4)之间碰巧产生了一个中断并导致了一个任务调度,只是执行了一次多余的任务调度而已,而且并不希望在(3)之后发生2次甚至多次的任务调度,相信读者也有这一愿望。
  除此之外,还可以发现任务的一个特点:当任务从(1)重新开始时,局部变量x和y是一个什么值并不在乎,即x和y即使在(3)之后改变了,也已经不再重要,不会影响程序的正确性。其实这一特点也是大部分任务,至少是太部分任务的大部分局部变量的一个共性&&如果任务在整个执行过程中,不会(被占先)放弃CPU控制权,则其局部变量大多数并不需要进行特别的保护,即其作用域只是任务的当次执行,针对上面的代码,就是临界保护区内的代码区域。
  2 实时操作系统要不要占先
  由上面的分析,如果要保持一个函数可重人,就得使用静态变量,系统的RAM资源将是一个严峻的考验;如果使用临界区来保护运行环境,系统的实时性又得不到保证,而且有将占先式任务调度转为非占先任务调度之虞。显然,使用静态变量简单,但有更多的不适用性,对将来功能的调整也是一个阻碍,一般不被采用。那么,就只能从环境保护上来下功夫了,但是果真只能以进入临界区牺牲系统的实时性来保证任务不被占先?下面看看临界保护这一方法的基本思路:
  ①在一个任务中,如果局部变量在其作用域内不被占先切换,则这些变量在任务被剥夺了CPU控制权后,不关心其值也不会影响任务的正确执行;
  ②使用临界区保护,可以达到上面所提到的要求;
  ③由此导致的实时性能与占先切换的减弱可以接受。由此可知,不被占先是任务保护局部变量的关键。既然如此,何不舍弃占先式的任务调度?这不失为一个好的出发点。针对Keil C51,非占先式任务调度,可能是一种更好的方法,更能协调51系列单片机的既定资源。下面编写这样一个系统:
  ①使用非占先式任务调度;
  ②可以在小容量的芯片中使用,开发目标是,即使是8051这样小的芯片,也可使用这个实时操作系统;
  ③支持优先级调度,尽可能保证其实时性。
  3 实时操作系统的实现
  基于以上的分析与目的,近日完成了这个操作系统。在堆栈上借用RTx的管理方法,即当前任务使用全部的堆空间,如图1所示。
  3.1 堆栈的初始化与任务的创建
  堆栈的初始化实际是初始化0STaskStackBotton数组,并将当前任务指定为空闲任务,下一个运行任务指定为最高优先级任务,即优先级为零的任务。初始化时,将SP的值存人OSTaslkStackBotton[O],SP+2的值存入OSTaskStacKBotton[1],依此类推。而任务是调用0STa-skCreate函数建立的。实际上只是将任务(假设为n号任务)的地址填人到对应OSTaskStackBotton[n]所指向的位置,并将SP向后移动2个字节,如图2所示。
  为什么要以这样一种规律而不是其他的方式呢?这是由于在任务建立后,还未进行任务调度之前,各任务的堆栈实际上是它们自身的地址,因而其堆栈深度为2,为了程序的简便而直接填入。
  void main(void){
  OSInit(); /*初始化OSTaskStackBcBotton队列*/
  TMOD=(TMOD&0XFO)│ 0XOl;
  TL0=0xBF;
  TH0=0xFC;
  TRO=1;
  ETO=1;
  TFO=O:
  OSTaskCreate(TaskA,NULL,0);
  OSTaskCreate(TaskB.NULL,1);
  OSTaskCreate(TaskC,NULL,2);
  OSStart();
  上面这段代码中,所有任务建立后,便调用OSStart()开始任务调度。OSStart()是一个宏定义,如下所示:
  #deflne OSStart() d0{\
  OSTaskCreate(TaskIdle,NULL,OS_MAX_TASKS);\
  EA=l:\
  }while(O)
  首先,它创建了一个空闲任务并打开中断,然后便返回。返回到哪里了呢?我们知道,空闲任务是优先级最低的任务,当调OSTaskCreate建立时,会将其地址填人到SP的位置,并把SP向后移动2个字节(见图2及说明),因而此时处在堆栈顶端的,一定是空闲任务Taslddle。这就使得这里的return一定会返回到空闲任务。至此,系统进入正常运行状态。
  3.2 任务的切换
  任务的切换分两种情况,在当前任务优先级低于下一个取得CPU控制权的任务时,将下一个取得CPU控制权的任务的栈顶到当前任务的栈顶之间的内容向RAM空间的高端搬移,以空出全部的RAM空间作下一个任务的堆空间,同时更新对应的OSTaskStackBotton,使其指向新的正确任务的堆栈栈底。如果当前任务的优先级高于下一个任务的优先级,则作相反的搬移,如图3与图4所示。
  所有任务必须主动调用OSSleep,放弃CPU的控制权。任务调用OSSleep后,将选择优先级最高的就绪任务运行。
  系统完成后,内核的代码量在400多个字节左右,占用1个定时器中断及小量的内存空间。系统设置容量为8个任务,用户实际可用任务为7个,能够满足一般需求,也达到了在小容量芯片中应用的开发要求。由于没有采用占先式的任务调度,除开全程相关的个别任务的一些局部变量外,其他局部变量已经不存在覆盖关系,由于是任务主动放弃CPU控制权,对于个别需要保护的变量单独进行处理也变得容易。在系统中,全程不需要反复地开关中断,实时性能也很好。对个别时序要求严格的外设(如DSl8820)除外。
FZ-MCU51单片机开发板集实验开发、编程、仿真和下载于一体,支持AT89S51、AT89S52、AT89S53的和STC系列单片机如STC89C53RC等全系列单片机的编程和实验,使用配套的仿真模......关键字:
一周前,HMD在国内首发了掌管诺基亚之后的首款产品:Nokia 6,其售价为1699元。
......关键字:
北京时间2月6日晚间消息,美媒报道称,亚马逊计划推出一种以机器人为主的杂货店,每班最少只需要3名人类员工。......关键字:
马云没有遮掩他想投资国安的野心,为此,他跑到足协跟蔡振华等足协高官进行沟通,他给出的最具有说服力的一个理由是,北京国安的人在他当年非常落魄的时候,曾经非常慷慨地帮助过他――尽管只有2000元钱。......关键字:
我 要 评 论
热门关键词}

我要回帖

更多关于 任务管理器优先级 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信