1. VxWorks时间管理概述:系统时钟与Tick机制、时间片轮转调度、时间管理核心API介绍
各位同学,咱们今天聊聊VxWorks的时间管理。说实话,时间管理在嵌入式系统里是个老生常谈的话题,但也是最容易出问题的地方。我见过不少项目,功能逻辑写得漂漂亮亮,一上电跑起来就乱套——十有八九是时间管理没搞明白。
好,咱们直接进入正题。
1.1 系统时钟与Tick机制
VxWorks里最核心的时间概念是什么?是Tick。说白了,Tick就是系统的心跳。你想想看,一个操作系统要调度任务、要延时、要超时检测,总得有个统一的节拍器吧?这个节拍器就是系统时钟中断。
系统时钟每产生一次中断,我们就说走过了一个Tick。默认情况下,VxWorks的Tick频率是60Hz,也就是每秒60次中断。嗯,这里要注意——这个值是可以改的,我后面会讲。
核心概念:Tick是VxWorks时间管理的最小时间单位。所有与时间相关的操作,最终都会落到Tick上。
我在项目中遇到过这样一个坑:有个同事把Tick频率设成了1000Hz,觉得这样精度高。结果系统负载直接飙升,任务切换频繁到CPU都快被吃光了。为什么?因为每秒1000次中断,每次中断都要保存上下文、调度任务,开销太大了。
所以Tick频率怎么选?我个人习惯是:
- 对实时性要求不高(比如数据采集、状态监控):用默认60Hz就够了
- 需要较高精度(比如电机控制、PWM生成):可以提高到100-200Hz
- 千万别超过500Hz,除非你的CPU非常强劲
1.2 时间片轮转调度
讲完Tick,咱们聊聊时间片轮转调度。这个机制其实很简单:多个同优先级的任务,每个任务轮流跑一小段时间,时间到了就换下一个。
这个「一小段时间」就是时间片。默认情况下,VxWorks的时间片长度是1个Tick。也就是说,如果Tick频率是60Hz,每个任务最多跑16.67毫秒就会被切换出去。
你可能会问:「那如果一个任务没跑完怎么办?」好问题。时间片轮转调度是抢占式的——时间一到,系统强制切换。任务下次再获得CPU时,从断点继续执行。
小技巧:我建议你在设计任务时,尽量让每个任务在一个时间片内完成主要工作。如果任务耗时太长,考虑拆分成多个小任务,或者用状态机实现。
我曾经接手过一个项目,里面有个任务要处理大量数据,每次运行都要几十毫秒。结果其他同优先级的任务根本抢不到CPU,系统响应慢得像蜗牛。后来我把那个任务拆成了几个阶段,每个阶段控制在5毫秒以内,问题就解决了。
时间片轮转调度的配置也很简单:
/* 开启时间片轮转调度 */
kernelTimeSlice(1); /* 参数是Tick数,1表示每个任务跑1个Tick */
/* 也可以设置更长时间片 */
kernelTimeSlice(5); /* 每个任务跑5个Tick */
嗯,这里要注意:kernelTimeSlice()的参数是Tick数,不是毫秒数。如果你想让每个任务跑10毫秒,而Tick频率是100Hz(每个Tick 10毫秒),那就传1。
1.3 时间管理核心API介绍
好了,前面铺垫了这么多,咱们来看看VxWorks到底提供了哪些时间管理API。我挑几个最常用的讲,这些你在实际项目中几乎天天都会用到。
1.3.1 获取系统时间
/* 获取系统启动以来的Tick数 */
ULONG tickGet(void);
/* 获取系统启动以来的秒数 */
ULONG sysClkRateGet(void); /* 获取Tick频率 */
ULONG tickGet() / sysClkRateGet(); /* 手动计算秒数 */
我个人习惯用tickGet()来做性能测量。比如测量某段代码的执行时间:
ULONG startTick, endTick;
startTick = tickGet();
/* 执行要测量的代码 */
endTick = tickGet();
printf("执行耗时: %d Ticks\n", endTick - startTick);
注意:tickGet()返回的是32位无符号整数。如果系统运行时间很长,Tick数可能会溢出。比如60Hz频率下,大约828天才溢出一次。一般项目不用担心,但如果你做的是长期运行的系统,建议用tick64Get()。
1.3.2 任务延时
任务延时是最常用的功能。VxWorks提供了两种方式:
/* 延时指定Tick数 */
STATUS taskDelay(ULONG ticks);
/* 延时指定秒数 */
STATUS taskDelay(sysClkRateGet() * seconds); /* 手动转换 */
举个例子:
/* 延时100个Tick */
taskDelay(100);
/* 延时2秒(假设Tick频率60Hz) */
taskDelay(120); /* 2 * 60 = 120 */
你可能会问:「为什么不用毫秒级的延时?」好问题。VxWorks确实提供了taskDelay()的毫秒版本——taskDelayMs(),但底层还是转换成Tick来实现的。所以精度受Tick频率限制。
避坑指南:我曾经在项目里用taskDelay(1)来实现「短暂等待」,结果发现实际等待时间远大于1个Tick。为什么?因为taskDelay(0)才是让出CPU但不等待,taskDelay(1)至少等待1个Tick。如果你想让任务立即让出CPU,用taskDelay(0)。
1.3.3 超时等待
很多同步机制都支持超时等待。比如信号量:
/* 等待信号量,最多等100个Tick */
semTake(semId, 100);
/* 等待信号量,最多等500毫秒 */
semTake(semId, WAIT_FOREVER); /* 无限等待 */
超时参数的单位也是Tick。我建议你养成一个好习惯:永远不要用WAIT_FOREVER。为什么?因为一旦信号量永远不来,你的任务就死锁了。我在项目中吃过这个亏,一个任务等另一个任务释放资源,结果对方挂了,这边就永远卡死了。
所以我的做法是:
/* 设置一个合理的超时时间 */
if (semTake(semId, 100) == OK) {
/* 成功获取信号量 */
} else {
/* 超时处理,比如重试或报错 */
printf("等待信号量超时!\n");
}
1.3.4 系统时钟控制
最后,咱们看看怎么控制系统时钟:
/* 获取当前Tick频率 */
ULONG rate = sysClkRateGet();
/* 设置Tick频率(需要管理员权限) */
sysClkRateSet(100); /* 设置为100Hz */
警告:修改Tick频率会影响所有与时间相关的操作。比如之前用taskDelay(60)延时1秒,改成100Hz后同样的代码只延时0.6秒。所以改频率前,一定要检查所有延时和超时参数。
我个人建议:项目初期就确定好Tick频率,后面尽量不改。如果实在要改,写个全局宏定义:
#define TICK_RATE 100 /* 系统Tick频率 */
#define MS_TO_TICKS(ms) ((ms) * TICK_RATE / 1000)
#define SEC_TO_TICKS(s) ((s) * TICK_RATE)
/* 使用宏来延时 */
taskDelay(MS_TO_TICKS(500)); /* 延时500毫秒 */
这样改频率时,只需要改TICK_RATE一个地方,所有延时自动适配。
小结
好了,这一章的内容就这些。咱们回顾一下:
- Tick机制是VxWorks时间管理的基础,频率选择要权衡精度和开销
- 时间片轮转调度让同优先级任务公平共享CPU,但要注意任务设计
- 核心API包括
tickGet()、taskDelay()、semTake()等,使用时注意单位是Tick
下一章咱们会深入讲定时器的使用,包括看门狗定时器和POSIX定时器。到时候我会分享一些实际项目中的定时器设计模式,保证让你少踩坑。
好,今天就到这里。有问题随时问我。