系统启动与时钟
本博客所有思路皆出自于:
【创客海】【操作系统原理及嵌入式UCOS-II教学视频合集】【一周搞定系列】
和µC/OS-II教程
uCOS-ii系统的启动过程分析
在嵌入式系统中,启动分为硬件启动与软件启动。 比如说ARM内核很多就是。
硬启动主要是指硬件系统(包括各个功能部件)的初始化和启动;
软启动是指操作系统内核的初始化与多任务的用户程序启动,比如说init。
uCOS内核的初始化:
主要运行OSInit初始化uCOS系统,然后才可以创建一个用户任务。
接着运行OSStrart();让操作系统的TCB来管理CPU。
系统初始化函数:OSInit()
将数据结构全都初始化,比如任务就绪组 任务就绪表 缓冲池都为0. 控制块也为0.
还有一些全局变量全部初始化,比如OSrunning设置为FALSE,不让他跑。
还有使所有的FreeList生成的链表全部指向0
系统中的代码:
/*$PAGE*/
/*
*********************************************************************************************************
* INITIALIZATION
*
* 描述:该函数用于初始化uC/OS-II的内部组件,必须在此之前调用
* 创建任何uC / OS-II对象,之前调用 OSStart().
*
* Arguments : none
*
* Returns : none
*********************************************************************************************************
*/
void OSInit (void)
{
#if OS_VERSION >= 204
OSInitHookBegin(); /* Call port specific initialization code */
#endif
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0) && (OS_MAX_QS > 0)
OS_QInit(); /* Initialize the message queue structures */
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
#if OS_VERSION >= 204
OSInitHookEnd(); /* Call port specific init. code */
#endif
#if OS_VERSION >= 270 && OS_DEBUG_EN > 0
OSDebugInit();
#endif
}
/*$PAGE*/
启动函数:OSStrat()
系统中的代码:
void OSStart (void)
{
INT8U y;
INT8U x;
if (OSRunning == FALSE) {
y = OSUnMapTbl[OSRdyGrp]; /* Find highest priority's task priority number */
x = OSUnMapTbl[OSRdyTbl[y]];
OSPrioHighRdy = (INT8U)((y << 3) + x);
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
当调用OSStart()时,OSStart()从任务就绪表中找出那个用户建立的优先级最高任务的任务控制块。
然后,OSStart()调用高优先级就绪任务启动函数OSStartHighRdy(),(见汇编语言文件OS_CPU_A.ASM),这个文件与选择的微处理器有关。
PS:OSStartHighRdy是用汇编写的函数,其功能是:
Note : OSStartHighRdy() MUST:
; a) Call OSTaskSwHook() then,
; b) Set OSRunning to TRUE, 设置运行为真
; c) Switch to the highest priority task. 选择最高优先级任务
实质上,函数OSStartHighRdy()是将任务栈中保存的值弹回到CPU寄存器中,然后执行一条中断返回指令,中断返回指令强制执行该任务代码。
注意,OSStartHighRdy()将永远不返回到OSStart()。
uCOS-ii的时钟
时钟在一个操作系统内核中占据着重要的地位,它是驱动一个OS内核运行的“起搏器”,相当于uCOS的心脏,时钟从硬件上来的(晶振)。
时钟的作用:
记录时间标记:timestamp
怎么用?
应该是在OSStart之后由任务去调用。
ps:ST_Enable是时钟使能函数(我自己个人的)
时钟的实现函数 OSTickISR()
大部分用汇编实现,可以直接操作芯片内部的寄存器,运行效率高。
里面调用的Otime()函数:
1.给任务计数器OSTime加1
2:遍历任务控制块TCB链表中的所有任务控制块(从OSTCBList开始,沿着OS_TCB链表查找,一直找到空闲任务),把各个任务控制块的时间延时OSTCBDly()变量减1.当某任务的任务控制块中的时间延时项OSTCBDly减到了零,这个任务就进入了就绪态。
用户API接口:
详看资料 第6章 任务之间的通讯与同步
时间管理实例及相关源码分析
实例是机密,恕我不能给你看
自己去买书看
源码解析:
任务延时函数,OSTimeDly()
本章所提到的函数可以在OS_TIME.C文件中找到。
µC/OS-Ⅱ提供了这样一个系统服务:申请该服务的任务可以延时一段时间,这段时间的长短是用时钟节拍的数目来确定的。
实现这个系统服务的函数叫做OSTimeDly()。
调用该函数会使µC/OS-Ⅱ进行一次任务调度,并且执行下一个优先级最高的就绪态任务。
任务调用OSTimeDly()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时,它就会马上进入就绪状态。
注意,只有当该任务在所有就绪任务中具有最高的优先级时,它才会立即运行。
如下,用户的应用程序是通过提供延时的时钟节拍数——一个1 到65535之间的数,来调用该函数的。
如果用户指定0值[(1)],则表明用户不想延时任务,函数会立即返回到调用者。
非0值会使得任务延时函数OSTimeDly()将当前任务从就绪表中移除[(2)]。
接着,这个延时节拍数会被保存在当前任务的OS_TCB中[(3)],并且通过OSTimeTick()每隔一个时钟节拍就减少一个延时节拍数。
最后,既然任务已经不再处于就绪状态,任务调度程序会执行下一个优先级最高的就绪任务。
void OSTimeDly (INT16U ticks)
{
if (ticks > 0) {//(1)
OS_ENTER_CRITICAL(); //关中断
if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {//(2) 将当前任务从就绪表中移除
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks;//(3) 保存延时节拍数ticks到OSTCBCur->OSTCBDly中
OS_EXIT_CRITICAL(); //开中断
OSSched();//(4)
}
}
**清楚地认识0到一个节拍之间的延时过程是非常重要的。**换句话说,如果用户只想延时一个时钟节拍,而实际上是在0到一个节拍之间结束延时。
即使用户的处理器的负荷不是很重,这种情况依然是存在的。
图F5.1详细说明了整个过程。系统每隔10ms发生一次时钟节拍中断[F5.1(1)]。假如用户没有执行其它的中断并且此时的中断是开着的,时钟节拍中断服务就会发生[F5.1(2)]。
也许用户有好几个高优先级的任务(HPT)在等待延时期满,它们会接着执行[F5.1(3)]。
接下来,图5.1中所示的低优先级任务(LPT)会得到执行的机会,该任务在执行完后马上调用[F5.1(4)]所示的OSTimeDly(1)。
µC/OS-Ⅱ会使该任务处于休眠状态直至下一个节拍的到来。当下一个节拍到来后,时钟节拍中断服务子程序会执行[F5.1(5)],但是这一次由于没有高优先级的任务被执行,µC/OS-Ⅱ会立即执行申请延时一个时钟节拍的任务[F5.1(6)]。
正如用户所看到的,该任务实际的延时少于一个节拍!在负荷很重的系统中,任务甚至有可能会在时钟中断即将发生时调用OSTimeDly(1),在这种情况下,任务几乎就没有得到任何延时,因为任务马上又被重新调度了。
如果用户的应用程序至少得延时一个节拍,必须要调用OSTimeDly(2),指定延时两个节拍!
OSTimeDly()虽然是一个非常有用的函数,但用户的应用程序需要知道延时时间对应的时钟节拍的数目。用户可以使用定义全局常数OS_TICKS_PER_SEC(参看OS_CFG.H)的方法将时间转换成时钟段,但这种方法有时显得比较愚笨。
按时分秒延时函数 OSTimeDlyHMSM()
ucos的作者增加了OSTimeDlyHMSM()函数后,用户就可以按小时(H)、分(M)、秒(S)和毫秒(m)来定义时间了,这样会显得更自然些。
与OSTimeDly()一样,调用OSTimeDlyHMSM()函数也会使µC/OS-Ⅱ进行一次任务调度,并且执行下一个优先级最高的就绪态任务。
任务调用OSTimeDlyHMSM()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时(参看5.02,恢复延时的任务OSTimeDlyResume()),它就会马上处于就绪态。
同样,只有当该任务在所有就绪态任务中具有最高的优先级时,它才会立即运行。
INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli)
{
INT32U ticks;
INT16U loops;
if (hours > 0 || minutes > 0 || seconds > 0 || milli > 0) { (1)
if (minutes > 59) {
return (OS_TIME_INVALID_MINUTES);
}
if (seconds > 59) {
return (OS_TIME_INVALID_SECONDS);
}
If (milli > 999) {
return (OS_TIME_INVALID_MILLI);
}
ticks = (INT32U)hours * 3600L * OS_TICKS_PER_SEC (2)
+ (INT32U)minutes * 60L * OS_TICKS_PER_SEC
+ (INT32U)seconds * OS_TICKS_PER_SEC
+ OS_TICKS_PER_SEC * ((INT32U)milli
+ 500L/OS_TICKS_PER_SEC) / 1000L; (3)
loops = ticks / 65536L; (4)
ticks = ticks % 65536L; (5)
OSTimeDly(ticks); (6)
while (loops > 0) { (7)
OSTimeDly(32768); (8)
OSTimeDly(32768);
loops--;
}
return (OS_NO_ERR);
} else {
return (OS_TIME_ZERO_DLY); (9)
}
}
从中可以看出,应用程序是通过用小时、分、秒和毫秒指定延时来调用该函数的。
在实际应用中,用户应避免使任务延时过长的时间,因为从任务中获得一些反馈行为(如减少计数器,清除LED等等)经常是很不错的事。但是,如果用户确实需要延时长时间的话,µC/OS-Ⅱ可以将任务延时长达256个小时(接近11天)。
OSTimeDlyHMSM()一开始先要检验用户是否为参数定义了有效的值[L5.2(1)]。与OSTimeDly()一样,即使用户没有定义延时,OSTimeDlyHMSM()也是存在的[L5.2(9)]。因为µC/OS-Ⅱ只知道节拍,所以节拍总数是从指定的时间中计算出来的[L5.2(3)]。很明显,程序清单 L5.2中的程序并不是十分有效的。笔者只是用这种方法告诉大家一个公式,这样用户就可以知道怎样计算总的节拍数了。真正有意义的只是OS_TICKS_PER_SEC。[L5.2(3)]决定了最接近需要延迟的时间的时钟节拍总数。500/OS_TICKS_PER_SECOND的值基本上与0.5个节拍对应的毫秒数相同。例如,若将时钟频率(OS_TICKS_PER_SEC)设置成100Hz(10ms),4ms的延时不会产生任何延时!而5ms的延时就等于延时10ms。
µC/OS-Ⅱ支持的延时最长为65,535个节拍。要想支持更长时间的延时,如L5.2(2)所示,OSTimeDlyHMSM()确定了用户想延时多少次超过65,535个节拍的数目[L5.2(4)]和剩下的节拍数[L5.2(5)]。例如,若OS_TICKS_PER_SEC的值为100,用户想延时15分钟,则OSTimeDlyHMSM()会延时15x60x100=90,000个时钟。这个延时会被分割成两次32,768个节拍的延时(因为用户只能延时65,535个节拍而不是65536个节拍)和一次24,464个节拍的延时。在这种情况下,OSTimeDlyHMSM()首先考虑剩下的节拍,然后是超过65,535的节拍数L5.2(7)和(8)。
由于OSTimeDlyHMSM()的具体实现方法,用户不能结束延时调用OSTimeDlyHMSM()要求延时超过65535个节拍的任务。换句话说,如果时钟节拍的频率是100Hz,用户不能让调用OSTimeDlyHMSM(0,10,55,350)或更长延迟时间的任务结束延时。
让处在延时期的任务结束延时,OSTimeDlyResume()
µC/OS-Ⅱ允许用户结束延时正处于延时期的任务。
延时的任务可以不等待延时期满,而是通过其它任务取消延时来使自己处于就绪态。这可以通过调用 OSTimeDlyResume() 和指定要恢复的任务的优先级来完成。
实际上,OSTimeDlyResume()也可以唤醒正在等待事件(参看第六章——任务间的通讯和同步)的任务,虽然这一点并没有提到过。
在这种情况下,等待事件发生的任务会考虑是否终止等待事件。
OSTimeDlyResume()的代码如程序清单 L5.3所示,它首先要确保指定的任务优先级有效 [L5.3(1)]。
接着,OSTimeDlyResume()要确认要结束延时的任务是确实存在的[L5.3(2)]。
如果任务存在,OSTimeDlyResume()会检验任务是否在等待延时期满[L5.3(3)]。
只要OS_TCB域中的OSTCBDly包含非0值就表明任务正在等待延时期满,因为任务调用了OSTimeDly(),OSTimeDlyHMSM()或其它在第六章中所描述的PEND函数。
然后延时就可以通过强制命令OSTCBDly为0来取消[L5.3(4)]。
延时的任务有可能已被挂起了,这样的话,任务只有在没有被挂起的情况下才能处于就绪状态[L5.3(5)]。
当上面的条件都满足后,任务就会被放在就绪表中[L5.3(6)]。
这时,OSTimeDlyResume()会调用任务调度程序来看被恢复的任务是否拥有比当前任务更高的优先级[L5.3(7)]。
这会导致任务的切换。
恢复正在延时的任务:
INT8U OSTimeDlyResume (INT8U prio)
{
OS_TCB *ptcb;
if (prio >= OS_LOWEST_PRIO) { (1)
return (OS_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
ptcb = (OS_TCB *)OSTCBPrioTbl[prio];
if (ptcb != (OS_TCB *)0) { (2)
if (ptcb->OSTCBDly != 0) { (3)
ptcb->OSTCBDly = 0; (4)
if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) { (5)
OSRdyGrp |= ptcb->OSTCBBitY; (6)
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
OSSched(); (7)
} else {
OS_EXIT_CRITICAL();
}
return (OS_NO_ERR);
} else {
OS_EXIT_CRITICAL();
return (OS_TIME_NOT_DLY);
}
} else {
OS_EXIT_CRITICAL();
return (OS_TASK_NOT_EXIST);
}
}
注意,用户的任务有可能是通过暂时等待信号量、邮箱或消息队列来延时自己的(参看第六章)。可以简单地通过控制信号量、邮箱或消息队列来恢复这样的任务。这种情况存在的唯一问题是它要求用户分配事件控制块(参看6.00),因此用户的应用程序会多占用一些RAM。
系统时间 OSTimeGet()和OSTimeSet()
无论时钟节拍何时发生,µC/OS-Ⅱ都会将一个32位的计数器加1。这个计数器在用户调用OSStart()初始化多任务和4,294,967,295个节拍执行完一遍的时候从0开始计数。在时钟节拍的频率等于100Hz的时候,这个32位的计数器每隔497天就重新开始计数。用户可以通过调用OSTimeGet()来获得该计数器的当前值。也可以通过调用OSTimeSet()来改变该计数器的值。OSTimeGet()和OSTimeSet()两个函数的代码如程序清单 L5.4所示。注意,在访问OSTime的时候中断是关掉的。这是因为在大多数8位处理器上增加和拷贝一个32位的数都需要数条指令,这些指令一般都需要一次执行完毕,而不能被中断等因素打断。
时钟一出错 全都得玩完
INT32U OSTimeGet (void)
{
INT32U ticks;
OS_ENTER_CRITICAL();
ticks = OSTime;
OS_EXIT_CRITICAL();
return (ticks);
}
void OSTimeSet (INT32U ticks)
{
OS_ENTER_CRITICAL();
OSTime = ticks;
OS_EXIT_CRITICAL();
}