μC/OS Ⅱ中的空闲任务与统计任务
空闲任务
先上一段转自他人博客的一段关于空闲任务为什么存在的一段描述:
uc/os-II操作系统关于空闲任务是这样描述的:
1、系统任务并且不能被删除;
2、优先级别最低而且永远就绪。
为什么必须要使用这个任务呢?我个人认为可以从两个方面回答:
第一个方面:从CPU本身的运行来看,没有任务就绪并不代表CPU要停止运行,这是总要让CPU做点什么,使CPU能正常的完成任务调度的所需要的一切动作,顺便统计一下CPU的使用率
第二个方面:从UC/OS-II的某些规则来看,空闲任务也是必须的。
1、规定在多任务启动(OSStart)之前用户必须创建一个用户任务(当然也可以创建多个),在只创建一个用户任务的情况下启动多任务,则必须有至少两个任务,此时空闲任务就有了存在的意义
2、更为细节的一个问题在任务就绪表的关于OSUnMapTbl数组的设计上,大家都清楚这个数据结构是为了快速的查找到最高优先级的就绪任务。但是这个数组的第一个元素是0,即OSUnMapTbl[0]=0。回过头来看OSRdyGrp和OSRdyTbl,会出现一个问题:当没有任务就绪的时候,查表出来的结果居然是0,也就是优先级别为0的任务就绪了,岂不是矛盾。但这种情况是不会出现的,因为空闲任务永远存在并且永远就绪!所以正确的说法是OSUnMapTbl[0]到底取值是多少是没有关系的(因为它永远不会被使用),只不过恰好取了0而不是其它的数。从这个方面来讲,空闲任务也是必不可少的
原文出自:http://blog.sina.com.cn/s/blog_5fc67c5d0100cv8r.html
以下是空闲任务的代码:
void OS_TaskIdle (void *pdata)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
pdata = pdata;
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
OSTaskIdleHook();
}
}
可以看出,空闲任务除了在不停的给OSIdleCtr变量进行自加操作外什么都没有做。这么做是为了在统计任务中统计用户任务的CPU的利用率。因为空闲任务的优先级是最低的,所以当系统中没有用户任务执行的时候就会执行空闲任务。这样就可以通过一段额定时间内变量OSIdleCtr的值来计算CPU的利用率。而这里OSIdleCtr是一个32位的变量,所以一些8位或者16位的的处理器要进行一次自加运算就需要多个时钟周期,为了防止在这过程中CPU转去执行优先级高的任务,所以这个操作要在临界区内运行。而OSTaskIdleHook是为了用户拓展空闲任务的功能设计的。比如说如果应用到主频特别高的处理器,有可能造成OSIdleCtr变量的溢出,这是就可以在OSTaskIdleHook加入延时函数消除这种隐患。
统计任务
偷个懒,先上书上的内容以及源代码,然后红字部分是我自己的一些理解。
μC/OS-Ⅱ有一个提供运行时间统计的任务。这个任务叫做OS_TaskStat(),如果用户将系统定义常数OS_TASK_STAT_EN(见文件OS_CFG.H)设为1,这个任务就会建立。一旦得到了允许,OS_TaskStat()每秒钟运行一次(见文件OS_CORE.C),计算当前的CPU利用率。换句话说,OS_TaskStat()告诉用户应用程序使用了多少CPU时间,用百分比表示,这个值放在一个有符号8位整数OSCPUsage中,精读度是1个百分点。
如果用户应用程序打算使用统计任务,用户必须在初始化时建立一个唯一的任务,在这个任务中调用OSStatInit()。换句话说,在调用系统启动函数OSStart()之前,用户初始代码必须先建立一个任务,在这个任务中调用系统统计初始化函数OSStatInit(),然后再建立应用程序中的其它任务。下面是应用统计任务的源代码。
Void main (void)
{
OSInit(); //系统初始化
/*安装任务切换向量*/
/*创建任务初始任务(TaskStart)*/
OSStart(); //开始任务调度
}
Void TaskStart (void *pdata)
{
/*安装并启动时钟节拍*/
OSStatInit(); //初始化统计任务
for (;;) {
}
}
因为用户的应用程序必须先建立一个起始任务[TaskStart()],当主程序main()调用系统启动函数OSStart()的时候,μC/OS-Ⅱ只有3个要管理的任务:TaskStart()、OSTaskIdle()和OS_TaskStat()。请注意,任务TaskStart()的名称是无所谓的,叫什么名字都可以。因为μC/OS-Ⅱ已经将空闲任务的优先级设为最低,即OS_LOWEST_PR10,统计任务的优先级设为次低,OS_LOWEST_PR10减1。启动任务TaskStart()总是优先级最高的任务。
当系统开始从main函数开始运行时,首先要利用OSInit函数对系统进行初始化。然后安装任务切换向量,这个地方我的理解就是这个向量就是一个中断向量,在之前对任务调度的学习过程中出现一个任务切换机制,其中,由于CPU不提供PC的出栈和入栈指令,那么就需要模拟一次中断来完成PC值得存储,而这里的任务切换向量就是这个中断向量。然后利用任务创建函数创建第一个任务TaskStart,为什么要在开始多任务调度前创建这个任务已将在前面介绍了。最后调度OSStart函数。
TaskStart任务中主要要做的就是安装时钟节拍和对统计任务的初始化。下面上统计任务初始化函数以及统计任务部分的源代码。
void OSStatInit (void)
{
#if OS_CRITICAL_METHOD == 3 /* CPU存储分配策略*/
OS_CPU_SR cpu_sr;
#endif
OSTimeDly(2); /*延时以同步时钟节拍*/
OS_ENTER_CRITICAL();
OSIdleCtr = 0L; /*空闲节拍计数器清零*/
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC); /*延时1s,运行空闲以计算每秒空闲节拍的最大数目 */
OS_ENTER_CRITICAL();
OSIdleCtrMax = OSIdleCtr; /*将得到的最大值赋给OSIdleCtrMax以计算利用率*/
OSStatRdy = TRUE;
OS_EXIT_CRITICAL();
}
void OS_TaskStat (void *pdata)
{
#if OS_CRITICAL_METHOD == 3 OS_CPU_SR cpu_sr;
#endif
INT32U run;
INT32U max;
INT8S usage;
pdata = pdata;
while (OSStatRdy == FALSE) {
OSTimeDly(2 * OS_TICKS_PER_SEC); /*等待初始化完成 */
}
max = OSIdleCtrMax / 100L; /*对空闲节拍最大值进行处理,避免出现取整为0*/
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtrRun = OSIdleCtr; /*获得过去1秒的空闲节拍数 */
run = OSIdleCtr;
OSIdleCtr = 0L; /*空闲节拍计数器清零 */
OS_EXIT_CRITICAL();
if (max > 0L) {
usage = (INT8S)(100L - run / max);
if (usage >= 0) {
OSCPUUsage = usage;
} else {
OSCPUUsage = 0;
}
} else {
OSCPUUsage = 0;
max = OSIdleCtrMax / 100L;
}
OSTaskStatHook(); /*用户拓展功能*/
OSTimeDly(OS_TICKS_PER_SEC); /*延时1秒 */
}
}
下面将以上几段代码结合在一起看,我用自己的理解说下这些代码各自的要完成的工作以及协同工作的方式:
在OSStart开始任务调度后,首先执行的是TaskStart,因为空闲任务和统计任务的优先级分别是最低和次低,这时只能先执行TaskStart任务,而在这个任务中调用OSStatInit函数,也就是说OSStatInit的优先级大于统计任务,大于空闲任务。
1. OSStatInit先延时以同步时钟(为什么这么做不是很理解,书上是这么解释的)。
2. 延时后转去运行统计任务TaskStat,TaskStat先判断标识OSStatRdy来确定统计任务初始化是否完成,因为在系统初始化中将这个值设为FALSE,所以这时候一定执行延时。
3. TaskStat也延时,这时系统中的就绪任务就只剩空闲任务TaskIdle了(规定它总是就绪的)。TaskIdle不停执行对空闲节拍计数器OSIdleCtr的自加操作以等待OSStatInit的延时结束。
4. OSStatInit延时结束被执行,将OSIdleCtr清零然后延时1s,这时由于OSStatRdy仍然是FALSE,所以仍然去执行TaskIdle。
5. TaskIdle在这1s的延时内对OSIdleCtr进行自加,以计算时钟节拍数(其实并不一定是真实的时钟节拍数,因为前面说了OSIdleCtr是32位变量,有的处理器对这个变量进行自加一次操作是不能够完成的,这里便于解释和记忆先这么叫,这对最后的计算没有任何影响)。因为这时系统中只有三个任务,而另外两个都在延时,所以这时TaskIdle可以持续运行。最后1s的时间到,OSIdleCtr的值就是1s内最大的空闲节拍数。
6. 转去继续运行OSStatInit,将得到的OSIdleCtr值赋值给OSIdleCtrMax,然后置OSStatRdy为TRUE,这样初始化完成,统计任务就可以继续运行了。
7. 统计任务根据得到的OSIdleCtrMax和上1s中统计的OSIdleCtr值可以算出CPU的利用率:。因为OSIdleCtr总是小于等于OSIdleCtrMax,当小于的时候计算机会把他们的商算成0(因为除法向下取证)所以要换一个方法,先对OSIdleCtrMax除以100,然后将公式变为。这样计算就没有问题了。当计算完成后统计任务延时1s,等待下次计算。
书上说统计任务每1s运行1次,我觉得如果只看代码的话有些问题,当统计任务完成后延时了1s,但是1s后也许有更高优先级的任务正在占用CPU,这时是不能运行统计任务的。我觉得统计任务并不能保证每秒运行1次,而其计算的应该是上次调用后1s内的CPU利用率,而不是此次调用之前1s内的CPU利用率。
感觉有的地方没有说明白,欢迎大家一起讨论 。。。我自己在学习中,求轻喷...