菜单

Cobalt抢占式和优先级调度

Cobalt 抢占式和优先级调度

Xenomai 项目是一个开源的 RTOS-to-Linux 可移植框架,遵循 Creative Commons BY-SA 3.0 和 GPLv2 许可证,有两种形式:

  • 作为经过补丁处理的 Linux 的共核/实时扩展(RTE),代号为 Cobalt
  • 作为本地 Linux(包括 PREEMPT-RT)的库,代号为 Mercury。它旨在 3.0 分支中既能作为共核使用,也能在 PREEMPT_RT 上运行。

Xenomai 项目将一个名为 Cobalt 核心的实时内核合并到了 Linux 内核中,并在内核空间中共存。Cobalt 核心中的中断和线程优先级比 Linux 内核中的中断和线程更高。由于 Cobalt 核心执行的指令较 Linux 内核少,因此可以减少调用路径中的不必要延迟和历史负担。

使用这种实时目标设计,Xenomai修补的Linux内核可以实现良好的实时多线程性能。


双域中断管道 - [Head] 和 [Root]

两阶段中断管道是实现 Cobalt 实时框架的底层机制。

Cobalt 补丁 Linux 内核在硬件中断上占据主导地位,这些中断原本属于 Linux 内核。Cobalt 将首先处理它感兴趣的中断,然后将其他中断路由到 Linux 内核。前者称为 head stage,后者称为 root stage:

  • [Head] 阶段 对应于 Cobalt 核心(实时域)
  • [Root] 阶段 对应于 Linux 内核(非实时域)

[Head] 阶段的优先级高于 [Root] 阶段,通过压缩硬件和软件的处理时间提供最短的响应时间。


设置POSIX 线程在双域切换

Linux 上,taskset 命令允许你更改进程的 CPU 亲和性。通常,它与内核命令行中确定的 CPU 隔离一起使用。以下示例脚本演示了将实时进程的亲和性更改为核心 1 的操作:

shell 复制代码
cpu="1"
taskset -c $cpu latency -c1 -t0 -p 100 -P 99 -h -g result.txt

如果作为systemd服务作为系统守护进程,可以参考下面的设置:

shell 复制代码
[Unit]
Description=cobalt latency service
Wants=network-online.target
[Service]
CPUAffinity=1
ExecStart=/usr/local/bin/latency -c1 -t0 -p 100 -P 99 -h -g result.txt
Type=exec
[Install]
WantedBy=multi-user.target
Alias=latency.service

Cobalt 核心的线程并不是完全与 Linux 内核的线程隔离。相反,它重用了普通的 kthread 并添加了特殊功能;kthread 可以在 Cobalt 的实时上下文(out-of-band 实时域)和普通的 Linux 内核上下文(in-band 非实时域)之间切换。其优势在于,当处于实时域上下文时,线程可以利用 Linux 内核的基础设施。在典型场景中,Cobalt 线程会以普通 kthread 的形式启动,调用 Linux 内核的 API 进行准备工作,然后切换到实时域上下文,作为 Cobalt 线程执行实时工作。其劣势在于,处于实时域上下文时,Cobalt 线程很容易因为误调用 Linux 内核 API 而迁移到非实时上下文。在这种情况下,问题很难被发现;开发者通常误认为他们的线程正在 Cobalt 核心下运行,直到检查 ftrace 输出或任务超出其截止期限时才注意到问题。

基于 Cobalt 的用户空间应用程序可以在抢占式调度和通用分时(SCHED_OTHER)调度策略之间影射同一线程:

非实时域:此模式下可以访问 Linux GPOS 服务和 Linux [ROOT] 域设备驱动程序(例如,可以使用 ps -x 或 top 等 Linux 命令)。
实时域:此模式下可以访问所有 Xenomai RTOS 服务和 RTDM [HEAD] 域设备驱动程序。

可以通过以下命令查看进程状态:

shell 复制代码
cat /proc/xenomai/sched/stat

使用Cobalt POSIX API 封装

当使用 libcobalt.so 链接时,通过 pthread_create()pthread_setschedparam() 设置的 Linux pthread,任何对标准(S) POSIX 调度策略的更改,例如 SCHED_FIFOSCHED_RR,都会通过一种称为影射 (shadowing) 的机制,跳转到 Cobalt 任务。

此外,Xenomai/Cobalt 提供了补充的调度策略(X)

  • SCHED_TP 实现了线程组的时间分区调度策略(一个组可以是一个或多个线程)。
  • SCHED_SPORADIC 实现了任务服务器调度器,用于运行分散活动,并使用配额避免周期性 (在 SCHED_RRSCHED_FIFO 任务) 干扰。
  • SCHED_QUOTA 实现了基于预算的调度策略。线程组在预算超出后暂停。预算将在每个配额周期内重新填充。
Scheduling Policies 基础Linux 基础Linux + PREEMPT_RT 基础Linux + Xenomai/COBALT
SCHED_TP, SCHED_BATCH, SCHED_IDLE S S S
SCHED_FIFO, SCHED_RR N N N
SCHED_FIFO N P P
SCHED_TP, SCHED_SPORADIC, and SCHED_QUOTA - - X

注释:

  • S = 标准调度策略
  • N = PREEMPT 补充调度策略
  • P = PREEMPT_RT 补充调度策略
  • X = XENOMAI 补充调度策略

在Cobalt中设置高分辨率计时器线程

MetaOS 支持使用X86 硬件定时器,以基于高优先级 实时任务中断创建时间间隔:

  • [host-timer/x] 和 [watchdog] 被多路复用为多个软件可编程定时器,并暴露给 Cobalt
  • timerfd_handler POSIX 定时器 API 从用户空间调用
  • clock_nanosleep() 通过高精度定时器硬件卸载实现线程精确唤醒
  • Linux 用户空间文件系统接口允许报告 Cobalt 定时器信息:

通过下面的命令查看Cobalt计时器信息:

shell 复制代码
cat /proc/xenomai/timer/coreclk

深入解析 Cobalt 的补充调度算法

在实时系统开发中,调度算法的选择和应用对系统的性能和实时性至关重要。XenomaiCobalt 实时内核为开发者提供了多种调度策略,以满足不同应用场景的需求。将深入探讨 Cobalt 的补充调度算法,包括 SCHED_QUOTASCHED_TPSCHED_WEAKSCHED_SPORADIC,并详细解释它们的工作原理、应用场景以及如何在实际开发中有效地利用这些策略。

在实时系统中,任务的调度需要考虑到多种因素,如任务的优先级、执行时间、资源使用等。传统的调度策略,如 SCHED_FIFOSCHED_RR,在某些场景下可能无法满足复杂的调度需求。为此,Cobalt 提供了多种补充调度策略,帮助开发者更灵活地管理系统中的线程和资源。

SCHED_QUOTA 调度策略

SCHED_QUOTA 是一种基于配额(Quota)的调度策略,它通过限制线程在固定的时间周期内的 CPU 使用量,来实现对线程组的资源控制。在该策略下,线程被分配到不同的线程组(Thread Group),每个组在全局配额周期内被分配一定的运行时间配额(以百分比表示)。在组内,所有线程遵循 SCHED_FIFO 策略。

工作原理

全局配额周期(Global Quota Period):这是一个固定的时间周期,例如 1 秒(默认),整个系统的配额管理都基于这个周期。

线程组配额:每个线程组被分配一定比例的运行时间,例如 35%、25% 等。

线程执行

  • 当一个属于 SCHED_QUOTA 策略的线程获得 CPU 资源时,其消耗的时间会被计入所属线程组的配额。
  • 当线程组的总消耗时间达到其配额上限时,该组内的所有线程将被暂停,直到下一个全局配额周期开始。
  • 在新的周期开始时,每个线程组的配额会被重置,线程可以继续运行。
示例说明

假设系统中有 5 个线程组,每个组的配额和全局周期如下:

SCHED_QUOTA 调度策略示例图

  • 全局配额周期:1 秒
  • 线程组配额:
    • 组 1:35%(350 毫秒)
    • 组 2:25%(250 毫秒)
    • 组 3:15%(150 毫秒)
    • 组 4:10%(100 毫秒)
    • 组 5:5%(50 毫秒)

在每个全局周期内,各线程组可以在其配额内运行线程。当组内所有线程的总运行时间达到配额后,该组将被暂停,等待下一个周期。

运行时间预算和峰值配额

运行时间预算:在每个全局周期开始时,线程组获得其完整的配额。如果该组在当前周期未完全消耗其配额,剩余的预算会被累积到下一个周期,直到达到定义的峰值配额(Peak Quota)。

峰值配额:这是线程组可累积的最大配额。当累积的预算超过峰值配额时,超出的部分会在后续多个周期内逐步消耗。

应用场景

  • 资源限制:需要限制某些线程组对 CPU 资源的使用,以防止资源被少数线程过度占用。
  • 服务质量保障:为关键任务分配更高的配额,确保其在系统中获得足够的 CPU 时间。

SCHED_TP 调度策略

SCHED_TP 策略实现了一种称为**时间分区(Temporal Partitioning)**的机制,它通过将 CPU 时间划分为固定的时间窗口,确保不同线程组在时间上不发生重叠地执行。

工作原理

主时间帧(Global Time Frame):一个固定的、重复的全局时间周期。

次要帧(Secondary Frames):主时间帧被划分为多个次要帧(时间窗口),每个次要帧具有固定的持续时间和相对于主时间帧的偏移量。

分区(Partitions):每个次要帧分配给一个特定的线程组(分区)。在次要帧的时间窗口内,只有该分区内的线程被允许执行。

线程执行

  • 当一个次要帧开始时,其对应分区内的线程可以开始执行。
  • 当次要帧结束时,该分区内的线程被暂停,切换到下一个分区的线程执行。
  • 当所有次要帧执行完毕后,主时间帧重新开始,循环往复。
示例说明

假设主时间帧为 100 毫秒,被划分为 5 个次要帧:

  • 次要帧 1:持续时间 30 毫秒,分区 A
  • 次要帧 2:持续时间 20 毫秒,分区 B
  • 次要帧 3:持续时间 10 毫秒,分区 C
  • 次要帧 4:持续时间 20 毫秒,分区 D
  • 次要帧 5:持续时间 10 毫秒,分区 D

在每个次要帧内,只有对应分区内的线程被调度执行。这样可以确保不同分区的线程在时间上严格隔离,防止相互干扰。

SCHED_TP 调度策略示例图

应用场景

  • 安全关键系统:需要严格的时间隔离,防止不同任务之间的干扰。
  • 实时性保障:确保高优先级任务在指定的时间窗口内得到执行。

SCHED_WEAK 调度策略

SCHED_WEAK 策略用于实现弱调度(Weak Scheduling),其主要目的是让线程能够与实时线程进行同步,但不与实时线程竞争 CPU 资源。

工作原理

  • 线程属性:SCHED_WEAK 类的线程具有较低的优先级,不会抢占实时线程。
  • 模式切换:当 SCHED_WEAK 线程在 Cobalt 系统调用返回后,会自动离开实时域,回到非实时模式。
  • 同步机制:SCHED_WEAK 线程可以使用 Cobalt 提供的同步原语(如互斥锁、条件变量)与实时线程进行同步。

应用场景

  • 辅助线程:执行非关键的后台任务,但需要与实时线程共享数据或进行同步。
  • 资源监控:收集系统状态、日志记录等,不影响实时任务的执行。

SCHED_SPORADIC 调度策略

SCHED_SPORADIC零星调度策略通常用于对给定时间段内的线程执行时间提供上限。在零星调度下,线程的优先级可以在前台(正常优先级)与后台(低优先级)之间动态振荡。与 FIFO 调度一样,使用零星调度的线程会继续执行,直到它被更高优先级的线程阻塞或抢占。与自适应调度一样,使用零星调度的线程的优先级会降低,但使用零星调度可以更精确地控制线程的行为。

零星调度策略支持的不规则条件:

初始预算(C):线程在被降级为低优先级 (L) 之前被允许以正常优先级 (N) 执行的时间量。

低优先级(L):线程将降级到的优先级。线程在后台时以这个较低的优先级 (L) 执行,在前台时以正常优先级 (N) 运行。

补充期(T):线程被允许消耗其执行预算的时间段。为了安排补充操作,POSIX 实现还使用此值作为线程变为 READY 的时间偏移量。

待处理补充的最大数量:此值限制可以进行的补充操作的数量,从而限制不规则调度策略消耗的系统开销。

执行过程

  1. 初始运行:线程以正常优先级(N)开始执行,预算为 10 毫秒。
  2. 预算消耗:线程运行 3 毫秒后被阻塞,剩余预算为 7 毫秒。
    • 这 3 毫秒的运行时间被安排在下一个补充期结束时补充,即在 40 毫秒后。
  3. 唤醒继续:线程在 6 毫秒时被唤醒,继续以正常优先级运行,消耗剩余的 7 毫秒预算。
    • 这 7 毫秒的运行时间被安排在第二个补充期结束时补充,即在 46 毫秒后。
  4. 预算耗尽:预算耗尽后,线程的优先级降低为低优先级(L)。
  5. 补充预算:在补充期(T)结束后,线程的预算被补充,优先级恢复为正常优先级(N)。
  6. 循环执行:线程继续在两个优先级之间切换,按照预算和补充期的设定执行。

示例图解

SCHED_SPORADIC 调度策略示例图

时间轴:

  • 0 ms:线程开始执行,预算为 10 ms,优先级为 N。
  • 3 ms:线程阻塞,消耗了 3 ms 预算,剩余 7 ms。3 ms 的预算将在 40 ms 后补充。
  • 6 ms:线程被唤醒,继续执行,消耗剩余的 7 ms 预算。7 ms 的预算将在 46 ms 后补充。
  • 13 ms:预算耗尽,线程优先级降为 L。
  • 40 ms:第一个补充期结束,补充 3 ms,优先级升为 N。
  • 43 ms:消耗补充的 3 ms 预算,优先级降为 L。
  • 46 ms:第二个补充期结束,补充 7 ms,优先级升为 N。
  • 53 ms:消耗补充的 7 ms 预算,优先级降为 L。

这样线程将继续在其两个优先级之间振荡,以一种受控的、可预测的方式为系统中的非周期性事件提供服务。

应用场景

  • 非周期性事件处理:需要对突发事件进行处理,但又要限制线程对系统资源的占用。
  • 资源保护:防止线程过度占用 CPU,影响其他实时任务的执行。
最近修改: 2025-07-24