时间管理的内容在代码os_time.c中,包含操作系统时间的设置及获取,对任务的延时,任务按分秒延时,取消任务的延时共5个系统调用。时间管理的最主要功能就是对任务进行延时。
时间管理中最重要的数据结构就是全局变量OSTime,OSTime的值就是操作系统的时间,它的定义在uC/OS-II的头文件ucos_ii.h中,代码如下所示:
这里首先要知道关键字volatile的含义。volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并、常量传播等优化。而关键字volatile却是禁止做这些优化的。因为OSTime的值是易变的,加了关键字volatile后,不会被编译器优化,每次取值都会直接在内存中对该变量的地址取值,从而保证不会因为编译器优化而产生错误的结果。
OSTime在操作系统初始化时被设置为0。
时间管理中使用的另一个重要的数据结构就是任务控制块,任务控制块有一项是OSTCBDly,标志这个任务延时的时间。这个时间是以两次时钟中断间隔的时间为单位的。另外,对任务的延时实际上阻塞了任务,因此要对就绪表和就绪组等数据结构进行相关的操作。
时间的设置和获取都是关于OSTime的赋值,代码比较简单,如下所示:
时间获取函数OSTimeGet简单地返回OSTime的值,需要注意的是,对OSTime的操作一定要使用临界区。时间设置函数将参数ticks的值赋值给OSTime,这两个函数并不常用。
任务延时函数OSTimeDly用于阻塞任务一定时间,这个时间以参数的形式给出。如果这个参数的值是N,那么在N个时间片(时钟滴答)之后,任务才能回到就绪态获得继续运行的机会。如果参数的值是0,就不会阻塞任务。任务延时函数OSTimeDly的代码如下所示:
本段代码层次清晰且比较简单。OSLockNesting是调度锁,也就是说,如果OSLockNesting>0,那么不允许进行任务调度。因为任务延时的时候要中止当前任务的执行,所以要进行调度,因此在调度锁有效的情况下是不能执行任务延时的。如果延时时间大于0,那么就要进行一次任务调度,将当前的任务的就绪标志取消,也就是对就绪表和就绪组的相关操作。之后延时时间赋值给任务块的OSTCBDly项以对延时计数。操作系统在每个时钟中断都要对每个OSTCBDly大于0的任务的OSTCBDly进行减1操作和进行任务调度,那么当任务的延时时间到了的时候(OSTCBDly为0)就可以恢复到就绪态。
需要注意的是,如果将任务延时1个时间片,调用OSTimeDly(1),会不会产生正确的结果呢?回答是否定的。这是因为任务在调用时间延时函数的时候可能已经马上就要发生时间中断了,那么设置OSTCBDly的值为1,想延时10ms,然后系统切换到一个新的任务运行。在可能极短的时间,如0.5ms的时候就进入时钟中断服务程序,立刻将OSTCBDly的值减到0了。调度器在调度的时候就会恢复这个才延时了0.5ms的任务。可见,OSTimeDly的误差最大应该是1个时间片的长度,OSTCBDly(1)不会刚好延时10ms,如果真的需要延时一个时间片,最好调用OSTCBDly(2)。
任务延时函数OSTimeDly用于将任务阻塞一段时间,这个时间是以时间片为单位的。如果想以时、分、秒、毫秒为单位进行任务延时,需要调用以分秒作为单位的任务延时函数OSTimeDlyHMSM。
OSTimeDlyHMSM从功能上来说和OSTimeDly并没有多大的差别,只是将时间单位进行了转换,也就是说,转换了以小时、分、秒、毫秒为单位的时间和以时间片为单位的时间。OSTimeDlyHMSM的参数分别是小时数(hours)、分钟数(minutes)、秒数(seconds)和毫秒数(ms)。OSTimeDlyHMSM的代码如下所示:
由代码可知,OSTimeDlyHMSM在进行了参数检查之后,将以小时、分、秒、毫秒为单位时间转换为时间片ticks,最终调用OSTimedly来解决问题。
任务在延时之后,进入阻塞态。当延时时间到了就从阻塞态恢复到就绪态,可以被操作系统调度执行。但是,并非回到就绪态就只有这么一种可能,因为即便任务的延时时间没到,还是可以通过函数OSTimeDlyResume恢复该任务到就绪态的。
另外,OSTimeDlyResume也不仅仅能恢复使用OSTimeDly或OSTimeDlyHMSM而延时的任务。对于因等待事件发生而阻塞的,并且设置了超时(timeout)时间的任务,也可以使用OSTimeDlyResume来恢复。对这些任务使用了OSTimeDlyResume,就好像已经等待超时了一样。
但是,对于,采用OSTaskSuspend挂起的任务,是不允许采用OSTimeDlyResume来恢复的。
OSTimeDlyResume由于要处理任务错综复杂的关系,因此代码稍显复杂。代码中一个非常重要的数据结构就是任务块的OSTCBStat,如下所示:
相关的宏定义如下所示:
因此,如果一个任务只是设置了延时,那么该任务块的OSTCBStat的值应该是0,也就是OS_STAT_RDY。被设置了延时的任务和就绪任务的区别在于,就绪任务的控制块的OSTCBDly的值一定是0,而设置了延时的任务的OSTCBDly的值一定不是0.
如果一个任务在等待一个或多个事件的发生,那么该任务的控制块的0、1、2、4、5位必然有1位或多位不为0。也就是ptcb->OSTCBStat&OS_STAT_PEND_ANY的值不为0,这是在判断任务是不是在等待事件的发生。等待事件发生的任务可能设置了超时,也可能没有设置超时,如果没有设置超时那么就会在下面所示的代码返回:
所以不会被恢复到就绪态。设置了超时的OSTCBDly的值大于0,先将OSTCBDly的值置位为0,然后使用ptcb->OSTCBStat&=~OS_STAT_PEND_ANY,所以的5种事件等待标志全部强制清0,不再等待了。
另外,还需要判断OSTCBStat的位3挂起标志。因为被挂起的任务必须用也只能用OSTaskResume来恢复。OS_STAT_SUSPEND的值是0x08,ptcb->OSTCBStat&OS_STAT_SUSPEND是将STCBStat的位3挂起标志位单独取出来了,判断它是不是0,如果是0,那么就不是被挂起的任务,否则就是被挂起的任务。对于挂起的任务只能处理到这里,对于其他的任务就开始对就绪表和就绪组进行处理,恢复任务到就绪态,然后执行任务调度。