这也是taskDelay使用的一个缺陷,使用时需要注意。taskDelay(n)表示任务延时至第n个系统时钟到来的时刻,如图1所示:如果在A时刻调用taskDelay(1)仅仅延时5ms,而在B时刻taskDelay(1)就是刚好一个tick周期。可见要10ms的延时就必须taskDelay(2)才能保证。taskDelay有接近-1个tick的误差存在,taskDelay(n)实际上是延时(n-1)tick ~ n tick 之间的时间。延时精度为1/n,延时1s就是taskDelay(60)的误差极限为1.6%,而taskDelay(1)的误差极限将是100%。
使用taskDelay需注意的另外一点是:即使经过n个tick,调用延时的任务也不保证返回执行状态,可能有更高或相同优先级的任务占用了CPU。如果不希望CPU被其他任务占用,我们可以让程序在此处执行一个无效的耗时代码即可,这样又可以实现延时,CPU又不会被其他任务抢占。
watchDog
VxWorks提供了一个通用的看门狗定时器机制,利用提供的函数,任何任务都可以创建一个看门狗定时器,经过指定的延时后,实现在系统时钟ISR的上下文中运行指定的程序。需要注意的是,看门狗定时触发的程序是在中断级别上执行,而不是在任务的上下文。因此,看门狗定时挂接的程序编写有一定的限制,这个限制条件与中断服务程序的约束是一样的。比如,不能使用获取信号量的语句,不能使用像printf()这样的I/O 系统函数。
通过wdCreate( )可以创建一个看门狗定时器。调用wdStart()启动定时器,延时参数同taskDelay一样以tick为单位,同时还要指定定时完成后要调用的程序。如果应用程序同时需要多个看门狗函数,应使用wdCreate( )产生多个独立的看门狗ID。因为对于给定的看门狗ID,通过wdStart()只能关联一个看门狗函数。在指定的tick计数到达之前,要取消一个看门狗计时器,可以调用wdCancel()实现。每调用一次wdStart(),看门狗定时器只执行一次。对于一些要求周期性执行的应用程序。要获得该效果,定时器函数本身必须通过递归调用wdStart()来重新启动定时器。
如果利用看门狗定时器实现延时,存在与taskDelay一样的精度上的缺陷,以tick为基准。并且看门狗关联的函数受的限制很大,也是使用不便的一个方面。不过启动看门狗的任务不会被阻塞,因为wdStart()调用立即返回并继续执行。
sleep/nanosleep
sleep() 和nanosleep() 是VxWorks提供的延时函数接口,sleep以秒为单位,nanosleep提供更精确的延时,传参是时钟的结构体,参数可以精确到ns,但实际上只能做到大于或等于这个时间。因为sleep或nanosleep函数延时的时间基准仍是tick,调用此函数的任务处于任务延时状态,这些点与taskDelay() 一致。不同的地方是,taskDelay() 是用于任务调度,taskDelay(0)有他自身的含义,sleep(0) 是没有意义的。前面提过,taskDelay(n) 延时时间(n-1) tick ~ n tick,而sleep/nanosleep保证实际延时时间大于或等于设定的时间参数。这一点我们可以通过编写一个测试程序试验证明。代码如下:
void testTimer(int sec,int nsec) // 测试函数,输入参数为秒和纳秒
{
struct timespec tm; // 声明一个时钟结构体,包括秒和纳秒成员
tm.tv_sec = sec; // 根据传参赋值
tm.tv_nsec = nsec;
nanosleep(&tm,NULL); // 执行延时程序
}
在tornado的windshell环境下执行该函数和输出结果如下:
-> sp testTimer,0,1000100016; i
NAME ENTRY TID PRI STATUS PC SP ERRNO DELAY
tExcTask _excTask 2318d10 0 PEND 412cd8 2318c14 0 0
tLogTask _logTask 23131e0 0 PEND 412cd8 23130e4 0 0
tWdbTask _wdbTask 230e920 3 READY 412cd8 230e7c8 0 0
s2u24 _testTimer 23092b8 100 DELAY 412cd8 23091b4 0 1
value = 0 = 0x0
-> sp testTimer,0,1; i
NAME ENTRY TID PRI STATUS PC SP ERRNO DELAY
tExcTask _excTask 2318d10 0 PEND 412cd8 2318c14 0 0
tLogTask _logTask 23131e0 0 PEND 412cd8 23130e4 0 0
tWdbTask _wdbTask 230e920 3 READY 412cd8 230e7c8 0 0
s2u25 _testTimer 23092b8 100 DELAY 412cd8 23091b4 0 1
value = 0 = 0x0
可见延时1ns和16ms的结果都是一样的,都相当于taskDelay(1)。
高精度时钟sysTimeStamp
sysTimeStamp()也称作时间戳,是通过系统时钟实现的,刚开始也觉得费解,系统时钟的定时周期就是tick,怎么实现高精度时钟呢?通过走读BSP底层代码发现,sysTimeStamp其实是通过读取该定时器的当前计数值来获取高精度的定时。通过sysTimestampFreq)函数可以得到系统时间戳的频率,它往往反映的是CPU定时器的基准频率。当然如此高的分辨率只能是一个理想值,对于不同的系统不一定都能实现。毕竟该时间戳的实现方式就有一个致命的弱点:通过查询方式。系统时钟定时中断是以tick为单位的,进一步提高分辨率读取定时器计数值(CPU的一个特殊功能寄存器),只能是查询方式实现。如下代码示例。
void msDelay( int ms )
{
int t, t1, t2;
t1 = sysTimestamp(); // 记录上一轮时间戳
do
{
t = 0; //计数清零
while(t <sysTimestampFreq()/1000) // 时间戳小于1ms
{
t2 = sysTimestamp(); // 读取当前时间戳
if( t2>t1) t += (t2-t1); // 根据前后时间戳比较算出经历时间
else t+=t2; // 发生定时中断清零,应该为t+=(t2+计数最大值-t1),这里简化计算
t1 = t2; // 当前时间戳保存到下一轮计算
}
}while(ms–) // 循环ms次
}
这种方式定时比较占用系统资源,但是能方便实现并只适用于短时间的定时。为确保定时准确,应该在锁定中断情况下调用sysTimestamp,否则应该考虑使用sysTimestampLock函数。
辅助时钟
辅助时钟是利用目标板上CPU的另一个定时器(除了系统时钟之外)中断实现的,它可以灵活配置实现高分辨率的定时,而且容易实现ms级甚至us级定时。VxWorks也提供一系列与系统时钟相同的操作接口,用户可以方便挂接自己的中断处理函数,时钟分辨率的高低取决于硬件定时器的精度和用户中断函数的长短。要将辅助时钟作为精确的延时机制(比如毫秒级延时),我们可以通过这种方式实现。初始化程序先调用SysAuxClkRateSet()函数设置辅助时钟中断周期为1ms(一般在config.h文件中对中断频率作了限定,在AUX_CLK_RATE_MIN和AUX_CLK_RATE_MAX之间,如果需要可以对此宏定义修改),通过SysAuxClkConnect() 将用户处理函数连接到辅助时钟中断上,用户处理函数可以为SemGive(semTimer)释放一个同步信号量。编写一个msDelay(int ms)作为其他任务调用接口,函数代码如下:
void msDelay(int ms)
{
int i;
sysAuxClkEnable(); //启动辅助定时器
for(i=0;i<ms;i++) //循环ms次
semTake(semTimer); //等待定时中断释放信号量
sysAuxClkDisable(); //关闭辅助定时器
}
这种方式能实现十分精确的定时,调用延时的任务处于任务阻塞状态。但是使用上任存在缺陷,不能实现多个任务同时调用,并且需要CPU的一个时钟资源,如果没有多余的时钟,这一方法就不能实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaFwDVNA-1636160296577)(https://www.vxworks.net/images/app/timer-spy.jpg)]
另外需要注意一点:Tornado的调试工具Browser ->Spy Chart的实现原理是利用辅助定时器产生中断,并记录当前被中断的任务,抽样数据反映各任务CPU占用率的情况。因此如果调试程序中使用了辅助定时器,使用Spy Chart时,定时处理函数会被重新挂接,原有定时挂接的程序将得不到进行。反过来,如果在Spy Chart运行之后挂接辅助定时处理函数,Spy Chart的运行将出现问题。试验发现,运行Spy Chart后重现挂接辅助定时处理函数,Spy Chart即使选中自动刷新,各任务状态也不会更新,如图2所示。
VxWorks提供的定时接口(不一定专门用于定时,可以间接实现)远不只这些,具体使用哪种方式,应根据其精度要求,资源状态和优先级要求而定。
后面介绍一下重要函数的使用方法
taskDelay( )
NAME
*taskDelay*( ) - delay a task from executing
SYNOPSIS
STATUS taskDelay
(
int ticks /* number of ticks to delay task */
)
DESCRIPTION
This routine causes the calling task to relinquish the CPU for the duration specified (in ticks). This is commonly referred to as manual rescheduling, but it is also useful when waiting for some external condition that does not have an interrupt associated with it.
If the calling task receives a signal that is not being blocked or ignored, *taskDelay*( ) returns ERROR and sets errno to EINTR after the signal handler is run.
RETURNS
OK, or ERROR if called from interrupt level or if the calling task receives a signal that is not blocked or ignored.
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
续会持续更新**
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-WWEfWHm6-1712918895831)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!