1. 时钟节拍函数怎么建立和启动的
首先时钟节拍任务是整个操作系统UCOSIII的一个核心的任务,是系统的时基,即它是系统的心脏。主要是处理UCOSIII延时、超时的任务,推动整个系统的运行,那么这个这个时钟节拍任务是什么时候建立的
在 OSInit(&err); //初始化UCOSIII 的这个函数里面的 OS_TickTaskInit() 这个函数建立这个任务
void OSInit (OS_ERR *p_err)
{
//省略大量代码
OS_TickTaskInit(p_err); /* Initialize the Tick Task
//省略大量代码
}
void OS_TickTaskInit (OS_ERR *p_err)
{
OSTickCtr = (OS_TICK)0u; /* 清空时钟节拍计数器 */
OSTickTaskTimeMax = (CPU_TS)0u;
OS_TickListInit(); /* 时钟节拍列表初始化 */
//参数检查省略
//时钟节拍任务建立,优先级是 1
OSTaskCreate((OS_TCB *)&OSTickTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Tick Task"),
(OS_TASK_PTR )OS_TickTask,
(void *)0,
(OS_PRIO )OSCfg_TickTaskPrio,
(CPU_STK *)OSCfg_TickTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_TickTaskStkLimit,
(CPU_STK_SIZE)OSCfg_TickTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
}
那么这个时钟节拍函数 OS_TickTask() 有做了哪些事情
看代码
void OS_TickTask (void *p_arg)
{
OS_ERR err;
CPU_TS ts;
p_arg = p_arg; /* Prevent compiler warning */
while (DEF_ON) {
//无限等待内嵌信号量,如果没有任务发送内嵌信号量,该时钟节拍函数一直处于等待态,不是就绪态
(void)OSTaskSemPend((OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)&ts,
(OS_ERR *)&err); /* Wait for signal from tick interrupt */
//获得信号量之后执行的代码
if (err == OS_ERR_NONE) {
if (OSRunning == OS_STATE_OS_RUNNING) { //判断系统是否运行
OS_TickListUpdate(); /* Update all tasks waiting for time 更新所有延时或者超时的任务*/
}
}
}
}
那么到底谁发了信号量让时钟节拍函数让其及时处理所有延时或者超时的任务
这里是通过配置滴答定时器,在滴答定时器的中断服务函数在OSTimeTick() 函数 里面发送一个信号量到中断队列,在UCOSIII的中断服务管理任务发送信号量给时钟节拍任务·,然后时钟节拍任务收到信号量开始对延时和超时的任务是否可以有等待态被转换成就绪态的任务
看代码
//初始化延迟函数配置了滴答定时器
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率=168M ST32F407
void delay_init(u8 SYSCLK)
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //SYSTICK使用外部时钟源为系统时钟的1/8 186/8=21M
fac_us=SYSCLK/8; //不论是否使用OS,fac_us都需要使用
reload=SYSCLK/8; //每秒钟的计数次数 单位为K
reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec=200设定溢出时间
//reload为重装载值=21*1000*5,它是定时了5ms。 因为21*1000是一毫秒此时系统时钟21M,计数器每计21次是1us*1000=1ms了
fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
SysTick->LOAD=reload; //每5ms中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
//systick中断服务函数,使用OS时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
{
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的时钟服务程序
OSIntExit(); //触发任务切换软中断
}
}
上面配置滴答定时器是在封装好的延时函数初始化里面,下面解析一下 OSTimeTick() 函数做了什么
void OSTimeTick (void)
{
OS_ERR err;
//代码省略一部分
ts = OS_TS_GET(); //获取时间戳
OS_IntQPost(
(OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, //任务信号量暂时发送到中断队列,退出中断后由优先级最高的延迟发布任务
(void *)&OSRdyList[OSPrioCur], //就绪发送给时钟节拍任务 OS_TickTask(),OS_TickTask() 接收到该信号量
(void *) 0, //就会继续执行。中断发送延迟可以减少中断时间,将中断级事件转为任务级
(OS_MSG_SIZE) 0u, //,提高了操作系统的实时性。
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err );
//代码省略一部分
}
2. 时钟节拍函数怎么处理任务
1.先介绍时钟节拍任务相关参数
时钟节拍结构体,是用来存储延时或者超时的任务的任务控制块,它向当于一个链表的头结点,通过一个双向链表将任务控制块连接起来,同时统计该链表下面有多少个任务
typedef struct os_tick_spoke OS_TICK_SPOKE;
struct os_tick_spoke {
OS_TCB FirstPtr; / Pointer to list of tasks in tick spoke 时基列表头结点指针 /
OS_OBJ_QTY NbrEntries; / Current number of entries in the tick spoke 统计有多少个节点=任务数量 /
OS_OBJ_QTY NbrEntriesMax; / Peak number of entries in the tick spoke */
};
这种结构体UCOSIII一共定义了17个,
#define OS_CFG_TICK_WHEEL_SIZE 17u
OS_TICK_SPOKE OSCfg_TickWheel [OS_CFG_TICK_WHEEL_SIZE];
这17个结构体组成了一个时基轮,每一个结构体又可以叫做时钟节拍列表
**那么肯定疑惑任务控制块与任务控制块根据那些参数建立连接
看看任务控制块
struct os_tcb {
OS_TCB *TickNextPtr; //TickNextPtr和TickPrevPtr可把正在延时或指定时间内等待某个事件的任务的OS_TCB构成双向链表
OS_TCB *TickPrevPtr;
OS_TICK_SPOKE *TickSpokePtr; /*通过该指针可知道该任务在哪个时钟节拍上 /
/ DELAY / TIMEOUT */
OS_TICK TickCtrPrev; /*存储OSTickCtr之前的数值 Previous time when task was ready */
OS_TICK TickCtrMatch; /*记录相对时钟节拍数,当TickCtrMatch和OSTickCtr一样时,任务等待延时结束 */
OS_TICK TickRemain; /*任务还要等待延时的节拍数 Number of ticks remaining for a match (updated at … /
/ … run-time by OS_StatTask() *
};
OS_TCB TickNextPtr; //TickNextPtr和TickPrevPtr可把正在延时或指定时间内等待某个事件的任务的OS_TCB构成双向链表
OS_TCB TickPrevPtr;
这两个参数记录链表的前驱节点和后继节点,从而连接出了整个链表,即头结点插入了第一个任务控制块之后,接下来利用任务控制块的这一个时基后继节点TickNextPtr 插入下一个任务控制块,而时基后继节点TickPrevPtr 有可以连接到前一个任务控制块,而时钟节拍列表作为头结点
连接完之后看图片,截的思维导图
插入的顺序是根据任务控制块这两个参数和一个全局变量
OS_TICK TickCtrMatch; /*记录相对时钟节拍数=OSTickCtr+延时节拍数,当TickCtrMatch和OSTickCtr一样时,任务等待延时结束 */
OS_TICK TickRemain; /*任务还要等待延时的节拍数 */
OSTickCtr 全局变量记录当前运行多少个时钟节拍数,每次运行一个节拍数都加1
根据 TickRemain 这个变量与 时钟节拍列表 挂载的任务控制块的TickRemain做比较,比它大就一直往后面找,直到找到一个比它小的就往它的前面插入,如果没有就插到最后,所以整个链表是升序的
那么怎么插入和删除节点的看代码,
//节点插入函数
void OS_TickListInsert (OS_TCB *p_tcb,
OS_TICK time,
OS_OPT opt,
OS_ERR *p_err)
{
OS_TICK tick_delta;
OS_TICK tick_next;
OS_TICK_SPOKE *p_spoke;
OS_TCB *p_tcb0;
OS_TCB *p_tcb1;
OS_TICK_SPOKE_IX spoke;
if (opt == OS_OPT_TIME_MATCH) { /*绝对时间 Task time is absolute. */
tick_delta = time - OSTickCtr - 1u;
if (tick_delta > OS_TICK_TH_RDY) { /*要延时的节拍数超过了上限 被定义成恶性延时 If delay already occurred, ... */
p_tcb->TickCtrMatch = (OS_TICK )0u;
p_tcb->TickRemain = (OS_TICK )0u;
p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0;
*p_err = OS_ERR_TIME_ZERO_DLY; /* ... do NOT delay. 延时时间变成了0延时 */
return;
}
p_tcb->TickCtrMatch = time;
p_tcb->TickRemain = tick_delta + 1u;
} else if (time > (OS_TICK)0u) {
if (opt == OS_OPT_TIME_PERIODIC) { /*周期性延时 Task time is periodic. */
tick_next = p_tcb->TickCtrPrev + time;
tick_delta = tick_next - OSTickCtr - 1u;
if (tick_delta < time) { /* If next periodic delay did NOT already occur, ... */
p_tcb->TickCtrMatch = tick_next; /* ... set next periodic delay; ... */
} else {
p_tcb->TickCtrMatch = OSTickCtr + time; /* ... else reset periodic delay. */
}
p_tcb->TickRemain = p_tcb->TickCtrMatch - OSTickCtr;
p_tcb->TickCtrPrev = p_tcb->TickCtrMatch;
} else { /* 反之就是 相对延时 最常用的选项 Task time is relative to current. */
p_tcb->TickCtrMatch = OSTickCtr + time; //任务等待的匹配点为 OSTickCtr + time;
p_tcb->TickRemain = time; //任务要等待的节拍数
}
} else { /* Zero time delay; ... */
p_tcb->TickCtrMatch = (OS_TICK )0u;
p_tcb->TickRemain = (OS_TICK )0u;
p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0;
*p_err = OS_ERR_TIME_ZERO_DLY; /* ... do NOT delay. */
return;
}
spoke = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize); //计算出任务控制块应该放到时钟节拍列表的那个位置 就是0--OSCfg_TickWheelSize=17的其中一个位置通过取余的方式
p_spoke = &OSCfg_TickWheel[spoke];
//注意:这里插入双向链表采用升序的方式插入节点,因为时钟节拍数越小说明等待时间越短,应该放在链表前面,越往后面延时时间越长
if (p_spoke->NbrEntries == (OS_OBJ_QTY)0u) { /*等于0说明该位置上时钟节拍列表没有任务则该任务是插入第一个位置 First entry in the spoke */
p_tcb->TickNextPtr = (OS_TCB *)0;
p_tcb->TickPrevPtr = (OS_TCB *)0;
p_spoke->FirstPtr = p_tcb;
p_spoke->NbrEntries = (OS_OBJ_QTY)1u; //在空链表插入一个节点现在节点数量为一个即任务的数量为1
} else { //反之就是该位置下有任务不是空的链表
p_tcb1 = p_spoke->FirstPtr; /*获得链表第一节点的首地址 就是第一个任务控制块 Point to current first TCB in the list */
while (p_tcb1 != (OS_TCB *)0) { //该时钟节拍列表的这个位置不是空链表有任务控制块
p_tcb1->TickRemain = p_tcb1->TickCtrMatch /*计算任务到期还有多少节拍数 Compute time remaining of current TCB in list */
- OSTickCtr;
if (p_tcb->TickRemain > p_tcb1->TickRemain) { /* 如果当前任务控制块所需要的延时时间比当前任务控制块需要的还多那么该节点要往后面插入Do we need to insert AFTER current TCB in list? */
if (p_tcb1->TickNextPtr != (OS_TCB *)0) { /*判断下一个节点存不存在 Yes, are we pointing at the last TCB in the list? */
p_tcb1 = p_tcb1->TickNextPtr; /*p_tcb1 No,下一个存在,就指向下一个节点因为要和下一个节点的时间做对比,从这里啊可以看出该双向链表是升序的 Point to next TCB in the list */
} else { //不存在,就是下一个节点是空的直接插到其后面,同时也说明已经到尾结点了
p_tcb->TickNextPtr = (OS_TCB *)0; //这里是双向链表的尾结点插入操作
p_tcb->TickPrevPtr = p_tcb1;
p_tcb1->TickNextPtr = p_tcb; /* Yes, TCB to add is now new last entry in the list */
p_tcb1 = (OS_TCB *)0; /*退出循环 Break loop */
}
} else { /*反之说明延时时间少,应该插入当前结点的前面就是该任务控制块的前面 Insert before the current TCB */
if (p_tcb1->TickPrevPtr == (OS_TCB *)0) { /*这里是0说明在任务控制块后面没有任务控制块,那就说明这是第一个节点(任务控制块)那个该任务控制块要做头结点(做第一个任务控制块) Are we inserting before the first TCB? */
p_tcb->TickPrevPtr = (OS_TCB *)0; //插入一个新的头结点
p_tcb->TickNextPtr = p_tcb1;
p_tcb1->TickPrevPtr = p_tcb;
p_spoke->FirstPtr = p_tcb;
} else { /*后面还有节点,说明不是第一个节点, p_tcb0 暂存该任务的前一个任务 就是在两者之间插入一个节点 Insert in between 2 TCBs already in the list */
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;
}
p_tcb1 = (OS_TCB *)0; /*退出循环 Break loop */
}
}
p_spoke->NbrEntries++; //节拍列表中的元素数目加1==该位置又多了一个节点就是多了一个任务
}
if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) { /*更新节拍列表的元素数目的最大记录 Keep track of maximum # of entries in each spoke */
p_spoke->NbrEntriesMax = p_spoke->NbrEntries;
}
p_tcb->TickSpokePtr = p_spoke; /*记录当前任务存放于哪个节拍列表==删除时钟节拍列表上面的任务用得上 Link back to tick spoke */
*p_err = OS_ERR_NONE;
}
void OS_TickListRemove (OS_TCB *p_tcb) //节点删除函数
{
OS_TICK_SPOKE *p_spoke;
OS_TCB *p_tcb1;
OS_TCB *p_tcb2;
p_spoke = p_tcb->TickSpokePtr; //获取时钟节拍的首地址因为要移出的任务都是时钟节拍列表的第一个任务
if (p_spoke != (OS_TICK_SPOKE *)0) { /*该 Confirm that task is in tick list */
p_tcb->TickRemain = (OS_TICK)0u; //任务延时时间清零
if (p_spoke->FirstPtr == p_tcb) { /*时钟节拍列表的第一个任务控制块等于当前任务控制块 说明当前任务控制块一定是第一个(太严谨了) Is timer to remove at the beginning of list? */
p_tcb1 = (OS_TCB *)p_tcb->TickNextPtr; /* Yes 任务控制块删除操作(就是双链表删除第一个节点) */
p_spoke->FirstPtr = p_tcb1;
if (p_tcb1 != (OS_TCB *)0) {
p_tcb1->TickPrevPtr = (OS_TCB *)0; //新的任务控制块作为第一个任务它的前面没有节点所以他的前驱结点是空的
}
} else { //不,从列表的某个地方删除时钟节拍-->这里是不知道任务控制块在列表的位置
p_tcb1 = p_tcb->TickPrevPtr; //找前驱节点 No, remove timer from somewhere in the list */
p_tcb2 = p_tcb->TickNextPtr; //找后继节点
p_tcb1->TickNextPtr = p_tcb2; //前驱指向后继节点实现删除
if (p_tcb2 != (OS_TCB *)0) { //后继节点非空
p_tcb2->TickPrevPtr = p_tcb1; //后继节点的前驱节点是p_tcb1
}
}
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;
p_spoke->NbrEntries--;
}
}
2.时钟节拍函数怎么处理任务
接下来开始解析一下时钟节拍函数是怎么处理延时或者超时的任务的,上面已经有所提及OS_TickTask() 函数里面有一个OS_TickListUpdate(); 处理这些延时任务,主要是计算任务延时节拍数是否到期判断依据 if (OSTickCtr == p_tcb->TickCtrMatch)
p_tcb->TickCtrMatch 记录相对时间=OSTickCtr(当前运行的节拍数)+ 延时节拍数 而 OSTickCtr 是每运行一个节拍数就自增1,
一定延时到期就将任务从等待态变成就绪态。
看代码
void OS_TickListUpdate (void)
{
CPU_BOOLEAN done;
OS_TICK_SPOKE *p_spoke;
OS_TCB *p_tcb;
OS_TCB *p_tcb_next;
OS_TICK_SPOKE_IX spoke;
CPU_TS ts_start;
CPU_TS ts_end;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
ts_start = OS_TS_GET(); //获取时间戳
OSTickCtr++; /*时钟节拍数加一 Keep track of the number of ticks */
spoke = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize); //通过取余运算在时钟节拍列表里面找出延时最快到期的任务
p_spoke = &OSCfg_TickWheel[spoke]; //
p_tcb = p_spoke->FirstPtr; //p_tcb指向时钟节拍列表对应位置里面的第一个任务的首地址
done = DEF_FALSE;
while (done == DEF_FALSE) { //while循环
if (p_tcb != (OS_TCB *)0) { //任务控制块非空如果是空的说明该节拍列表里面没有任务直接退出循环
p_tcb_next = p_tcb->TickNextPtr; /*指向下一个任务控制块 Point to next TCB to update */
switch (p_tcb->TaskState) { //从任务控制块里面取出任务运行状态
case OS_TASK_STATE_RDY: //任务就绪状态
case OS_TASK_STATE_PEND: //任务等待状态
case OS_TASK_STATE_SUSPENDED: //任务被挂起
case OS_TASK_STATE_PEND_SUSPENDED:
break; //这些任务状态不用理会
//这里处理延时的任务
case OS_TASK_STATE_DLY: //任务处于延时状态
p_tcb->TickRemain = p_tcb->TickCtrMatch /*计算任务延时还剩几个时钟节拍数 Compute time remaining of current TCB */
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { /*判断任务延时到期 p_tcb->TickCtrMatch一直没有变过它是用来计算任务需要多少个节拍数从它延时开始时的OSTickCtr加上自身需要延时的节拍数而OSTickCtr是不断增加的 Process each TCB that expires */
p_tcb->TaskState = OS_TASK_STATE_RDY; //任务状态变成就绪态
OS_TaskRdy(p_tcb); /*优先级位映射表置一同时将任务控制块插入到任务就绪列表 Make task ready to run */
} else {
done = DEF_TRUE; /*否则就退出循环,因为时钟节拍表里面的任务是根据延时时间的升序(即时间越来越长)来插入,如果第一个任务都没有到期那么剩下的任务都不会到期就不用历遍链表直接退出循环 Don't find a match, we're done! */
}
break;
//信号量或者消息队列里面如果选择有限等待会将其任务控制块插入到时基轮,然后在这里处理
case OS_TASK_STATE_PEND_TIMEOUT: //如果是有期限等待状态
p_tcb->TickRemain = p_tcb->TickCtrMatch //计算期限的的剩余时间
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { //如果任务期满
#if (OS_MSG_EN > 0u) //如果使能了消息队列(普通消息队列或任务消息队列)
p_tcb->MsgPtr = (void *)0; //把任务保存接收到消息的地址的成员清空
p_tcb->MsgSize = (OS_MSG_SIZE)0u; //把任务保存接收到消息的长度的成员清零
#endif
p_tcb->TS = OS_TS_GET(); //记录任务结束等待的时间戳
OS_PendListRemove(p_tcb); //从等待列表移除该任务
OS_TaskRdy(p_tcb); //让任务就绪
p_tcb->TaskState = OS_TASK_STATE_RDY; //修改任务状态量为就绪状态
p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT; //记录等待状态为超时
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //记录等待内核对象变量为空
} else { //如果任务未期满(由于升序排列,该列表后面的任务肯定也未期满)
done = DEF_TRUE; //不再遍历该列表,退出 while 循环
}
break;
case OS_TASK_STATE_DLY_SUSPENDED: //在延时中任务被挂起
p_tcb->TickRemain = p_tcb->TickCtrMatch /* 计算离任务延时到期还有几个时钟节拍Compute time remaining of current TCB */
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { /*判断任务延时到期了 Process each TCB that expires */
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
OS_TickListRemove(p_tcb); /*从时钟节拍列表取出该任务 Remove from current wheel spoke */
} else {
done = DEF_TRUE; /*任务没有到期退出循环 Don't find a match, we're done! */
}
break;
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: //如果是有期限等待中被挂起状态
p_tcb->TickRemain = p_tcb->TickCtrMatch //计算期限的的剩余时间
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { //如果任务期满
#if (OS_MSG_EN > 0u) //如果使能了消息队列(普通消息队列或任务消息队列)
p_tcb->MsgPtr = (void *)0; //把任务保存接收到消息的地址的成员清空
p_tcb->MsgSize = (OS_MSG_SIZE)0u; //把任务保存接收到消息的长度的成员清零
#endif
p_tcb->TS = OS_TS_GET(); //记录任务结束等待的时间戳
OS_PendListRemove(p_tcb); //从等待列表移除该任务
OS_TickListRemove(p_tcb); //从节拍列表移除该任务
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //修改任务状态量为被挂起状态
p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT; //记录等待状态为超时
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; //记录等待内核对象变量为空
} else { //如果任务未期满(由于升序排列,该列表后面的任务肯定也未期满)
done = DEF_TRUE; //不再遍历该列表,退出 while 循环
}
break;
default:
break;
}
p_tcb = p_tcb_next; //任务控制块指向下一个任务
} else {
done = DEF_TRUE;
}
}
ts_end = OS_TS_GET() - ts_start; /* Measure execution time of tick task */
if (OSTickTaskTimeMax < ts_end) {
OSTickTaskTimeMax = ts_end;
}
OS_CRITICAL_EXIT();
}
这里最后在总结一下
/任务如何从等待态转成就绪态—>时钟节拍任务这是一个途径 系统滴答定时器定时中断5ms的中断到了就进行它的中断服务函数SysTick_Handler()----->
//在函数 OSTimeTick () 会发送信号量给时钟节拍任务OS_TickTask() ,任务 OS_TickTask() 接收到信号量后就会进入就绪状态,准备运行
//在任务 OS_TickTask() 里面这个函数OS_TickListUpdate()将超时了的任务或者是延时将要到期的任务找到将他们变成就绪态就是在任务就绪表OSPrioTb1[]这个数组里面对应的优先级置一,
//同时在就绪任务列表OSRdyList[]里面插入该任务,最后在时钟节拍列表OSCfg_TickWheel[]里面将该任务去掉