uCos的多任务实现

作为操作系统(OS),最基本的一项服务就是提供多线程,在实时操作系统uCos里,多线程被称为多任务(Task)。多任务并不是CPU能真正同时运行多个程序,实际是靠CPU在多个任务之间转换切换实现的,CPU轮番的服务于一系列的任务,这样CPU在宏观上好像在同时执行多个任务,实际在微观上CPU绝对是“单任务”的。这里要注意区别多线程和多核,如果系统里是有多个CPU,则可以实现真正的多线程了。

按照上面的思路,多任务的实现,就是要实现CPU在不同的任务之间切换。按照uCos作者的话说:“就是不断的保存,恢复CPU的那些寄存器”。

我们知道uCos的多任务(这里以两个任务为例)的程序模型如下:

void  Task_A(void *p_arg)

{

while(1)

{

OSTimeDly(10);  //任务里必须有类似的主动释放CPU的函数

......

}

}

void  Task_B(void *p_arg)

{

while(1)

{

OSTimeDly(10);

......

}

}

使用uCos,程序将在这两个任务之间轮换,这两个while(1)里的程序都可以得到执行。

我们知道,在裸机编程里,如果出现while(1)这样的语句,那么程序将永远在这个循环里执行(当然是要程序主动调用break除外),他是不会放弃CPU的,那为什么加了操作系统后,程序能在这两个while(1)之间轮换?

操作系统都需要使用“时钟节拍”技术来实现对任务的监控,并能主动调度和切换任务的执行。uCos也同样是使用“时钟节拍”来实现任务的监管的,以STM32单片机为例,一般用SysTick这个系统时钟定时器来实现,比如我们设置这个定时器10ms的定时间隔,那么每隔10ms都会调用下面的中断服务(所以移植的时候,需要实现这个函数):

void SysTickHandler(void)

{

OSIntEnter();

OSTimeTick();

OSIntExit();

}

假设现在程序在Task_A的while(1)里,然后SysTick中断来了,SysTickHandler服务程序开始执行,先执行OSIntEnter():

void  OSIntEnter (void)

{

if (OSRunning == OS_TRUE) {

if (OSIntNesting < 255u) {

OSIntNesting++;

}

}

}

这个函数很简单,他是uCos的内核函数,主要是给中断嵌套计数器OSIntNesting加一,因为uCos内核需要实时的判断程序的当前执行是不是在中断里,要知道,大部分的处理器是可以中断嵌套的,这里我们先不管判断程序是不是在中断里有什么用,后面马上会说到。

然后开始执行OSTimeTick(),这个函数我们只分析关键代码,也就是跟任务管理有关的代码:

void  OSTimeTick (void)

{

OS_TCB    *ptcb;

#if OS_TICK_STEP_EN > 0

BOOLEAN    step;

#endif

#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register     */

OS_CPU_SR  cpu_sr = 0;

#endif

#if OS_TIME_TICK_HOOK_EN > 0

OSTimeTickHook();                                      /* Call user definable hook                     */

#endif

#if OS_TIME_GET_SET_EN > 0

OS_ENTER_CRITICAL();                                   /* Update the 32-bit tick counter               */

OSTime++;

OS_EXIT_CRITICAL();

#endif

if (OSRunning == OS_TRUE) {

#if OS_TICK_STEP_EN > 0

switch (OSTickStepState) {                         /* Determine whether we need to process a tick  */

case OS_TICK_STEP_DIS:                         /* Yes, stepping is disabled                    */

step = OS_TRUE;

break;

case OS_TICK_STEP_WAIT:                        /* No,  waiting for uC/OS-View to set ...       */

step = OS_FALSE;                          /*      .. OSTickStepState to OS_TICK_STEP_ONCE */

break;

case OS_TICK_STEP_ONCE:                        /* Yes, process tick once and wait for next ... */

step            = OS_TRUE;                /*      ... step command from uC/OS-View        */

OSTickStepState = OS_TICK_STEP_WAIT;

break;

default:                                       /* Invalid case, correct situation              */

step            = OS_TRUE;

OSTickStepState = OS_TICK_STEP_DIS;

break;

}

if (step == OS_FALSE) {                            /* Return if waiting for step command           */

return;

}

#endif

ptcb = OSTCBList;                                  /* Point at first TCB in TCB list               */

while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {     /* Go through all TCBs in TCB list              */

OS_ENTER_CRITICAL();

if (ptcb->OSTCBDly != 0) {                     /* No, Delayed or waiting for event with TO     */

if (--ptcb->OSTCBDly == 0) {               /* Decrement nbr of ticks to end of delay       */

/* Check for timeout                            */

if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {

ptcb->OSTCBStat   &= ~OS_STAT_PEND_ANY;                /* Yes, Clear status flag   */

ptcb->OSTCBPendTO  = OS_TRUE;                          /* Indicate PEND timeout    */

} else {

ptcb->OSTCBPendTO  = OS_FALSE;

}

if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */

OSRdyGrp               |= ptcb->OSTCBBitY;             /* No,  Make ready          */

OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

}

}

}

ptcb = ptcb->OSTCBNext;                        /* Point at next TCB in TCB list                */

OS_EXIT_CRITICAL();

}

}

}

在这个函数里,出现了一个全局变量OSTCBList,这个变量是uCos内核里任务控制块(TCB)链表的表头指针,问题又来了,TCB是什么,TCB链表又是什么?

原来uCos的每个任务都需要有一个TCB来管理,这个TCB记录了该任务的所有信息,同时uCos用链表来管理所有的这些TCB,TCB的结构如下:

typedef struct os_tcb {

OS_STK          *OSTCBStkPtr;      /* Pointer to current top of stack                              */

#if OS_TASK_CREATE_EXT_EN > 0

void            *OSTCBExtPtr;      /* Pointer to user definable data for TCB extension             */

OS_STK          *OSTCBStkBottom;   /* Pointer to bottom of stack                                   */

INT32U           OSTCBStkSize;     /* Size of task stack (in number of stack elements)             */

INT16U           OSTCBOpt;         /* Task options as passed by OSTaskCreateExt()                  */

INT16U           OSTCBId;          /* Task ID (0..65535)                                           */

#endif

struct os_tcb   *OSTCBNext;        /* Pointer to next     TCB in the TCB list                      */

struct os_tcb   *OSTCBPrev;        /* Pointer to previous TCB in the TCB list                      */

#if OS_EVENT_EN

OS_EVENT        *OSTCBEventPtr;    /* Pointer to event control block                               */

#endif

#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)

void            *OSTCBMsg;         /* Message received from OSMboxPost() or OSQPost()              */

#endif

#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)

#if OS_TASK_DEL_EN > 0

OS_FLAG_NODE    *OSTCBFlagNode;    /* Pointer to event flag node                                   */

#endif

OS_FLAGS         OSTCBFlagsRdy;    /* Event flags that made task ready to run                      */

#endif

INT16U           OSTCBDly;         /* Nbr ticks to delay task or, timeout waiting for event        */

INT8U            OSTCBStat;        /* Task status                                                  */

BOOLEAN          OSTCBPendTO;      /* Flag indicating PEND timed out (OS_TRUE == timed out)           */

INT8U            OSTCBPrio;        /* Task priority (0 == highest)                                 */

INT8U            OSTCBX;           /* Bit position in group  corresponding to task priority        */

INT8U            OSTCBY;           /* Index into ready table corresponding to task priority        */

#if OS_LOWEST_PRIO <= 63

INT8U            OSTCBBitX;        /* Bit mask to access bit position in ready table               */

INT8U            OSTCBBitY;        /* Bit mask to access bit position in ready group               */

#else

INT16U           OSTCBBitX;        /* Bit mask to access bit position in ready table               */

INT16U           OSTCBBitY;        /* Bit mask to access bit position in ready group               */

#endif

#if OS_TASK_DEL_EN > 0

INT8U            OSTCBDelReq;      /* Indicates whether a task needs to delete itself              */

#endif

#if OS_TASK_PROFILE_EN > 0

INT32U           OSTCBCtxSwCtr;    /* Number of time the task was switched in                      */

INT32U           OSTCBCyclesTot;   /* Total number of clock cycles the task has been running       */

INT32U           OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption        */

OS_STK          *OSTCBStkBase;     /* Pointer to the beginning of the task stack                   */

INT32U           OSTCBStkUsed;     /* Number of bytes used from the stack                          */

#endif

#if OS_TASK_NAME_SIZE > 1

INT8U            OSTCBTaskName[OS_TASK_NAME_SIZE];

#endif

} OS_TCB;

这结构体稍微有点大,这里我们先知需要关心OSTCBDly这个成员,这个成员记录该任务的延时值,当我们调用uCos的系统函数OSTimeDly(),OSQPend()等这些阻塞类型的函数时,该任务的TCB成员OSTCBDly就会被赋予相应值。

我们继续分析OSTimeTick(),这个函数,在这个函数里,主要是给每个TCB的OSTCBDly的计数值减一,如果OSTCBDly为零了,则在任务就绪表里标记该任务,这里又引出了一问题,什么是就绪表,uCos的就绪表的实现用了一个比较巧妙的算法,这里先不仔细去分析,只有知道就绪表里记录了当前系统中,哪些任务是处于就绪态,也就是可被CPU执行的状态。

好,到这里OSTimeTick()分析完了,他完成了对每个任务的延时值减一操作,并更新了就绪表。下面开始执行OSIntExit(),这个函数是个很关键的函数,她将实现任务的上下文切换。

OSIntExit函数的源码如下:

void  OSIntExit (void)

{

#if OS_CRITICAL_METHOD == 3

OS_CPU_SR  cpu_sr = 0;

#endif

if (OSRunning == OS_TRUE) {   /* 如果uCos还没有启动运行,则不进行任务调度,因为这时候uCos的初始化还没完成 */

OS_ENTER_CRITICAL();

if (OSIntNesting > 0) {    /* 将中断嵌套计数器减一 */

OSIntNesting--;

}

if (OSIntNesting == 0) {   /* 只有在中断嵌套为0,也就是即将退出中断时才进行任务调度 */

if (OSLockNesting == 0) {

OS_SchedNew();/* 找出当前就绪表中的最高优先级任务,并更新全局变量OSPrioHighRdy */

if (OSPrioHighRdy != OSPrioCur) {/*如果发现当前最高优先级任务不是正在运行的任务,就要进行任务切换*/

OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

#if OS_TASK_PROFILE_EN > 0

OSTCBHighRdy->OSTCBCtxSwCtr++;

#endif

OSCtxSwCtr++;

OSIntCtxSw(); /*调用专门用于中断服务里的上下文切换函数,进行山下文的切换 */

}

}

}

OS_EXIT_CRITICAL();

}

}

从上面的源码和代码注释可以总结出该函数完成的功能为:找出就绪表里的最高优先级任务,并执行OSIntCtxSw函数来进行任务切换。这里要注意,虽然执行了任务切换,但不会立刻进行上下文的切换,这个实现过程跟CPU的硬件有关,在STM32中,上下文的切换是用的“悬起中断”,该中断只有当CPU的所有中断完成了,也就是退出了所有的中断嵌套后,才会执行。OSIntCtxSw函数是移植uCos的一个非常关键的函数,他负责恢复运行任务的上次中断现场,这个函数跟CPU体系有紧密的联系,这里先不仔细分析。

到这里我们基本可以看到uCos利用系统时钟滴答实现多任务的大概流程如下:

uCos的任务切换过程就分析跟踪完了,这里要注意几点,任务的切换并不只会发生在系统时钟滴答的中断服务里,调用发送信号量,发送消息等这些系统函数时也会引起任务切换,但大致的原理都差不多,这里我们只对程序的原理和框架做了说明,实际还是有些细节需要处理的。

uCos的多任务实现的更多相关文章

  1. ucos任务控制块详解

    Ucos实现多任务的基础包括几个方面:任务控制块,任务堆栈,中断,任务优先级,一一说起 首先,任务控制块的结构如下 //系统在运行一个任务的时候,按照任务的优先级获取任务控制块,再在任务堆栈中获得任务 ...

  2. 全球最低功耗蓝牙单芯片DA14580的软件体系 -RW内核和消息处理机制

    上一篇文章<蓝牙单芯片DA14580的硬件架构和低功耗>阐述了DA14580的硬件架构和低功耗的工作原理.本文文章阐述该平台的软件体系,并着重分析消息事件的处理机制. 一.DA14580S ...

  3. stm32学习基本知识点

    1.AHB系统总线分为APB1(36MHz)和APB2(72MHz),其中2>1,意思是APB2接高速设备 2.Stm32f10x.h相当于reg52.h(里面有基本的位操作定义),另一个为st ...

  4. 一步步学习操作系统(1)——参照ucos,在STM32上实现一个简单的多任务(“啰里啰嗦版”)

    该篇为“啰里啰嗦版”,另有相应的“精简版”供参考 “不到长城非好汉:不做OS,枉为程序员” OS之于程序员,如同梵蒂冈之于天主教徒,那永远都是块神圣的领土.若今生不能亲历之,实乃憾事! 但是,圣域不是 ...

  5. UCOS 多任务系统中需要注意的地方 一个任务至少要有一些执行内容

    图片说明: 在一个TASK的最外层FOR();中  如果有调用BREAK会怎样??

  6. [stm32][ucos][ucgui] 2、LED闪烁、串口、滑块、文本编辑框简单例程

    上一篇:[stm32][ucos] 1.基于ucos操作系统的LED闪烁.串口通信简单例程 * 内容简述: 本例程操作系统采用ucos2.86a版本, 建立了7个任务            任务名   ...

  7. Vxworks、QNX、Xenomai、Intime、Sylixos、Ucos等实时操作系统的性能特点

    Vxworks.QNX.Xenomai.Intime.Sylixos.Ucos等实时操作系统的性能特点 VxWorks操作系统 VxWorks 操作系统是美国WindRiver公司于1983年设计开发 ...

  8. ucos系统初始化及启动过程

    之前在ucos多任务切换中漏掉了一个变量, OSCtxSwCtr标识系统任务切换次数 主要应该还是用在调试功能中 Ucos系统初始化函数为OSInit(),主要完成以下功能 全局变量初始化 就绪任务表 ...

  9. 基于嵌入式操作系统VxWorks的多任务并发程序设计(1)――基本概念

    1引言 嵌入式系统定义义为:嵌入到对象体系中的专用计算机系统."嵌入性"."专用性"与"计算机系统"是嵌入式统的三个基本要素,对象系统则是指 ...

随机推荐

  1. Android:自定义控件样式(Selector)

    前言 在开发一个应用程序过程中不可避免的要去修改组件的样式,比如按钮.输入框等.现在就看下如何通过Seletor实现样式的自定义.先看下简单的效果对比

  2. [大牛翻译系列]Hadoop(11)MapReduce 性能调优:诊断一般性能瓶颈

    6.2.4 任务一般性能问题 这部分将介绍那些对map和reduce任务都有影响的性能问题. 技术37 作业竞争和调度器限制 即便map任务和reduce任务都进行了调优,但整个作业仍然会因为环境原因 ...

  3. VisualVM 监控

    一:服务器端: 找到 jstatd 所在目录 find / -name jstatd 在此目录下添加 jstatd.all.policy 文件 cat /usr/java/jdk1.7.0_51/bi ...

  4. WordPress 主题开发 - (五)WordPress 主题模板及目录结构 待翻译

    While the most minimal of WordPress Themes really only need an index.php template and a style.css fi ...

  5. WPF 系统托盘 图标闪烁

    WPF消息通知 系统托盘,图标闪烁 using System.Windows.Forms; using System.Windows.Threading; public partial class W ...

  6. (转)Windows驱动编程基础教程

    版权声明     本书是免费电子书. 作者保留一切权利.但在保证本书完整性(包括版权声明.前言.正文内容.后记.以及作者的信息),并不增删.改变其中任何文字内容的前提下,欢迎任何读者 以任何形式(包括 ...

  7. EWOULDBLOCK = EAGAIN

    #define EAGAIN 11 /* Try again */ #define EINTR 4 /* Interrupted system call */ #define EWOULDBLOCK ...

  8. [转]system函数返回值探究

    对于system这个函数的功能早就有一定了解,读书期间,就学习了UNIX系统编程这本书,后来买了APUE.我这个人总是有好读书不求甚解的毛病.对于system函数只知其一,不知其二.后来被人问起相关的 ...

  9. Hadoop2

    http://www.cnblogs.com/miaoxiaoyu/archive/2012/07/29/2614060.html

  10. uImage、zImage、bzImage、vlinzx区别

    在网络中,不少服务器采用的是Linux系统.为了进一步提高服务器的性能,可能需要根 据特定的硬件及需求重新编译Linux内核.编译Linux 内核,需要根据规定的步骤进行,编译内核过程中涉及到几个重要 ...