3、POSIX定时器接口:timer_create/timer_settime/timer_gettime/timer_delete、信号驱动与线程驱动模式对比、POSIX定时器在VxWorks中的实现差异
好,咱们今天聊聊POSIX定时器。说实话,这个接口在VxWorks里是个好东西,但也是个容易踩坑的地方。我最早接触它是在一个多传感器数据采集项目里,当时需要同时管理七八个不同周期的定时任务,用传统的watchdog方式根本搞不定。后来换成POSIX定时器,整个世界清静了。
3.1 核心API:四个函数搞定一切
POSIX定时器的接口非常简洁,就四个函数。你想想看,一个定时器的完整生命周期,从创建到设置、查询、删除,全包了。
3.1.1 timer_create — 创建定时器
这个函数负责创建一个定时器对象。原型长这样:
#include <signal.h>
#include <time.h>
int timer_create(clockid_t clockid,
struct sigevent *evp,
timer_t *timerid);
参数说明:
- clockid:时钟源。常用的是
CLOCK_REALTIME(系统实时时钟)和CLOCK_MONOTONIC(单调递增时钟)。我个人习惯用CLOCK_MONOTONIC,因为它不受系统时间调整影响,做周期性任务更靠谱。 - evp:事件通知方式。这是关键,决定了定时器到期后怎么通知你。后面会细讲。
- timerid:输出参数,返回定时器句柄。
timer_t类型是个指针,不是整数。别拿它跟Linux下的实现混为一谈。我曾经见过有人把timer_t当int用,结果传参传错了,定时器根本跑不起来。
3.1.2 timer_settime — 启动/修改定时器
创建完定时器,得告诉它什么时候触发、要不要重复。这就是timer_settime的活:
int timer_settime(timer_t timerid,
int flags,
const struct itimerspec *value,
struct itimerspec *ovalue);
itimerspec结构体包含两个成员:
- it_interval:周期间隔。如果为0,就是单次定时器。
- it_value:首次到期时间。如果为0,停止定时器。
举个例子,创建一个每100毫秒触发一次的定时器:
struct itimerspec ts;
ts.it_interval.tv_sec = 0;
ts.it_interval.tv_nsec = 100000000; // 100ms
ts.it_value.tv_sec = 0;
ts.it_value.tv_nsec = 100000000; // 首次也是100ms后触发
timer_settime(my_timer, 0, &ts, NULL);
it_value设成0。但注意,设成0其实是停止定时器。正确的做法是设一个极小的值,比如1纳秒。
3.1.3 timer_gettime — 查询剩余时间
这个函数用来看看定时器还有多久到期:
int timer_gettime(timer_t timerid, struct itimerspec *value);
返回的it_value就是距离下次到期还剩多少时间。如果返回0,说明定时器已经停止了。我在调试的时候经常用这个函数来确认定时器是不是按预期在跑。
3.1.4 timer_delete — 销毁定时器
用完记得销毁,别造成资源泄漏:
int timer_delete(timer_t timerid);
嗯,这里要注意:如果定时器正在运行,timer_delete会先停止它再销毁。但如果有未处理的信号或回调,可能会出问题。我建议在删除前先调用timer_settime把定时器停掉,确保安全。
3.2 信号驱动 vs 线程驱动:两种模式怎么选?
这是POSIX定时器最核心的设计选择。说白了,就是定时器到期后,系统怎么通知你。
3.2.1 信号驱动模式
这是POSIX标准默认的方式。创建定时器时,sigevent结构体里指定信号编号:
struct sigevent ev;
ev.sigev_notify = SIGEV_SIGNAL;
ev.sigev_signo = SIGRTMIN; // 使用实时信号
ev.sigev_value.sival_ptr = my_timer;
timer_create(CLOCK_MONOTONIC, &ev, &my_timer);
定时器到期时,内核会向你的任务发送一个信号。你需要注册信号处理函数来响应。
优点:
- 开销小,不需要额外线程
- 响应快,信号是异步通知
缺点:
- 信号处理函数里能干的事有限。不能调用printf、malloc等非重入函数
- 多个定时器共用一个信号时,你得自己区分是哪个定时器触发的
- 信号可能丢失。如果信号来得太频繁,内核会合并或丢弃
3.2.2 线程驱动模式
VxWorks对POSIX标准做了扩展,支持用线程来处理定时器事件:
struct sigevent ev;
ev.sigev_notify = SIGEV_THREAD;
ev.sigev_notify_function = my_timer_callback;
ev.sigev_notify_attributes = NULL; // 使用默认线程属性
ev.sigev_value.sival_ptr = my_data;
timer_create(CLOCK_MONOTONIC, &ev, &my_timer);
定时器到期时,系统会创建一个新线程来执行my_timer_callback函数。
优点:
- 回调函数里可以随便写,没有信号处理函数的限制
- 每个定时器有自己的线程上下文,互不干扰
- 代码逻辑清晰,容易维护
缺点:
- 线程创建有开销。如果定时器频率很高(比如1ms),频繁创建线程会拖垮系统
- 需要处理线程同步问题。多个定时器回调可能同时访问共享资源
- 线程栈大小要合理设置,否则容易栈溢出
3.3 VxWorks中的实现差异
VxWorks的POSIX定时器实现跟标准的Linux实现有几个关键差异,我踩过不少坑,给你总结一下:
| 特性 | 标准POSIX | VxWorks实现 |
|---|---|---|
| 时钟源支持 | CLOCK_REALTIME, CLOCK_MONOTONIC等 | 额外支持CLOCK_TIMER(VxWorks专用高精度时钟) |
| 线程驱动模式 | 可选(部分系统支持) | 原生支持,且性能优化过 |
| 定时器精度 | 取决于系统时钟滴答 | 支持纳秒级精度(需硬件支持) |
| 最大定时器数量 | 由系统限制 | 可配置,默认通常为256个 |
| 信号处理 | 标准信号机制 | VxWorks信号处理有优先级,实时信号优先级更高 |
第一个差异:时钟源选择
VxWorks提供了CLOCK_TIMER这个专用时钟源。它直接绑定到硬件定时器,精度比CLOCK_MONOTONIC更高。我在做电机控制项目时,要求PWM周期误差不超过1微秒,用CLOCK_TIMER才搞定。
timer_t motor_pwm_timer;
struct sigevent ev;
ev.sigev_notify = SIGEV_THREAD;
ev.sigev_notify_function = pwm_callback;
// 使用高精度时钟源
timer_create(CLOCK_TIMER, &ev, &motor_pwm_timer);
第二个差异:线程驱动模式的实现
VxWorks的SIGEV_THREAD模式跟Linux不太一样。Linux下每次定时器到期都会创建一个新线程,而VxWorks会复用线程池中的线程,减少了创建开销。但要注意,如果回调函数执行时间超过了定时器周期,下一个到期事件可能会被阻塞。
SIGEV_THREAD模式时,如果回调函数里调用了taskDelay(),会导致定时器线程池耗尽,其他定时器全部失效。后来我改用信号驱动+任务间通信的方式才解决。所以,回调函数里尽量不要做阻塞操作。
第三个差异:定时器精度与系统配置
VxWorks的定时器精度受系统时钟滴答影响。默认情况下,系统时钟滴答是60Hz(约16.67ms),这意味着你设的1ms定时器实际上只能以16.67ms的粒度工作。要获得高精度,需要做两件事:
- 在VxWorks内核配置中启用高精度定时器(
INCLUDE_HIGH_RES_TIMER) - 使用
sysClkRateSet()提高系统时钟频率,或者直接使用硬件定时器
// 提高系统时钟频率到1000Hz
sysClkRateSet(1000);
// 现在定时器精度可以达到1ms
struct itimerspec ts;
ts.it_interval.tv_sec = 0;
ts.it_interval.tv_nsec = 1000000; // 1ms
第四个差异:定时器资源管理
VxWorks的定时器资源是静态分配的。默认情况下,系统最多支持256个POSIX定时器。如果你需要更多,得在VxWorks镜像配置里修改NUM_TIMERS参数。我建议你在设计阶段就评估好定时器数量,别等到运行时才发现不够用。
好了,POSIX定时器的核心内容就这些。总结一下:
- 四个API函数,记住它们的用法和参数含义
- 信号驱动适合简单场景,线程驱动适合复杂场景
- VxWorks的实现有差异,特别是时钟源和线程模式
- 高精度定时需要调整系统配置
下一章我们会讲VxWorks原生的看门狗定时器,那又是另一套玩法了。