3. INTEGRITY调度器框架

INTEGRITY的调度器不是一个“单一算法”,而是一套可裁剪、可组合的调度框架。它把“谁先运行、运行多久、何时切换、切换前后做什么”拆成若干可配置的维度:调度器类型决定基本排序规则,调度策略配置决定同一类型下的行为细节,时间片与优先级决定运行时长与抢占关系,钩子函数则把用户逻辑插入到调度决策的关键节点。理解这套框架,比记住某个API更重要。

一、调度器类型:先确定“排序依据”

INTEGRITY支持多种调度器类型,常见的有时间片轮转(Round-Robin)、优先级抢占(Priority Preemptive)、以及速率单调(Rate Monotonic)等。类型的选择直接决定任务在就绪队列中的排列方式。例如,优先级抢占型调度器始终让最高优先级就绪任务运行;而时间片轮转型则在同优先级任务之间按时间片轮流执行。在INTEGRITY中,调度器类型通常通过配置表或初始化参数指定,而不是在运行时随意切换。一个典型的配置片段如下:

/* 示例:调度器类型配置(示意) */
SchedulerConfig schedCfg = {
    .type = SCHED_PRIORITY_PREEMPTIVE,
    .timeSliceTicks = 10,
    .preemptThreshold = 0
};

这里type字段就是调度器类型。选择优先级抢占型时,timeSliceTicks通常只对同优先级任务生效;若系统只使用唯一优先级,则时间片不起作用。因此,类型选择必须与任务优先级设计一起考虑。

二、调度策略配置:类型之外的“行为开关”

同一调度器类型下,策略配置决定更细的行为。INTEGRITY常见的策略参数包括:是否允许同优先级时间片轮转、是否启用抢占阈值、是否允许任务主动让出CPU、以及空闲任务的处理方式。抢占阈值是一个容易被忽略但很实用的参数:当任务优先级高于阈值时才允许抢占,低于阈值的任务即使优先级更高也不能打断当前任务。这可以避免高频低优先级任务频繁打断关键任务。

策略配置通常以结构体或配置表形式给出,并在系统初始化时一次性加载。配置错误不会在编译期报错,而是在运行时表现为任务饥饿或响应延迟,因此建议在集成阶段用调度跟踪工具确认实际调度序列。

三、时间片与优先级:运行时长与抢占关系

时间片决定一个任务在未被抢占时最多连续运行多少个时钟节拍。优先级决定任务之间的抢占顺序。两者共同作用:高优先级任务一旦就绪,立即抢占低优先级任务;同优先级任务之间,若启用时间片,则按时间片轮流运行。假设系统节拍为1ms,时间片配置为10个节拍,则同优先级任务每次最多运行10ms。若任务在时间片用完前主动阻塞或让出,剩余时间片通常不累积到下一次。

优先级数值的方向必须明确:INTEGRITY中通常数值越小优先级越高,或相反,取决于具体版本和配置。本文统一约定“数值越小优先级越高”。在配置任务时,必须与调度器类型和策略保持一致,否则会出现“高优先级任务反而被低优先级任务阻塞”的异常现象。

INTEGRITY调度器框架:配置维度与运行时行为 调度器类型 优先级抢占 / 时间片轮转 调度策略配置 抢占阈值 / 同优先级轮转 时间片 节拍数 / 同优先级生效 优先级 数值越小越高 调度器初始化 加载配置表 → 建立就绪队列 → 注册钩子函数 运行时调度行为 任务就绪 插入就绪队列 选择最高优先级 同优先级按时间片 上下文切换 保存/恢复现场 钩子回调 切换前后触发 钩子 函数 插入点

四、调度器钩子函数:把用户逻辑插入决策点

钩子函数是INTEGRITY调度框架中非常实用的扩展点。常见的钩子包括:任务切换前钩子、任务切换后钩子、时间片到期钩子、以及空闲任务进入钩子。它们允许在不修改调度器核心代码的前提下,插入统计、监控或安全逻辑。例如,在切换前钩子中记录被换出任务的ID和剩余时间片,在切换后钩子中记录新任务的启动时间,就可以构建一个轻量级的调度跟踪器。

钩子函数的执行上下文通常是调度器内部,因此必须遵守两条规则:第一,钩子中不能调用可能引起阻塞的API;第二,钩子执行时间应尽可能短,否则会直接增加上下文切换开销。一个典型的钩子注册示例如下:

/* 示例:注册任务切换钩子(示意) */
void onTaskSwitchOut(TaskId prev, TaskId next) {
    traceRecord(prev, next, getTickCount());
}
SchedulerHook hook = {
    .onSwitchOut = onTaskSwitchOut,
    .onSwitchIn  = NULL
};
registerSchedulerHook(&hook);

这里onSwitchOut在任务被换出时调用,onSwitchIn在任务被换入时调用。若不需要某个钩子,将其置为NULL即可。钩子函数的注册必须在调度器启动前完成,运行时注册通常不被支持。

五、调度器初始化与运行时行为

调度器初始化发生在系统启动阶段,主要完成三件事:加载调度器类型和策略配置、建立就绪队列和空闲任务、注册钩子函数。初始化完成后,调度器进入运行时状态,开始响应任务状态变化。运行时行为可以概括为一个循环:任务就绪时插入就绪队列,调度器选择最高优先级任务运行,若同优先级则按时间片轮转;当更高优先级任务就绪时发生抢占;当任务阻塞或时间片用完时触发切换;每次切换前后调用相应钩子。

需要特别注意的是,调度器初始化顺序会影响运行时行为。例如,若在注册钩子之前就启动了调度器,则早期任务切换不会触发钩子,导致跟踪数据不完整。因此,建议的初始化顺序是:配置调度器类型与策略 → 创建任务 → 注册钩子 → 启动调度器。这个顺序在INTEGRITY的启动代码中通常由InitializeScheduler()和StartScheduler()两个阶段体现,前者完成配置加载,后者真正开始调度。

实践建议:在集成阶段,先用一个高优先级任务和一个低优先级任务验证抢占行为,再用两个同优先级任务验证时间片轮转。观察钩子输出的切换序列,确认与配置预期一致。若发现同优先级任务没有轮转,优先检查时间片是否配置为0,以及策略中是否禁用了同优先级轮转。

总结来说,INTEGRITY调度器框架的核心在于“配置决定行为,钩子提供观察”。调度器类型和策略配置决定了任务如何被排序和抢占,时间片和优先级决定了运行时长与抢占关系,钩子函数则让开发者能够在不侵入内核的情况下监控和扩展调度行为。初始化顺序和运行时状态变化是验证配置是否生效的关键观察点。掌握这五个维度,就能在实际项目中快速定位调度异常,并做出合理的配置调整。