0-1write MC/OS __Basics4

2021-07-24 10:04:51 星期六

九、实现时基列表

什么是时基列表

时基列表是与时间相关的,处于延时的任务和等待事件有超时限制的任务都会从就绪列表中移除,然后插入时基列表。
时基列表在OSTimeTIck中更新,如果任务的延时时间结束或者超时到期, 就会让任务就绪,从时基列表移除,插入就绪列表。

1、实现时基列表

1.1、定义时基列表变量

/* 时基列表大小,在os_cfg_app.h 定义 */
#define  OS_CFG_TICK_WHEEL_SIZE           17u

/* 在os_cfg_app.c 定义 */
/* 时基列表 */
(1)(2)
OS_TICK_SPOKE  OSCfg_TickWheel[OS_CFG_TICK_WHEEL_SIZE];
/* 时基列表大小 */
OS_OBJ_QTY const OSCfg_TickWheelSize = (OS_OBJ_QTY  )OS_CFG_TICK_WHEEL_SIZE;


/* 在os.h中声明 */
/* 时基列表 */
extern  OS_TICK_SPOKE  OSCfg_TickWheel[];
/* 时基列表大小 */
extern  OS_OBJ_QTY    const OSCfg_TickWheelSize;


/* Tick 计数器,在os.h中定义 */
OS_EXT            OS_TICK                OSTickCtr;(3)
typedefstruct  os_tick_spoke       OS_TICK_SPOKE;(1)

struct  os_tick_spoke {
    OS_TCB              *FirstPtr;(2)
    OS_OBJ_QTY           NbrEntries;(3)
    OS_OBJ_QTY           NbrEntriesMax;(4)
};
//时基列表OSCfg_TickWheel[]的每个成员都包含一条单向链表, 被插入该条链表的TCB会按照延时时间做升序排列。
FirstPtr用于指向这条单向链表的第一个节点。
NbrEntries表示该条单向链表当前有多少个节点。
NbrEntriesMax记录该条单向链表最多的时候有多少个节点, 在增加节点的时候会刷新,在删除节点的时候不刷新。

1.2、修改任务控制块TCB

时基列表OSCfg_TickWheel[]的每个成员都包含一条单向链表,被插入该条链表的TCB会按照延时时间做升序排列,为了TCB能按照延时时间从小到大串接在一起, 需要在TCB中加入几个成员

struct os_tcb {
    CPU_STK         *StkPtr;
    CPU_STK_SIZE    StkSize;

    /* 任务延时周期个数 */
    OS_TICK         TaskDelayTicks;

    /* 任务优先级 */
    OS_PRIO         Prio;

    /* 就绪列表双向链表的下一个指针 */
    OS_TCB          *NextPtr;
    /* 就绪列表双向链表的前一个指针 */
    OS_TCB          *PrevPtr;

    /* 时基列表相关字段 */
    OS_TCB          *TickNextPtr;//指向链表中的下一个TCB节点。
    OS_TCB          *TickPrevPtr;//指向链表中的上一个TCB节点
    OS_TICK_SPOKE   *TickSpokePtr;//每个被插入链表的TCB都包含一个字段TickSpokePtr,用于回指到链表的根部。

    OS_TICK         TickCtrMatch;//等于时基计数器OSTickCtr的值加上TickRemain的值, 当TickCtrMatch的值等于OSTickCtr的值的时候,表示等待到期,TCB会从链表中删除。
    OS_TICK         TickRemain;//用于设置任务还需要等待多少个时钟周期,每到来一个时钟周期,该值会递减。
};

1.3、实现时基列表相关函数

  • OS_TickListInit()

/* 初始化时基列表的数据域 */
void  OS_TickListInit (void)   //初始化时基列表,即将全局变量OSCfg_TickWheel[]的数据域全部初始化为0
{
    OS_TICK_SPOKE_IX   i;
    OS_TICK_SPOKE     *p_spoke;

    for (i = 0u; i < OSCfg_TickWheelSize; i++) {
        p_spoke                = (OS_TICK_SPOKE *)&OSCfg_TickWheel[i];
        p_spoke->FirstPtr      = (OS_TCB        *)0;
        p_spoke->NbrEntries    = (OS_OBJ_QTY     )0u;
        p_spoke->NbrEntriesMax = (OS_OBJ_QTY     )0u;
    }
}
  • OS_TickListInsert()

/* 将一个任务插入时基列表,根据延时时间的大小升序排列 */
void  OS_TickListInsert (OS_TCB *p_tcb,OS_TICK time)   //往时基列表中插入一个任务TCB
{
    OS_TICK_SPOKE_IX   spoke;
    OS_TICK_SPOKE     *p_spoke;
    OS_TCB            *p_tcb0;
    OS_TCB            *p_tcb1;

    p_tcb->TickCtrMatch = OSTickCtr + time;(1)
    p_tcb->TickRemain   = time;(2)

    spoke   = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize);(3)
    p_spoke = &OSCfg_TickWheel[spoke];(4)

    /* 插入 OSCfg_TickWheel[spoke] 的第一个节点 */
    if (p_spoke->NbrEntries == (OS_OBJ_QTY)0u) (5)
    {
        p_tcb->TickNextPtr   = (OS_TCB   *)0;
        p_tcb->TickPrevPtr   = (OS_TCB   *)0;
        p_spoke->FirstPtr    =  p_tcb;
        p_spoke->NbrEntries  = (OS_OBJ_QTY)1u;
    }
    /* 如果插入的不是第一个节点,则按照TickRemain大小升序排列 */
    else (6)
    {
        /* 获取第一个节点指针 */
        p_tcb1 = p_spoke->FirstPtr;
        while (p_tcb1 != (OS_TCB *)0)
        {
            /* 计算比较节点的剩余时间 */
            p_tcb1->TickRemain = p_tcb1->TickCtrMatch - OSTickCtr;

            /* 插入比较节点的后面 */
            if (p_tcb->TickRemain > p_tcb1->TickRemain)
            {
                if (p_tcb1->TickNextPtr != (OS_TCB *)0)
                {
                    /* 寻找下一个比较节点 */
                    p_tcb1 =  p_tcb1->TickNextPtr;
                }
                else
                {  /* 在最后一个节点插入 */
                    p_tcb->TickNextPtr   = (OS_TCB *)0;
                    p_tcb->TickPrevPtr   =  p_tcb1;
                    p_tcb1->TickNextPtr  =  p_tcb;
                    p_tcb1               = (OS_TCB *)0;(7)
                }
            }
            /* 插入比较节点的前面 */
            else
            {
                /* 在第一个节点插入 */
                if (p_tcb1->TickPrevPtr == (OS_TCB *)0) {
                    p_tcb->TickPrevPtr   = (OS_TCB *)0;
                    p_tcb->TickNextPtr   =  p_tcb1;
                    p_tcb1->TickPrevPtr  =  p_tcb;
                    p_spoke->FirstPtr    =  p_tcb;
                }
                else
                {
                    /* 插入两个节点之间 */
                    p_tcb0               =  p_tcb1->TickPrevPtr;
                    p_tcb->TickPrevPtr   =  p_tcb0;
                    p_tcb->TickNextPtr   =  p_tcb1;
                    p_tcb0->TickNextPtr  =  p_tcb;
                    p_tcb1->TickPrevPtr  =  p_tcb;
                }
                /* 跳出while循环 */
                p_tcb1 = (OS_TCB *)0;(8)
            }
        }

        /* 节点成功插入 */
        p_spoke->NbrEntries++;(9)
    }

    /* 刷新NbrEntriesMax的值 */
    if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) //刷新NbrEntriesMax的值,NbrEntriesMax用于记录当前链表曾经最多有多少个节点, 只有在增加节点的时候才刷新,在删除节点的时候
    {
        p_spoke->NbrEntriesMax = p_spoke->NbrEntries;//是不刷新的。
    }

    /* 任务TCB中的TickSpokePtr回指根节点 */
    p_tcb->TickSpokePtr = p_spoke;(11)
}
//(3):由任务的TickCtrMatch 对时基列表的大小OSCfg_TickWheelSize进行求余操作, 得出的值spoke作为时基列表OSCfg_TickWheel[]的索引。只要是任务的TickCtrMatch对OSCfg_TickWheelSize求余后得到的值spoke相等, 那么任务的TCB就会被插入OSCfg_TickWheel[spoke]下的单向链表中,节点按照任务的TickCtrMatch值做升序排列。 举例:在时基列表中有三个TCB 中,时基列表OSCfg_TickWheel[]的大小OSCfg_TickWheelSize等于12, 当前时基计数器OSTickCtr的值为10,有三个任务分别需要延时TickTemain=1、TickTemain=23和TickTemain=25个时钟周期, 三个任务的TickRemain加上OSTickCtr可分别得出它们的TickCtrMatch等于11、23和35, 这三个任务的TickCtrMatch对OSCfg_TickWheelSize求余操作后的值spoke都等于11,所以这三个任务的TCB会被插入OSCfg_TickWheel[11]下的同一条链表, 节点顺序根据TickCtrMatch的值做升序排列。
  • OS_TickListRemove()

/* 从时基列表中移除一个任务 */
void  OS_TickListRemove (OS_TCB  *p_tcb)  //从时基列表删除一个指定的TCB节点
{
    OS_TICK_SPOKE  *p_spoke;
    OS_TCB         *p_tcb1;
    OS_TCB         *p_tcb2;

    /* 获取任务TCB所在链表的根指针 */
    p_spoke = p_tcb->TickSpokePtr;(1)

    /* 确保任务在链表中 */
    if (p_spoke != (OS_TICK_SPOKE *)0)
    {
        /* 将剩余时间清零 */
        p_tcb->TickRemain = (OS_TICK)0u;

        /* 要移除的刚好是第一个节点 */
        if (p_spoke->FirstPtr == p_tcb) (2)
        {
            /* 更新第一个节点,原来的第一个节点需要被移除 */
            p_tcb1            = (OS_TCB *)p_tcb->TickNextPtr;
            p_spoke->FirstPtr = p_tcb1;
            if (p_tcb1 != (OS_TCB *)0)
            {
                p_tcb1->TickPrevPtr = (OS_TCB *)0;
            }
        }
        /* 要移除的不是第一个节点 */(3)
        else
        {
            /* 保存要移除的节点的前后节点的指针 */
            p_tcb1              = p_tcb->TickPrevPtr;
            p_tcb2              = p_tcb->TickNextPtr;

            /* 节点移除,将节点前后的两个节点连接在一起 */
            p_tcb1->TickNextPtr = p_tcb2;
            if (p_tcb2 != (OS_TCB *)0)
            {
                p_tcb2->TickPrevPtr = p_tcb1;
            }
        }

        /* 复位任务TCB中时基列表相关的字段成员 */(4)
        p_tcb->TickNextPtr  = (OS_TCB        *)0;
        p_tcb->TickPrevPtr  = (OS_TCB        *)0;
        p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0;
        p_tcb->TickCtrMatch = (OS_TICK        )0u;

        /* 节点减1 */
        p_spoke->NbrEntries--;(5)
    }
}
  • OS_TickListUpdate()

void  OS_TickListUpdate (void)         //每个SysTick周期到来时在OSTimeTick()被调用,用于更新时基计数器OSTickCtr, 扫描时基列表中的任务延时是否到期
{
    OS_TICK_SPOKE_IX   spoke;
    OS_TICK_SPOKE     *p_spoke;
    OS_TCB            *p_tcb;
    OS_TCB            *p_tcb_next;
    CPU_BOOLEAN        done;

    CPU_SR_ALLOC();

    /* 进入临界段 */
    OS_CRITICAL_ENTER();

    /* 时基计数器++ */
    OSTickCtr++;(1)

    spoke    = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize);(2)
    p_spoke  = &OSCfg_TickWheel[spoke];

    p_tcb    = p_spoke->FirstPtr;
    done     = DEF_FALSE;

    while (done == DEF_FALSE)
    {
        if (p_tcb != (OS_TCB *)0) (3)
        {
            p_tcb_next = p_tcb->TickNextPtr;

            p_tcb->TickRemain = p_tcb->TickCtrMatch - OSTickCtr;//链表不为空,递减第一个节点的TickRemain。

            /* 节点延时时间到 */
            if (OSTickCtr == p_tcb->TickCtrMatch)//判断第一个节点的延时时间是否到,如果到期,让任务就绪, 即将任务从时基列表删除,插入就绪列表,这两步由函数OS_TaskRdy()来完成
            {
                /* 让任务就绪 */
                OS_TaskRdy(p_tcb);
            }
            else (6)
            {
                /* 如果第一个节点延时期未满,则退出while循环
                因为链表是根据升序排列的,第一个节点延时期未满,那后面的肯定未满 */
                done = DEF_TRUE;
            }

            /* 如果第一个节点延时期满,则继续遍历链表,看看还有没有延时期满的任务
            如果有,则让它就绪 */
            p_tcb = p_tcb_next;(7)
        }
        else
        {
            done  = DEF_TRUE;(8)
        }
    }

    /* 退出临界段 */
    OS_CRITICAL_EXIT();
}
//计算要扫描的时基列表的索引,每次只扫描一条链表。 时基列表里面有可能有多条链表,为啥只扫描其中一条链表就可以?因为任务在插入时基列表的时候, 插入的索引值spoke_insert是通过//TickCtrMatch对OSCfg_TickWheelSize求余得出, 现在需要扫描的索引值spoke_update是通过OSTickCtr对OSCfg_TickWheelSize求余得出, TickCtrMatch的值等于OSTickCt加上TickRemain,只有//在经过TickRemain个时钟周期后, spoke_update的值才有可能等于spoke_insert。如果算出的spoke_update小于spoke_insert, 且OSCfg_TickWheel[spoke_update]下的链表的任务没有到期,那//后面的肯定都没有到期,不用继续扫描。

//举例,在时基列表中有三个TCB ,时基列表OSCfg_TickWheel[]的大小OSCfg_TickWheelSize等于12, 当前时基计数器OSTickCtr的值为7,有三个任务分别需要延时TickTemain=16、TickTemain=28和//TickTemain=40个时钟周期, 三个任务的TickRemain加上OSTickCtr可分别得出它们的TickCtrMatch等于23、35和47, 这三个任务的TickCtrMatch对OSCfg_TickWheelSize求余操作后的值spoke都等//于11, 所以这三个任务的TCB会被插入OSCfg_TickWheel[11]下的同一条链表,节点顺序根据TickCtrMatch的值做升序排列。 当下一个SysTick时钟周期到来的时候,会调用OS_TickListUpdate(),这//时OSTickCtr加一操作后等于8, 对OSCfg_TickWheelSize(等于12)求余算得要扫描更新的索引值spoke_update等8,则对OSCfg_TickWheel[8]下面的链表进行扫描, 从时基列表中有三个TCB 可以得//知,8这个索引下没有节点,则直接退出,刚刚插入的三个TCB是在OSCfg_TickWheel[11]下的链表, 根本不用扫描,因为时间只是刚刚过了1个时钟周期而已,远远没有达到他们需要的延时时间。
void  OS_TaskRdy (OS_TCB  *p_tcb)
{
    /* 从时基列表删除 */
    OS_TickListRemove(p_tcb);

    /* 插入就绪列表 */
    OS_RdyListInsert(p_tcb);
}

2、修改OSTimeDly()函数

void  OSTimeDly(OS_TICK dly)
{
    CPU_SR_ALLOC();

    /* 进入临界区 */
    OS_CRITICAL_ENTER();
#if 0
    /* 设置延时时间 */
    OSTCBCurPtr->TaskDelayTicks = dly;

    /* 从就绪列表中移除 */
    //OS_RdyListRemove(OSTCBCurPtr);
    OS_PrioRemove(OSTCBCurPtr->Prio);
#endif

    /* 插入时基列表 */
    OS_TickListInsert(OSTCBCurPtr, dly);

    /* 从就绪列表移除 */
    OS_RdyListRemove(OSTCBCurPtr);

    /* 退出临界区 */
    OS_CRITICAL_EXIT();

    /* 任务调度 */
    OSSched();
}

3、修改OSTimeTick()函数

void  OSTimeTick (void)
{
#if 0
    unsigned int i;
    CPU_SR_ALLOC();

    /* 进入临界区 */
    OS_CRITICAL_ENTER();

    for (i=0; i<OS_CFG_PRIO_MAX; i++)
    {
        if (OSRdyList[i].HeadPtr->TaskDelayTicks > 0)
        {
            OSRdyList[i].HeadPtr->TaskDelayTicks --;
            if (OSRdyList[i].HeadPtr->TaskDelayTicks == 0)
            {
                /* 为0则表示延时时间到,让任务就绪 */
                //OS_RdyListInsert (OSRdyList[i].HeadPtr);
                OS_PrioInsert(i);
            }
        }
    }

    /* 退出临界区 */
    OS_CRITICAL_EXIT();

#endif

    /* 更新时基列表 */
    OS_TickListUpdate();

    /* 任务调度 */
    OSSched();
}

十、实现时间片

本次代码实例要让OS支持 同一个优先级下可以有多个任务的功能,这些任务可以分配不同的时间片,当任务时间片用完的时候,任务会从链表的头部移动到尾部,让下一个任务共享时间片,以此循环。

1、实现时间片

1.1、修改任务TCB

struct os_tcb {                        //为了实现时间片功能,我们需要先在任务控制块TCB中添加两个时间片相关的变量
    CPU_STK         *StkPtr;
    CPU_STK_SIZE    StkSize;

    /* 任务延时周期个数 */
    OS_TICK         TaskDelayTicks;

    /* 任务优先级 */
    OS_PRIO         Prio;

    /* 就绪列表双向链表的下一个指针 */
    OS_TCB          *NextPtr;
    /* 就绪列表双向链表的前一个指针 */
    OS_TCB          *PrevPtr;

    /*时基列表相关字段*/
    OS_TCB          *TickNextPtr;
    OS_TCB          *TickPrevPtr;
    OS_TICK_SPOKE   *TickSpokePtr;

    OS_TICK         TickCtrMatch;
    OS_TICK         TickRemain;

    /* 时间片相关字段 */
    OS_TICK              TimeQuanta;//(1)表示任务需要多少个时间片,单位为系统时钟周期Tick。
    OS_TICK              TimeQuantaCtr;//(2)表示任务还剩下多少个时间片,每到来一个系统时钟周期, TimeQuantaCtr会减一,当TimeQuantaCtr等于零的时候,表示时间片用完, 任务的TCB会//从就绪列表链表的头部移动到尾部,好让下一个任务共享时间片。
};

1.2、实现时间片调度函数


如图所示,在一个就绪链表中, 有三个任务就绪,其中在优先级2下面有两个任务,均分配了两个时间片,其中任务3的时间片已用完,则位于链表的末尾, 任务2的时间片还剩一个,则位于链表的头部。当下一个时钟周期到来的时候,任务2的时间片将耗完, 相应的TimeQuantaCtr会递减为0,任务2的TCB会被移动到链表的末尾,任务3则被成为链表的头部, 然后重置任务3的时间片计数器TimeQuantaCtr的值为2,重新享有时间片。

#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u        //时间片是一个可选的功能, 是否选择由OS_CFG_SCHED_ROUND_ROBIN_EN控制
void OS_SchedRoundRobin(OS_RDY_LIST  *p_rdy_list)
{
    OS_TCB   *p_tcb;
    CPU_SR_ALLOC();

    /*  进入临界段 */
    CPU_CRITICAL_ENTER();

    p_tcb = p_rdy_list->HeadPtr;//获取链表的第一个节点。

    /* 如果TCB节点为空,则退出 */
    if (p_tcb == (OS_TCB *)0) //
    {
        CPU_CRITICAL_EXIT();
        return;
    }

    /* 如果是空闲任务,也退出 */
    if (p_tcb == &OSIdleTaskTCB) {//(4)
        CPU_CRITICAL_EXIT();
        return;
    }

    /* 时间片自减 */
    if (p_tcb->TimeQuantaCtr > (OS_TICK)0) {//(5)如果不是空闲任务,则时间片计数器TimeQuantaCtr减一操作。
        p_tcb->TimeQuantaCtr--;
    }

    /* 时间片没有用完,则退出 */
    if (p_tcb->TimeQuantaCtr > (OS_TICK)0) {//(6)
        CPU_CRITICAL_EXIT();
        return;
    }

    /* 如果当前优先级只有一个任务,则退出 */
    if (p_rdy_list->NbrEntries < (OS_OBJ_QTY)2) {//(7)
        CPU_CRITICAL_EXIT();
        return;
    }

    /* 时间片耗完,将任务放到链表的最后一个节点 */
    OS_RdyListMoveHeadToTail(p_rdy_list);//(8)

    /* 重新获取任务节点 */
    p_tcb = p_rdy_list->HeadPtr;//(9)
    /* 重载默认的时间片计数值 */
    p_tcb->TimeQuantaCtr = p_tcb->TimeQuanta;

    /* 退出临界段 */
    CPU_CRITICAL_EXIT();
}
#endif/* OS_CFG_SCHED_ROUND_ROBIN_EN > 0u */

2、修改OSTimeTick()函数

任务的时间片的单位在每个系统时钟周期到来的时候被更新,时间片调度函数则由时基周期处理函数OSTimeTick()调用, 只需要在更新时基列表之后调用时间片调度函数即可

void  OSTimeTick (void)
{
    /* 更新时基列表 */
    OS_TickListUpdate();

#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
    /* 时间片调度 */
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif

    /* 任务调度 */
    OSSched();
}

3、修改OSTaskCreate()函数

任务的时间片在函数创建的时候被指定

void OSTaskCreate (OS_TCB        *p_tcb,
                OS_TASK_PTR   p_task,
                void          *p_arg,
                OS_PRIO       prio,
                CPU_STK       *p_stk_base,
                CPU_STK_SIZE  stk_size,
                OS_TICK       time_quanta,//(1)
                OS_ERR        *p_err)
{
    CPU_STK       *p_sp;
    CPU_SR_ALLOC();

    /* 初始化TCB为默认值 */
    OS_TaskInitTCB(p_tcb);

    /* 初始化栈 */
    p_sp = OSTaskStkInit( p_task,
                        p_arg,
                        p_stk_base,
                        stk_size );

    p_tcb->Prio = prio;

    p_tcb->StkPtr = p_sp;
    p_tcb->StkSize = stk_size;

    /* 时间片相关初始化 */
    p_tcb->TimeQuanta    = time_quanta;//(2)初始化任务TCB字段的时间片变量TimeQuanta, 该变量表示任务能享有的最大的时间片是多少,该值一旦初始化后就不会变,除非认为修改。
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
    p_tcb->TimeQuantaCtr = time_quanta;//(3)初始化时间片计数器TimeQuantaCtr的值等于TimeQuanta, 每经过一个系统时钟周期,该值会递减,如果该值为0,则表示时间片耗完。
#endif

    /* 进入临界段 */
    OS_CRITICAL_ENTER();

    /* 将任务添加到就绪列表 */
    OS_PrioInsert(p_tcb->Prio);
    OS_RdyListInsertTail(p_tcb);

    /* 退出临界段 */
    OS_CRITICAL_EXIT();

    *p_err = OS_ERR_NONE;
}

4、修改OS_IdleTaskInit()函数

因为在OS_IdleTaskInit()函数中创建了空闲任务,所以该函数也需要修改,只需在空闲任务创建函数中,添加一个时间片的形参就可, 时间片我们分配为0,因为在空闲任务优先级下只有空闲任务一个任务,没有其他的任务

void  OS_IdleTaskInit(OS_ERR  *p_err)
{
    /* 初始化空闲任务计数器 */
    OSIdleTaskCtr = (OS_IDLE_CTR)0;

    /* 创建空闲任务 */
    OSTaskCreate( (OS_TCB     *)&OSIdleTaskTCB,
                (OS_TASK_PTR )OS_IdleTask,
                (void       *)0,
                (OS_PRIO)(OS_CFG_PRIO_MAX - 1u),
                (CPU_STK    *)OSCfg_IdleTaskStkBasePtr,
                (CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
                (OS_TICK       )0,
                (OS_ERR     *)p_err );
}

5、main()函数

int main(void)
{
    OS_ERR err;


    /* CPU初始化:1、初始化时间戳 */
    CPU_Init();

    /* 关闭中断 */
    CPU_IntDis();

    /* 配置SysTick 10ms 中断一次 */
    OS_CPU_SysTickInit (10);

    /* 初始化相关的全局变量 */
    OSInit(&err);

    /* 创建任务 */
    OSTaskCreate( (OS_TCB       *)&Task1TCB,
                (OS_TASK_PTR   )Task1,
                (void         *)0,
                (OS_PRIO       )1,(1)
                (CPU_STK      *)&Task1Stk[0],
                (CPU_STK_SIZE  )TASK1_STK_SIZE,
                (OS_TICK       )0,(1)
                (OS_ERR       *)&err );

    OSTaskCreate( (OS_TCB       *)&Task2TCB,
                (OS_TASK_PTR   )Task2,
                (void         *)0,
                (OS_PRIO       )2,(2)
                (CPU_STK      *)&Task2Stk[0],
                (CPU_STK_SIZE  )TASK2_STK_SIZE,
                (OS_TICK       )1,(2)
                (OS_ERR       *)&err );

    OSTaskCreate( (OS_TCB       *)&Task3TCB,
                (OS_TASK_PTR   )Task3,
                (void         *)0,
                (OS_PRIO       )2,(2)
                (CPU_STK      *)&Task3Stk[0],
                (CPU_STK_SIZE  )TASK3_STK_SIZE,
                (OS_TICK       )1,(2)
                (OS_ERR       *)&err );

    /* 启动OS,将不再返回 */
    OSStart(&err);
}

void Task1( void *p_arg )
{
    for ( ;; ) {
        flag1 = 1;
        OSTimeDly(2);
        flag1 = 0;
        OSTimeDly(2);
    }
}

void Task2( void *p_arg )
{
    for ( ;; ) {
        flag2 = 1;
        //OSTimeDly(1);(3)
        delay(0xff);
        flag2 = 0;
        //OSTimeDly(1);
        delay(0xff);
    }
}

void Task3( void *p_arg )
{
    for ( ;; ) {
        flag3 = 1;
        //OSTimeDly(1);(3)
        delay(0xff);
        flag3 = 0;
        //OSTimeDly(1);
        delay(0xff);
    }
}
//(1):任务1的优先级为1,时间片为0。当同一个优先级下有多个任务的时候才需要时间片功能。
//(2):任务2和任务3的优先级相同,均为2,且分配相同的时间片,时间片也可以不同。
//(3):因为任务2和3的优先级相同,分配了相同的时间片,也可以分配不同的时间片, 并把阻塞延时换成软件延时,不管是阻塞延时还是软件延时,延时的时间都必须小于时间片, 因为相同优先级的任//务在运行的时候最大不能超过时间片的时间。

代码实验现象


在图中可以看到,在任务1的flag1置1和置0的两个时间片内,任务2和3都各运行了一次,运行的时间均为1个时间片,在这1个时间片内任务2和3的flag变量翻转了好多次,即任务运行了好多次。

posted @ 2021-11-30 20:31  cswft  Views(33)  Comments(0Edit  收藏  举报