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. 版本控制器 (Svn,Git)

    Svn: 集中式版本控制器,首先开发者在开始新一天的工作之前必须从服务器获取代码,然后进入自己的分支开发,开发完成后把自己的分支合并到主分支上进行提交,解决冲突.所有的版本信息都放在服务器上.如果脱离 ...

  2. java 高精度

    package BigDecimal; import java.math.BigDecimal; import java.lang.Object; public class BigDecimalTes ...

  3. How to changes to Table & EDT Relations[AX2012]

    Well I hope everyone is having a fine week so far. Oh Wednesdays, the furthermost point between two ...

  4. hibernate数据库配置

    在文件夹etc中 ## MySQL #hibernate.dialect org.hibernate.dialect.MySQLDialect #hibernate.dialect org.hiber ...

  5. 11g RAC R2 体系结构---进程,日志

    进程结构:Overview of Oracle Clusterware Platform-Specific Software Components When Oracle Clusterware is ...

  6. 【笔记】W3C CSS关键属性

    white-space属性: white-space 属性设置如何处理元素内的空白. 可能的值 值 描述 normal 默认值,合并所有空格,换行符会被浏览器忽略 pre 空白会被浏览器保留.其行为方 ...

  7. Oracle中的if...then...elsif

    if...then...elsif实现多分支判断语句 其语法如下: if <condition_expression1> then plsql_sentence_1; elseif< ...

  8. Android 中的WiFi剖析

    Android的WiFi 我们通常看到WiFi的守护进程wpa_supplicant在我们的ps的进程列表中,这个就是我们的wifi守护进程.wpa_supplicant在external/wpa_s ...

  9. Jquery each 的跳出 break continue

    在Jquery each 中 break 是用 return false; continue 是用 return true;

  10. javascript中出现identifier starts immediately after numeric literal错误原因以及解决方法

    javascript遇到参数是字符型和数字型组合的参数就会出现这种错误,例如alert(1);可以正確輸出alert(str);就會報錯alert("str");可以正確輸出.