6.1 软件定时器:守护任务与回调纪律


6.1 软件定时器:守护任务与回调纪律

本节摘要:软件定时器把"到点做某事"从业务任务中解耦出来:所有定时器由一个守护任务统一管理,回调在守护任务的上下文里串行执行,命令经队列传递。本节拆解这个模型的三个关键机制——守护任务的仲裁循环、命令队列的串行化、单次与周期两类语义;再给出回调纪律清单与常见误用的病理分析;最后讨论守护任务三个配置项的联动调参。读懂它,你会发现定时器不是"小工具",而是理解内核任务化设计哲学的绝佳标本。

系统里到处是周期性工作:传感器每秒采样、状态灯每半秒翻转、看门狗定期喂、界面定时刷新。最直觉的解法是每件事一个任务加绝对延时循环——第 2 章正是这么教的。但当周期性工作膨胀到十几个,每个"任务"其实只干几行活,任务的开销(栈、控制块、调度税)就喧宾夺主了。软件定时器为此而生:一件小事不值得一个任务,一串小事共享一个任务

一、守护任务模型:一个任务管一群钟

内核开启定时器功能后,调度器启动时多创建一个任务——定时器守护任务(也叫定时器服务任务)。所有软件定时器都住在它那里:到期时刻排序(与延迟链表同构的有序结构),守护任务阻塞在"最近一个到期时刻"上,到点醒来逐个执行到期的回调。你调用定时器操作接口(启动、停止、改周期、复位)时,真正的动作不是原地完成的——操作被封装成一条命令,投进守护任务的命令队列,由守护任务在下一轮仲裁时执行。

为什么绕这一道?两个理由。其一,串行化消竞态:定时器状态只有一个主人(守护任务),所有操作排队进门,不存在两个执行流同时改一个定时器的状态——第 3 章的锁在这里被架构消灭了,与第 4 章"队列替代共享"的倾向一脉相承。其二,回调上下文统一:所有回调都在守护任务的栈上、以守护任务的优先级执行,回调的世界是可预测的单线程世界。

守护任务的仲裁循环

这个循环解释了一个高频困惑:**为什么定时器操作的生效有延迟?**你停止一个定时器后,回调可能还会执行一次——命令在队列里排队期间,到期时刻先到了。对时间敏感的场合,判断"操作是否生效"要看返回语义(操作接口带阻塞等待选项,可等守护任务确认执行完毕),而不是假设瞬时生效。

二、两类语义与操作面板

单次定时器到期执行一次即停;周期定时器到期执行后自动重排下一轮。语义细节里有两个易错点。周期定时器的"周期"锚定在到期时刻,与绝对延时的锚定同理:回调执行超时不累积到下一轮(到期点漂移可控),但如果回调本身耗时超过周期,排队就会越积越深——守护任务将永远追不上时刻表,这是回调纪律的定量版本。复位操作把定时器拉回起跑线重新计时,语义是"从现在再等一个周期",按钮去抖、空闲检测这类"反复续期"的场景靠它一行搞定。

操作面板速览:创建(单次或周期、回调函数、传入标识)、启动、停止、改周期(常用作"带参数的重启")、复位、删除。创建可动态可静态(第 5 章的两条路线在此同样适用),删除前要确认没有别处还持有它的句柄——与任务删除同理,内核不替你管理引用。

三、回调纪律:四条禁令与一份病理手册

回调运行在守护任务上下文,这个事实推出四条禁令。禁令一,不许阻塞:守护任务睡了,全部定时器停摆——你的系统里所有周期性工作一起冻结。症状极具迷惑性:某个回调里一句"等待队列"偶尔卡住,全系统"所有定时器偶发集体迟到"。禁令二,不许干重活:回调占用守护任务的时间,等于给所有其他定时器加延迟;标准是微秒级的轻活,重活触发一个任务去做(通知或队列,第 4 章两板斧)。禁令三,不许调用可能阻塞的接口:普通版队列收发带超时参数就可能睡——回调里要么用零超时,要么用中断安全版(它保证不睡),要么干脆别在回调里碰队列。禁令四,不许自我死等:回调里调用定时器操作并选择"等待守护任务确认"选项,等于自己等自己——直接死锁。

病理手册记两个典型。病理一,周期漂移:团队发现状态灯闪烁"偶尔顿一下",排查发现某回调里做了浮点格式化(微秒到毫秒级),把同期的喂狗回调顶晚了。处方:回调只置标志,格式化移到显示任务。病理二,队列打嗝:回调里向满队列发送且带短超时——偶尔队列满,守护任务睡了几毫秒,全部定时器抖动一次。处方:零超时加丢弃计数(与 4.1 节生产者策略完全一致——问题同源,药方同源)。

⚠️ 定时器回调最容易违反直觉的一点:它的优先级是守护任务的优先级,不是"你自己定的"。想让回调准时,调的是守护任务的优先级配置;想让回调能跑得动,调的是守护任务的栈深配置。配错栈深的症状最阴险——回调里稍微多几个局部变量就爆栈,而爆的是守护任务的栈(2.4 节的钩子报的是守护任务名,第一次见的人很难想到病根在自己的回调里)。

四、三个配置项的联动调参

守护任务由三个配置项塑造,它们联动而非独立。优先级:回调的及时性上限。放低了,业务繁忙时回调迟到;放过高了,回调(哪怕极短)打断一切。经验起点是中高优先级——定时器多属"组织性工作",不该被业务压着,但也不该压过硬实时任务。栈深:全部回调共享这块栈,深度按"最深的一个回调"加余量定,回调纪律越好(活越轻),栈越省。命令队列长度:突发操作请求的缓冲。队列满时操作接口按超时参数行为(阻塞等待或失败返回),高频操作定时器的系统要加大队列或改用零超时加重试的组合。

调参的次序建议:先定优先级(观察回调及时性),再按最深回调定栈(用水位测量核实,别拍脑袋),最后压力测试定队列长度(统计操作突发期的最长排队)。三个参数都在配置文件里,改起来零代码成本——但每次改动都要重新过回调及时性的观察,因为它们联动。

五、案例:用定时器重构一组周期杂务

背景。某采集终端有五件周期杂务:状态灯翻转(半秒)、看门狗喂食(一百毫秒)、传感器轮询(一秒)、统计上报(一分钟)、按键长按检测(五十毫秒)。原实现五个任务五个栈,随机存储器紧张。操作:全部改为软件定时器回调——喂食、翻灯、轮询触发(只发通知)、上报触发(只发通知)、按键扫描(只读引脚电平,状态机判断长按)。重活(实际采样、实际组包)全部留在原有任务里,回调只做触发与极轻判断。结果:任务数从五减到二(采集与上报),省下约一千字节的栈与控制块内存;守护任务配置为较高优先级、中等栈深。解读:收益不止内存——五件事的时序行为集中到一处可观察(守护任务的回调日志一目了然),出问题不再需要在五个任务间跳。变式:若其中一件事的回调可能变重(如传感器轮询后来升级为带重试的读取),它应该"毕业"回任务形态——定时器触发加任务干活的混合结构,触发准时、干活从容,是周期性工作最稳健的终态。

本节要点回顾

  • 模型一句话:所有定时器住在一个守护任务里,命令走队列、回调串行执行——竞态被架构消灭;
  • 操作非瞬时生效:命令排队期间到期点可能先到,敏感场景用带确认的调用或检查返回语义;
  • 四条禁令:不阻塞、不重活、不碰可能睡的接口、不自我死等——违反的症状全部指向"全部定时器一起抖";
  • 回调的优先级与栈是守护任务的配置,调参看守护任务,爆栈报的是它的名;
  • 三配置联动:优先级定及时性、栈深按最深回调、队列长度按操作突发;
  • 重构方向:小事定时器化、重活留任务、触发加干活的混合结构是周期工作的终态。

周期的工作有了组织,但系统大部分时间其实无事可做——电池设备最贵的恰是这些"无事可做"的毫秒。下一节看内核如何连自己的心跳都肯停:无滴答空闲的完整决策链。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U