了解 RHEL for Real Time


Red Hat Enterprise Linux for Real Time 8

RHEL for Real Time 内核简介

Red Hat Customer Content Services

摘要

了解调整 RHEL for Real Time 内核的基本概念和相关参考,以保持低延迟和对延迟敏感应用程序的响应时间。

对红帽文档提供反馈

我们感谢您对我们文档的反馈。让我们了解如何改进它。

通过 JIRA 提交反馈(需要帐户)

  1. 登录到 JIRA 网站。
  2. 单击顶部导航栏中的 Create
  3. Summary 字段中输入描述性标题。
  4. Description 字段中输入您的改进建议。包括到文档相关部分的链接。
  5. 点对话框底部的 Create

第 1 章 RHEL for Real Time 的硬件平台

在设置实时环境时,正确配置硬件会扮演重要角色,因为硬件会影响您的系统运行方式。并非所有硬件平台都能够实时启用调优。在进行调优前,您必须确保潜在的硬件平台具有实时功能。

硬件平台因供应商而异。您可以使用硬件延迟检测器(hwlatdetect)程序测试和验证硬件是否适合性。程序控制延迟 detector 内核模块,并有助于检测底层硬件或固件行为造成的延迟。

完成低延迟操作所需的任何调整步骤。有关降低低延迟问题并改进调整的说明,请参阅厂商文档。

先决条件

  • 已安装 RHEL-RT 软件包。
  • 完成低延迟操作所需的任何调整步骤。有关减少或删除任何系统管理中断(SMI)的说明,请参阅厂商文档,使系统移至系统管理模式(SMM)。

    警告

    您必须完全禁用系统管理中断(SMI),因为它可能会导致严重的硬件故障。

1.1. 处理器内核

实时处理器内核是物理中央处理单元(CPU),它执行机器代码。套接字是处理器和计算机的主板之间的连接。套接字是处理器要放入的主板上的位置。有两组处理器:

  • 使用一个可用内核占用一个插槽的单一核心处理器。
  • 四核处理器会占用有四个可用内核的一个插槽。

在设计实时环境时,请注意可用内核的数量、核心中的缓存布局以及内核物理连接的方式。

当有多个内核可用时,请使用线程或进程。程序在没有使用这些构造的情况下编写,一次在一个处理器上运行。多核平台通过使用不同内核进行不同类型的操作提供优势。

缓存

缓存对整个处理时间和确定性造成显著影响。通常,应用的线程需要同步对共享资源的访问,如数据结构。

使用 tuna 命令行工具(CLI),您可以确定缓存布局并将交互线程绑定到内核,以便它们共享缓存。缓存共享通过确保 mutual 排除原语(mutex、条件变量或类似)和数据结构使用相同的缓存来减少内存错误。

互连

增加系统上的内核数可能会导致互连上的冲突需求。这样就可以确定互连拓扑,以帮助检测实时系统中内核之间发生冲突。

现在,许多硬件供应商提供内核与内存之间的透明互连网络,称为非统一内存访问(NUMA)架构。

NUMA 是多处理中使用的系统内存设计,内存访问时间取决于处理器的内存位置。使用 NUMA 时,处理器可以比非本地内存更快地访问自己的本地内存,如处理器之间共享的其他处理器或内存的内存。在 NUMA 系统上,了解互连拓扑有助于将经常在相邻内核中通信的线程放置。

tasksetnumactl 实用程序决定了 CPU 拓扑。taskset 定义没有 NUMA 资源的 CPU 关联性,如内存节点和 numactl 控制进程和共享内存的 NUMA 策略。

第 2 章 RHEL for Real Time 的内存管理

实时系统使用虚拟内存系统,其中由用户空间应用程序引用的地址转换为物理地址。翻译是通过底层计算系统中页表和地址转换硬件的组合来实现的。在程序和实际内存之间转换机制的一个优点是,操作系统在需要或 CPU 请求时可以交换页面。

实时,到从存储到主内存的交换页面,之前使用的页表条目被标记为无效。因此,即使在普通内存压力下,操作系统也可以从一个应用程序中检索页面并将其提供给另一个应用程序。这可能导致无法预计的系统行为。

内存分配实现包括要求分页机制和内存锁定(mlock())系统调用。

注意

在不同缓存和 NUMA 域中共享 CPU 的数据信息可能会导致流量问题和瓶颈。

在编写多线程应用程序时,请在设计数据组成前考虑机器拓扑。topology 是内存层次结构,包括 CPU 缓存和非统一内存访问(NUMA)节点。

2.1. 需求分页

需求分页与页交换的分页系统类似。当需要或 CPU 需求时,系统会加载存储在次要内存中的页面。程序通过处理器中的地址转换机制生成的所有内存地址。然后,地址从特定于进程的虚拟地址转换为物理内存地址。这称为虚拟内存。转换机制中的两个主要组件是页表和翻译查找缓冲区(TLB)

页面表

页面表是物理内存中的多级别表,其中包含虚拟到物理内存的映射。这些映射可由处理器中的虚拟内存转换硬件读取。

带有分配物理地址的页表条目称为常驻工作集。当操作系统需要为其他进程释放内存时,它可以从常驻工作集中交换页面。在交换页面时,对该页面中的虚拟地址的任何引用都会创建一个页面错误,并导致页面重新分配。

当系统在物理内存上非常低时,交换进程开始为 thrash,从而持续窃取进程中的页面,并且永远不会允许进程完成。您可以通过在 /proc/vmstat 文件中查找 pgfault 值来监控虚拟内存统计信息。

翻译查找缓冲区

translation Lookaside Buffers (TLB)是虚拟内存转换的硬件缓存。任何带有 TLB 的处理器内核都会并行检查 TLB,启动页表条目的内存读取。如果虚拟地址的 TLB 条目有效,则会中止内存,并且 TLB 中的值用于地址转换。

TLB 在引用的本地原则上运行。这意味着,如果代码在大量时间内(如循环或调用相关功能)保留其中一个内存,则 TLB 引用可以避免主内存进行地址转换。这可显著提高处理时间。

在编写确定和快速代码时,请使用维护参考本地功能。这可能意味着使用循环而不是递归。如果递归不可避免,请将递归调用放在函数的末尾。这被称为 tail-recursion,它使代码在相对较小的内存中工作,并避免从主内存调用表转换。

2.2. 主和次要页面错误

RHEL for Real Time 通过将物理内存分解为名为页面的块来分配内存,然后将它们映射到虚拟内存。当进程需要未映射或在内存中不再可用的特定页面时,会实时发生故障。因此,故障基本上意味着在 CPU 需要时页面不可用。当进程遇到页面错误时,所有线程都会冻结,直到内核处理这个故障为止。解决此问题的方法有多种,但最好的解决方法是调整源代码以避免页面错误。

次要页面错误

当进程在初始化部分内存前试图访问一部分内存时,会实时进行次要页面错误。在这种情况下,系统会执行操作来填充内存映射或其他管理结构。次要页面错误的严重性可能取决于系统负载和其他因素,但它们通常很短,并有可忽略的影响。

主页面错误

当系统必须将内存缓冲区与属于其他进程的磁盘、内存不足页面或者将任何其他输入输出(I/O)活动设置为可用内存时,会发生实时主要故障。当处理器引用没有分配物理页面的虚拟内存地址时,会出现这种情况。对空页面的引用会导致处理器执行错误,并指示内核代码分配一个页面,这会显著提高延迟。

实时,当应用程序显示性能时,检查 /proc/ 目录中与页面错误相关的进程信息是很有帮助的。对于特定的进程标识符(PID),您可以使用 cat 命令查看 /proc/PID/stat 文件以了解以下相关条目:

  • 字段 2:可执行文件名称。
  • 字段 10:次要页面错误的数量。
  • 字段 12:主页面错误的数量。

以下示例演示了使用 cat 命令查看页面错误并使用 pipe 功能,只返回 /proc/PID/stat 文件的第二行、第十和第十二行:

# cat /proc/3366/stat | cut -d\ -f2,10,12
  (bash) 5389 0
Copy to Clipboard Toggle word wrap

在示例输出中,PID 3366 的进程是 bash,它具有 5389 次要页面错误,没有主要页面错误。

2.3. mlock() system calls

内存锁定(mlock ())系统调用允许调用进程锁定或解锁指定范围的地址空间,并防止 Linux 将锁定的内存分页到交换空间。将物理页面分配给 page 表条目后,对该页面的引用相对较快。内存锁定系统调用属于 mlock ()m unlock () 类别。

mlock ()m unlock () 系统调用锁定和解锁指定的进程地址页面。成功后,指定范围内的页面将保持驻留在内存中,直到 m unlock () 系统调用解锁页面。

mlock ()m unlock () 系统调用获取以下参数:

  • addr: 指定地址范围的开头。
  • len :指定地址空间的长度(以字节为单位)。

成功后,mlock ()m unlock () 系统调用返回 0。如果出现错误,它们会返回 -1,并设置 errno 来指示错误。

mlockall ()m unlockall () 系统调用锁定或解锁所有程序空间。

注意

mlock () 系统调用无法确保程序没有页 I/O。它确保数据保留在内存中,但不能确保它停留在同一个页面上。move_pages 和 memory compactors 等其他功能都可以移动数据,而不考虑使用 mlock ()

内存锁定会根据页面进行,而不是堆栈。如果两个动态分配的内存片段共享同一页面由 mlock ()mlockall () 锁定两次,则它们通过使用单个 m lock ()m unlockall () 系统调用来解锁。因此,务必要了解应用解锁的页面,以避免双锁定或单锁定问题。

以下是缓解双锁定或单锁定问题的两个最常见的临时解决方案:

  • 跟踪已分配和锁定的内存区域,并创建一个 wrapper 功能,在解锁页面前验证页面分配的数量。这是设备驱动程序中使用的资源计数原则。
  • 根据页面大小进行内存分配,并避免在页面上进行双锁定。

2.4. 共享库

用于 Real Time 的 RHEL 称为动态共享对象(DSO),是预编译代码块的集合,称为功能。这些功能可在多个程序中重复使用,它们在运行时或编译时加载。

Linux 支持以下两个库类:

  • 动态或共享库: 作为可执行文件之外的单独文件存在。这些文件加载到内存中,并在运行时进行映射。
  • 静态库:在编译时静态链接到程序的文件。

ld.so 动态链路器加载程序所需的共享库,然后执行代码。DSO 函数加载内存中的库一次,然后多个进程可以通过映射到进程的地址空间来引用对象。您可以使用 LD_BIND_NOW 变量将动态库配置为在编译时加载。

在程序初始化前评估符号可以提高性能,因为当内存页面位于外部磁盘上时评估应用程序运行时可能会导致延迟。

2.5. 共享内存

在 RHEL for Real Time 中,共享内存是在多个进程间共享的内存空间。通过使用程序线程,在一个进程上下文中创建的所有线程都可以共享相同的地址空间。这使得线程可以访问所有数据结构。使用 POSIX 共享内存调用,您可以配置进程来共享地址空间的一部分。

您可以使用以下支持的 POSIX 共享内存调用:

  • shm_open () :创建并打开新的或打开现有的 POSIX 共享内存对象。
  • shm_unlink () :取消链接 POSIX 共享内存对象。
  • mmap () :在调用进程的虚拟地址空间中创建一个新的映射。
注意

使用一组 System V IPC shmem () 调用的两个进程间共享内存区域的机制已弃用,在 RHEL for Real Time 中不再支持。

第 3 章 RHEL for Real Time 的硬件中断

实时系统在其操作过程中收到许多中断,包括定期执行维护和系统调度决策的半常规"timer"中断。系统也可能收到特殊中断,如不可屏蔽中断(NMI)和系统管理中断(SMI)。设备使用硬件中断来指示需要注意的系统物理状态的变化。例如,硬盘表示它已读取一系列数据块,或者在网络设备处理包含网络数据包的缓冲区时。

当实时发生中断时,系统会停止活动程序,并执行中断处理程序。

在实时中,硬件中断由中断数引用。这些数字会映射到创建中断的硬件片段。这可让系统监控哪个设备创建中断并发生时间。当实时发生中断时,系统会停止活动程序并执行中断处理程序。处理程序抢占了其他正在运行的程序和系统活动。这会减慢整个系统并创建延迟。

RHEL for Real Time 修改处理中断的方式,以提高性能和降低延迟。使用 cat /proc/interrupts 命令,您可以打印输出来查看发生的硬件中断类型、收到的中断数量、中断的目标 CPU 以及生成中断的设备。

3.1. Level-signaled 的中断

在实时中,level-signaled 中断使用提供自愿转换的专用中断行。设备控制器通过在中断请求行中模拟信号来引发中断。中断行发送两个 voltages 中的一个来代表二进制 1 或二进制 0。

当中断信号被行发送时,它会一直处于该状态,直到 CPU 重置为止。CPU 执行状态保存、捕获中断并分配中断处理程序。中断处理程序决定了中断的原因,通过执行必要的服务清除中断,并恢复设备的状态。级别签名的中断更为可靠,并且支持多个设备,尽管它们比较复杂。

3.2. 消息信号中断

在实时中,许多系统都使用消息签名中断(MSI),它将信号发送为数据包或基于消息的电网上的专用消息。这种总线的常见示例是 Peripheral Component Interconnect Express (PCI Express 或 PCIe)。这些设备传输消息类型,PCIe 主机控制器将解析为中断消息。然后,主机控制器会将 上的消息发送到 CPU。

在实时(根据硬件)中,PCIe 系统执行以下操作之一:

  • 使用 PCIe 主机控制器和 CPU 之间的专用中断行发送信号。
  • 通过 CPU HyperTransport 总线发送消息。

在实时中,PCIe 系统还可在传统模式下操作,在实施传统中断行以便支持旧操作系统或引导 Linux 内核时,在内核命令行中使用 pci=nomsi 选项 pci=nomsi。

3.3. 非屏蔽中断

在实时中,不可屏蔽中断是系统中标准中断的硬件中断无法忽略。NMI 的优先级高于掩码中断。NMI 会出现信号,注意不可恢复的硬件错误。

在实时中,某些系统也使用 NMI 作为硬件监控器。当处理器收到 NMI 时,它通过调用中断向量指向的 NMI 处理程序立即处理 NMI。如果满足某些条件,如在指定时间内没有触发中断,则 NMI 处理程序会发出警告并提供有关问题的调试信息。这有助于识别和阻止系统锁定。

实时中断是硬件中断,通过在中断掩码注册位设置位来忽略这些中断。CPU 可以在关键处理过程中临时忽略可屏蔽中断。

3.4. 系统管理中断

实时,系统管理中断(SMI)提供扩展功能,如传统的硬件设备仿真,也可用于系统管理任务。SMI 与不可屏蔽中断(NMI)类似,它们在使用特殊电脑信号行且通常不可屏蔽。当发生 SMI 时,CPU 会进入系统管理模式(SMM)。在这个模式中,执行特殊的低级处理程序来处理 SMI。SMM 通常直接从系统管理固件提供,通常是 BIOS 或 EFI。

实时 SMI 最常用于提供传统硬件模拟。一个常见的例子是模拟一个 diskette 驱动器。如果没有附加 diskette 驱动器,操作系统会尝试访问 diskette 并触发 SMI。在这种情况下,处理程序会为操作系统提供模拟设备。然后,操作系统会将模拟视为旧设备。

在实时中,SMI 可能会对系统造成负面影响,因为它们无需直接参与操作系统。编写较差的 SMI 处理例程可能会消耗大量 CPU 时间,操作系统可能无法抢占处理程序。这可在其他调优时创建定期高延迟和高度响应的系统。由于供应商可以使用 SMI 处理程序来管理 CPU 温度和 fan 控制,因此可能无法禁用它们。在这种情况下,您必须通知供应商使用这些中断时发生的问题。

在实时中,您可以使用 hwlatdetect 程序隔离 SMI。它包括在 rt-tests 软件包中。这个工具会测量 SMI 处理例程使用 CPU 的时间周期。

3.5. 高级可编程中断控制器

Intel 公司开发的高级可编程中断控制器(APIC)提供了以下功能:

  • 处理大量中断,将每个中断路由到特定的 CPU 集合。
  • 支持 CPU 间的通信,无需多个设备来共享单个中断行。

实时 APIC 代表一系列设备和技术,它们以可扩展、可管理的方式生成、路由和处理大量硬件中断。它使用在每个系统 CPU 中内置的本地 APIC 的组合以及直接连接到硬件设备的输入/输出 APIC。

在实时时,当硬件设备生成中断时,连接的 I/O APIC 会检测到并路由系统 APIC 总线到特定 CPU 的中断。操作系统知道 IO-APIC 连接到设备,并在该设备中中断行。高级配置和电源接口区分系统描述表(ACPI DSDT)包括有关主机系统主板和外围组件的特定 wiring 的信息,以及设备提供有关可用中断源的信息。这两个数据集合一起提供有关总体中断层次结构的信息。

RHEL for Real Time 支持使用层次结构中连接的系统 APIC 基于 APIC 的中断管理策略,并通过负载均衡的方式向 CPU 提供中断,而不是针对特定 CPU 或一组 CPU。

第 4 章 用于 Real Time 流程和线程的 RHEL

操作系统中的 RHEL for Real Time key factors 是最小中断延迟和最小线程切换延迟。虽然所有程序都使用线程和进程,但与标准 Red Hat Enterprise Linux 相比,对于 Real Time 的 RHEL 会以不同的方式处理它们。

实时,使用并行有助于提高任务执行和延迟效率。并行性是,当多个任务或多个子任务同时使用 CPU 的多核基础架构同时运行时。

4.1. Process

实时进程在最简单的方面是执行中的程序。术语进程指的是独立地址空间,可能包含多个线程。当开发了多个进程在一个地址空间内运行时,Linux 转而成一个与其他进程共享地址空间的进程结构。只要进程数据结构很小,就可以正常工作。

UNIX® 风格的进程结构包含:

  • 虚拟内存的地址映射.
  • 执行上下文(PC、堆栈、注册)。
  • 州和记帐信息。

实时中,每个进程通过一个线程启动,通常称为父线程。您可以使用 fork () 系统调用从父线程创建额外的线程。fork () 创建新的子进程,与父进程相同,但新进程标识符除外。子进程独立于创建进程运行。可以同时执行父进程和子进程。fork ()exec () 系统调用之间的区别在于,fork () 启动一个新进程,它是父进程的副本,exec () 将当前进程替换为新进程镜像。

在实时中,fork () 系统调用在成功时返回子进程的进程标识符,父进程返回非零值。出错时,它会返回错误号。

4.2. 线程

在实时中,进程中可以存在多个线程。进程的所有线程共享其虚拟地址空间和系统资源。线程是一个可调度的实体,包含:

  • 程序计数器(PC)。
  • 注册上下文。
  • 堆栈指针。

在实时中,以下是创建并行性的潜在机制:

  • 使用 fork ()exec () 函数调用来创建新进程。fork () 调用会为进程创建一个名为 且具有唯一标识符的进程的精确重复。
  • 使用 Posix 线程(pthreads) API 在已经运行的进程内创建新的线程。

在对实时线程进行分叉前,您必须评估组件交互级别。当组件独立于另一个或较少交互时,创建新的地址空间并将其作为新进程运行是有益的。当组件需要共享数据或经常通信时,在一个地址空间中运行线程会更高效。

实时中,fork () 系统调用在成功时返回零值。出错时,它会返回错误号。

第 5 章 RHEL for Real Time 的应用程序时间戳

执行频繁 时间戳 的应用程序会受到读取时钟的 CPU 成本的影响。用于读取时钟的高成本和时间可能会对应用程序性能造成负面影响。

您可以通过选择具有读取机制的硬件时钟比默认时钟快,从而减少读取时钟的成本。

在 RHEL for Real Time 中,可以通过使用带有 clock_gettime () 函数的 POSIX 时钟来获得进一步的性能,以最低的 CPU 成本生成时钟读取。

在使用硬件时钟高读取成本的系统中,这些好处更明显。

5.1. 硬件时钟

在多处理器系统中发现的多个时钟源实例,如非统一内存访问(NUMA)和 Symmetric 多处理(SMP),并在自身间交互,以及它们对系统事件的响应方式,如 CPU 频率扩展或进入能源经济模式,确定它们是否适合实时内核的时钟源。

首选时钟源是时间戳计数器(TSC)。如果 TSC 不可用,则 Highrecision Event Timer (HPET)是第二个最佳选项。但是,并非所有系统都有 HPET 时钟,一些 HPET 时钟可能不可靠。

如果没有 TSC 和 HPET,其他选项包括 ACPI Power Management Timer (ACPI_PM)、Programmable Interval Timer (PIT)和 Real Time Clock (RTC)。最后两个选项需要昂贵,读取或具有低分辨率(时间粒度),因此它们是与实时内核一起使用的子选择。

5.2. POSIX 时钟

POSIX 是实施和代表时间源的标准。您可以将 POSIX 时钟分配给应用程序,而不影响系统中的其他应用程序。这与内核选择并在系统中实施的硬件时钟不同。

用于读取给定 POSIX 时钟的功能是 clock_gettime (),它在 < time.h > 定义。内核与 clock_gettime () 是系统调用。当用户进程调用 clock_gettime () 时:

  1. 对应的 C 库(glibc)调用 sys_clock_gettime () 系统调用。
  2. sys_clock_gettime () 执行请求的操作。
  3. sys_clock_gettime() returns the result to the user program.

但是,从用户应用程序切换到内核的上下文会有 CPU 成本。尽管这种成本非常低,如果操作重复了数以千计的时间,但累计成本可能会对应用程序的整体性能产生影响。为了避免上下文切换到内核,从而可以更快地读取时钟,支持 CLOCK_MONOTONIC_COARSECLOCK_REALTIME_COARSE POSIX 时钟,格式为虚拟动态共享对象(VDSO)库功能。

使用其中一个 _COARSE 时钟变体执行 clock_gettime () 执行的时间读取,不需要内核干预,完全在用户空间中执行。这带来了显著的性能提升。读取 _COARSE 时钟的时间读取具有毫秒(ms)解析,这意味着不会记录时间间隔小于 1 ms。POSIX 时钟的 _COARSE 变体适合可容纳数毫秒时钟解析的任何应用程序。

注意

要将读取 POSIX 时钟的成本和解析与没有 _COARSE 前缀的成本和解析进行比较,请参阅 RHEL for Real Time 参考指南

5.3. clock_gettime() function

以下代码演示了使用带有 CLOCK_MONOTONIC_COARSE POSIX 时钟的 clock_gettime () 函数的代码示例:

#include <time.h>
main()
{
	int rc;
	long i;
	struct timespec ts;

	for(i=0; i<10000000; i++) {
		rc = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
	}
}
Copy to Clipboard Toggle word wrap

您可以改进上面的示例,添加检查来验证 clock_gettime() 的返回值,验证 rc 变量的值,或确保 ts 结构的内容被信任。

注意

clock_gettime () 手册页提供有关编写更可靠的应用程序的更多信息。

重要

使用 clock_gettime() 函数的程序必须通过将 -lrt 添加到 gcc 命令行来与 rt 库相关联。

$ gcc clock_timing.c -o clock_timing -lrt

第 6 章 RHEL for Real Time 的调度策略

实时调度程序是决定要运行的线程的内核组件。每个线程都有一个关联的调度策略和静态调度优先级,称为 sched_priority。调度是抢占的,因此当具有较高静态优先级的线程准备好运行时,当前运行的线程将停止。然后,运行的线程会返回其静态优先级的 waitlist

所有 Linux 线程都有以下调度策略之一:

  • SCHED_OTHERSCHED_NORMAL :是默认策略。
  • SCHED_BATCH :与 SCHED_OTHER 类似,但使用增量情况。
  • SCHED_IDLE : 是优先级低于 SCHED_OTHER 的策略。
  • SCHED_FIFO : 是第一个 in 和 first out 实时策略。
  • SCHED_RR :是循环实时策略。
  • SCHED_DEADLINE: 是一种调度程序策略,根据作业期限对任务进行优先级排序。最早绝对截止时间的作业会首先运行。

6.1. 调度程序策略

实时线程的优先级高于标准线程。策略具有调度优先级值,范围从最小值 1 到最大值 99。

以下策略对实时至关重要:

  • SCHED_OTHERSCHED_NORMAL 策略

    这是 Linux 线程的默认调度策略。它有一个动态优先级,系统会根据线程的特征更改。SCHED_OTHER 线程在 -20 之间具有 nice 值,即最高优先级和 19,这是最低优先级。SCHED_OTHER 线程的默认 nice 值为 0。

  • SCHED_FIFO 策略

    带有 SCHED_FIFO 的线程会高于 SCHED_OTHER 任务。SCHED_FIFO 不使用 nice 值,而是使用 1 之间的固定优先级,即最低和 99 (最高)。优先级为 1 的 SCHED_FIFO 线程始终首先通过 SCHED_OTHER 线程进行调度。

  • SCHED_RR 策略

    SCHED_RR 策略与 SCHED_FIFO 策略类似。同等优先级的线程以轮循方式调度。SCHED_FIFOSCHED_RR 线程运行直到发生以下事件之一:

    • 线程进入睡眠或等待事件。
    • 较高优先级的实时线程可以运行。

      除非发生上述事件,否则线程在指定的处理器上无限期运行,而较低优先级线程保留在队列中等待运行。这可能导致系统服务线程处于常部,并阻止交换,并导致文件系统数据清除失败。

  • SCHED_DEADLINE 策略

    SCHED_DEADLINE 策略指定计时要求。它根据任务的截止时间调度每个任务。最早期限(EDF)调度的任务首先运行。

    内核需要 运行时的deadline …​period 为 true。所需选项之间的关系是 runtimedeadline …​period

6.2. SCHED_DEADLINE 策略的参数

每个 SCHED_DEADLINE 任务都按 句点运行时和 截止时间 参数表示。这些参数的值是纳秒的整数。

Expand
表 6.1. SCHED_DEADLINE 参数
参数描述

周期

period 是实时任务的激活模式。

例如,如果视频处理任务每秒有 60 个帧来处理,则每 16 毫秒生成一个新帧。因此,周期 为 16 毫秒。

runtime

runtime 是分配给任务而分配的 CPU 执行时间,以生成输出。实时执行上限,也称为"Worst Case Execution Time" (WCET)是 运行时

例如,如果视频处理工具可以采用最糟糕的情况,则 5 毫秒来处理镜像,则 运行时 为 5 毫秒。

deadline

deadline 是要生成输出的最长时间。

例如,如果任务需要在十毫秒内提供已处理帧,则 截止时间 为 10 毫秒。

6.3. 配置 SCHED_DEADLINE 参数

Red Hat Enterprise Linux 中的 sched_deadline_period_max_ussched_deadline_period_min_us 参数是 SCHED_DEADLINE 调度策略的内核可调参数。这些参数通过使用此实时调度类来控制任务的最大允许周期(以微秒为单位)。

sched_deadline_period_max_ussched_deadline_period_min_us 一起工作,为 SCHED_DEADLINE 任务的 period 值定义一个可接受的范围。

  • min_us 可防止可能使用过量资源的高频率任务。
  • max_us 会阻止极长的任务,它们可能会导致其他任务的性能。
注意

使用参数的默认配置。如果需要更改参数的值,您必须在实时环境中配置自定义值前测试它们。

参数的值以微秒为单位。例如,1 秒等于 100000 微秒。

先决条件

  • 您的系统必须具有 root 权限。

流程

  1. 使用其中一个 sysctl 命令临时设置所需的值。

    • 要使用 sched_deadline_period_max_us 参数,请运行以下命令:

      # sysctl -w kernel.sched_deadline_period_max_us=2000000
      Copy to Clipboard Toggle word wrap
    • 要使用 sched_deadline_period_min_us 参数,请运行以下命令:

      # sysctl -w kernel.sched_deadline_period_min_us=100
      Copy to Clipboard Toggle word wrap
  2. 永久设置值。

    • 对于 max_us,编辑 /etc/sysctl.conf 并添加以下行:

      kernel.sched_deadline_period_max_us = 2000000
      Copy to Clipboard Toggle word wrap
    • 对于 min_us,请编辑 /etc/sysctl.conf 并添加以下行:

      kernel.sched_deadline_period_min_us = 100
      Copy to Clipboard Toggle word wrap
  3. 应用更改:

    # sysctl -p
    Copy to Clipboard Toggle word wrap

验证

  • 验证 max_us 的自定义值:

    $ cat /proc/sys/kernel/sched_deadline_period_max_us
    2000000
    Copy to Clipboard Toggle word wrap
  • 验证 min_us 的自定义值:

    $ cat /proc/sys/kernel/sched_deadline_period_min_us
    100
    Copy to Clipboard Toggle word wrap

第 7 章 RHEL for Real Time 中的关联性

实时,系统中的每个线程和中断源都具有处理器关联属性。操作系统调度程序使用此信息来确定要在哪个 CPU 上运行哪些线程和中断。

实时中的 Affinity 代表位掩码,掩码中的每个位代表一个 CPU 内核。如果位设置为 1,则线程或中断可在该内核上运行;如果 0,则线程或中断不包括在内核上运行。关联性位掩码的默认值是所有的,这意味着线程或中断可以在系统中的任何内核上运行。

默认情况下,进程可以在任何 CPU 上运行。但是,可以通过更改进程的关联性来指示在预先选择的 CPU 上运行进程。子进程继承其父进程的 CPU 相关性。

一些更典型的关联性设置包括:

  • 为所有系统进程保留一个 CPU 内核,并允许应用程序在剩余内核中运行。
  • 允许同一 CPU 上的线程应用程序和给定内核线程(如网络 softirq 或驱动程序线程)。
  • 每个 CPU 上的对制作者和消费者线程。
注意

关联性设置必须与 程序一起设计,才能实现良好的行为。

7.1. 处理器关联性

默认情况下,进程可以实时运行任意 CPU。但是,您可以通过更改进程的关联性,将进程配置为在预先确定的 CPU 上运行。子进程继承其父进程的 CPU 相关性。

在系统中调优操作的实际情况是确定运行应用程序所需的内核数量,然后隔离这些内核。这可以通过 Tuna 工具来实现,也可以使用 shell 脚本修改位掩码值。

taskset 命令可用于更改进程的关联性,并修改 /proc/ filesystem 条目会更改中断的关联性。使用带有 -p--pid 选项的 taskset 命令和进程的进程标识符(PID)检查进程的关联性。

-c--cpu-list 选项显示内核的数字列表,而不是位掩码。可以通过指定要绑定特定进程的 CPU 数量来设置关联性。例如,对于之前使用 CPU 0 或 CPU 1 的进程,您可以更改关联性,使其只能在 CPU 1 上运行。除了 taskset 命令外,您还可以使用 sched_setaffinity () 系统调用来设置处理器关联性。

7.2. SCHED_DEADLINE 和 cpusets

内核的截止时间调度类(SCHED_DEADLINE)在截止时间前实施第一次调度程序(EDF),用于有有限期限的任务。它根据作业截止时间排列任务优先级:最早的绝对截止时间。除了 EDF 调度程序外,截止时间调度程序也实现了恒定的带宽服务器(CBS)。CBS 算法是一个资源保留协议。

CBS 确保每个任务在每个期间 (T) 接收其运行时间 (Q)。在任务的每个激活开始时,CBS 重新配置任务的运行时。在作业运行时,它会消耗其 运行时,如果任务超出 运行时,则任务会节流和取消调度。节流机制可防止单个任务运行超过其运行时,并有助于避免其他作业的性能问题。

实时,为了避免在 截止时间 任务的情况下过载系统,截止时间调度程序 会实施验收测试,每次任务被配置为 使用截止时间调度程序 运行时运行。接受的测试保证了 SCHED_DEADLINE 任务不使用超过 kernel.sched_rt_runtime_us/kernel.sched_rt_period_us 文件(默认为 950 ms 超过 1s)的 CPU 时间。

第 8 章 RHEL for Real Time 中的线程同步机制

同时,当两个或多个线程需要同时访问共享资源时,线程会协调使用线程同步机制。线程同步可确保一个线程一次使用共享资源。Linux 上使用的三个线程同步机制: Mutexes、Barriers 和 Condition 变量(condvars)。

8.1. Mutexes

mutex 从 mutual 排除术语衍生而来。mutual exclude 对象同步对资源的访问。它是确保一个线程一次只能获取 mutex 的机制。

mutex 算法创建对每个代码的每个部分的串行访问,因此在任何时间点上只能有一个线程执行代码。Mutexes 使用称为 mutex 属性对象的属性对象来创建。这是一个抽象对象,其中包含几个依赖于您选择的 POSIX 选项的属性。attribute 对象通过 pthread_mutex_t 变量定义。对象存储为 mutex 定义的属性。pthread_mutex_init (&my_mutex, &my_mutex_attr), pthread_mutexattr_setrobust ()pthread_mutexattr_getrobust () 功能返回 0。出错时,它们会返回错误号。

在实时中,您可以保留属性对象来初始化同一类型的更多 mutexes,也可以清理(destroy)属性对象。mutex 在任一情况下都不受影响。Mutexes 包括标准和高级 mutexes。

标准 mutexes

实时标准 mutexes 是私有、非递归、非攻击和非优先级继承能力 mutexes。初始一个 pthread_mutex_t,使用 pthread_mutex_init(&my_mutex, &my_mutex_attr) 创建一个标准的 mutex。当使用标准 mutex 类型时,您的应用程序可能无法从 pthreads API 和 RHEL for Real Time 内核提供的好处中获益。

Advanced mutexes

通过额外功能定义的 Mutexes 称为高级 mutexes。高级功能包括优先级继承、mutex 的强大行为以及共享和私有 mutexes。例如,对于强大的 mutexes,初始化 pthread_mutexattr_setrobust () 函数,设置强大的属性。同样,使用 PTHREAD_PROCESS_SHARED 属性允许任何线程在 mutex 上运行,只要线程可以访问其分配的内存。属性 PTHREAD_PROCESS_PRIVATE 设置私有 mutex。

在手动释放之前,非robust mutex 不会自动发布并保持锁定状态。

8.2. 障碍

与其他线程同步方法相比,障碍以非常不同的方式运行。障碍定义代码中所有活动线程停止的点,直到所有线程和进程达到这一障碍为止。当运行的应用程序需要确保所有线程都已完成特定任务时,可以使用障碍,然后再执行才可以继续。

barrier mutex 实时使用以下两个变量:

  • 第一个变量记录屏障的 stoppass 状态。
  • 第二个变量记录进入屏障的线程总数。

只有指定数量的线程数量达到定义的 barrier 时,barrier 才会将状态设置为 pass。当 barrier 状态设置为 通过 时,线程和进程将继续进行。pthread_barrier_init () 函数分配所需的资源以使用定义的 barrier 并使用 attr 属性对象引用的属性进行初始化。

当成功时,pthread_barrier_init ()pthread_barrier_destroy () 函数返回零值。出错时,它们会返回错误号。

8.3. condition 变量

在实时中,条件变量(condvar)是一个 POSIX 线程构造,在继续前等待执行特定条件。通常,信号条件与线程与其他线程共享的数据状态相关。例如,condvar 可用于向处理队列发送数据条目,以及等待队列中处理该数据的线程。使用 pthread_cond_init () 函数,您可以初始化条件变量。

当成功时,pthread_cond_init ()、pthread_cond_wait ()pthread_cond_signal () 函数返回零值。出错时,它会返回错误号。

8.4. mutex 类

上述 mutex 选项提供了在编写或移植应用程序时需要考虑的 mutex 类的指导。

Expand
表 8.1. mutex 选项
Advanced mutexes描述

shared mutexes

定义多个线程的共享访问,以便在给定时间获取 mutex。共享 mutexes 可以创建延迟。属性为 PTHREAD_PROCESS_SHARED

private mutexes

确保只有同一进程中创建的线程可以访问 mutex。属性为 PTHREAD_PROCESS_PRIVATE

实时优先级继承

设置优先级高于当前优先级任务的优先级级别。当任务完成后,它会释放资源,并将任务降至其原始优先级允许运行更高的优先级。属性为 PTHREAD_PRIO_INHERIT

强大的 mutexes

当拥有线程停止时,将强大的 mutexes 设置为自动释放。字符串 PTHREAD_MUTEX_ROBUST_NP 中的子字符串 NP 表示可靠的 mutexes 是非 POSIX 或不可移植的

8.5. 线程同步功能

上述功能类型列表和描述提供了用于实时内核线程同步机制的功能信息。

Expand
表 8.2. Functions
功能描述

pthread_mutexattr_init(&my_mutex_attr)

使用 attr 指定的属性启动 mutex。如果 attr 是 NULL,它会应用默认的 mutex 属性。

pthread_mutexattr_destroy(&my_mutex_attr)

销毁指定的 mutex 对象。您可以使用 pthread_mutex_init () 重新初始化。

pthread_mutexattr_setrobust()

指定 mutex 的 PTHREAD_MUTEX_ROBUST 属性。PTHREAD_MUTEX_ROBUST 属性定义了一个可在不解锁 mutex 的情况下停止的线程。以后调用自己的 mutex 会自动成功,并返回值 EOWNERDEAD,以指示之前的 mutex 所有者不再存在。

pthread_mutexattr_getrobust()

查询 mutex 的 PTHREAD_MUTEX_ROBUST 属性。

pthread_barrier_init()

分配所需的资源以使用并初始化属性对象 attr 的障碍。如果 attr 是 NULL,它会应用默认值。

pthread_cond_init()

初始化一个 condition 变量。cond 参数定义要使用 condition 变量属性对象 attr 中的属性启动的对象。如果 attr 是 NULL,它会应用默认值。

pthread_cond_wait()

阻止线程执行,直到它收到来自另一个线程的信号。另外,调用这个功能也会在阻塞前在 mutex 上释放相关的锁定。参数 cond 定义要阻止线程的 pthread_cond_t 对象。mutex 参数指定为 unblock 的 mutex。

pthread_cond_signal()

至少在指定条件变量中阻止的线程之一。参数 cond 指定使用 pthread_cond_t 对象来取消阻塞线程。

第 9 章 RHEL for Real Time 中的套接字选项

实时套接字是同一系统中两个进程(如 UNIX 域和回送设备或网络套接字等不同系统上)之间的数据传输机制。

传输控制协议(TCP)是最常见的传输协议,通常用于为需要持续通信的服务或将低优先级限制环境中的套接字实现一致的低延迟。

通过新应用程序、硬件功能和内核架构优化,TCP 必须引入新的方法来有效地处理更改。新方法可能会导致不稳定的程序行为。由于程序行为随着底层操作系统组件更改而改变,因此必须小心处理它们。

TCP 中此类行为的一个示例是发送小缓冲区的延迟。这允许将它们发送为一个网络数据包。对 TCP 缓冲区的小写入操作,并一次性发送它们,但它也可以创建延迟。对于实时应用程序,TCP_NODELAY 套接字选项禁用延迟,并在它们就绪后立即发送小写入操作。

数据传输的相关套接字选项是 TCP_NODELAYTCP_CORK

9.1. TCP_NODELAY 套接字选项

TCP_NODELAY 套接字选项禁用 Nagle 的算法。使用 setsockopt socket API 功能配置 TCP_NODELAY,当单个数据包就绪后立即发送多个小缓冲区写入。

在发送之前,通过构建连续数据包以单一数据包的形式发送多个逻辑相关的缓冲区,以实现更好的延迟和性能。或者,如果内存缓冲区是逻辑上相关但不是连续的,您可以在启用了 TCP_NODELAY 的套接字中使用 writev 创建 I/O 向量,并将其传递给内核。

以下示例演示了通过 setsockopt socket API 启用 TCP_NODELAY

int one = 1;
setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
Copy to Clipboard Toggle word wrap
注意

要有效地使用 TCP_NODELAY,请避免与逻辑上相关的缓冲区写入。使用 TCP_NODELAY 时,小的写入会使 TCP 发送多个缓冲区作为单个数据包,这可能会导致整体性能降低。

9.2. TCP_CORK 套接字选项

TCP_CORK 选项收集套接字中的所有数据包,并阻止传输它们,直到缓冲区填充到指定的限制。这使得应用能够在内核空间中构建数据包,并在禁用 TCP_CORK 时发送数据。使用 setsocketopt () 函数在套接字文件描述符上设置 TCP_CORK。在开发程序时,如果必须从文件发送批量数据,请考虑使用带有 sendfile () 函数的 TCP_CORK

当通过各种组件在内核中构建逻辑数据包时,可使用 setsockopt 套接字 API 将它配置为 1 的值来启用 TCP_CORK。这称为 "corking the socket"。如果特定时间没有删除 cork,TCP_CORK 可能会导致错误。

以下示例演示了通过 setsockopt socket API 启用 TCP_CORK

int one = 1;
setsockopt(descriptor, SOL_TCP, TCP_CORK, &one, sizeof(one));
Copy to Clipboard Toggle word wrap

在某些环境中,如果内核无法识别何时删除 cork,您可以手动删除它,如下所示:

int zero = 0;
setsockopt(descriptor, SOL_TCP, TCP_CORK, &zero, sizeof(zero));
Copy to Clipboard Toggle word wrap

9.3. 使用套接字选项的程序示例

TCP_NODELAYTCP_CORK 套接字选项可显著影响网络连接的行为。TCP_NODELAY 在应用程序上禁用 Nagle 的算法,该算法在应用程序就绪后马上发送数据包。使用 TCP_CORK,您可以同时传输多个数据数据包,而在它们之间没有延迟。

注意

要启用套接字选项,如 TCP_NODELAY,请使用以下代码构建它,然后设置适当的选项。

gcc tcp_nodelay_client.c -o tcp_nodelay_client -lrt
Copy to Clipboard Toggle word wrap

当您运行没有任何参数的 tcp_nodelay_servertcp_nodelay_client 程序时,客户端会使用默认套接字选项。有关 tcp_nodelay_servertcp_nodelay_client 程序的更多信息,请参阅红帽知识库解决方案 TCP 更改会在使用小缓冲区时造成延迟性能

示例程序提供有关这些套接字选项对应用程序的性能影响的信息。

对客户端的性能影响

您可以在不使用 TCP_NODELAYTCP_CORK 套接字选项的情况下,向客户端发送小的缓冲区写入。当不带任何参数运行 时,客户端使用默认套接字选项。

  • 要启动数据传输,请定义服务器 TCP 端口:

    $ ./tcp_nodelay_server 5001
    Copy to Clipboard Toggle word wrap

    代码发送 15 个数据包,每个字节均为两字节,并等待来自该服务器的响应。它在这里采用默认 TCP 行为

对回环接口的性能影响

要启用 socket 选项,请使用 gcc tcp_nodelay_client.c -o tcp_nodelay_client -lrt 来构建它,然后设置适当的选项。

以下示例使用回环接口来演示三种变体:

  • 要立即发送缓冲区写入,请在配置了 TCP_NODELAY 的套接字上设置 no_delay 选项。

    $ ./tcp_nodelay_client localhost --port=5001 --nr_logical_packets=10000 --no_delay --verbose
    
     10000 packets (100 buffers) sent in 4079.655518 ms: 490.237457 bytes/ms using TCP_NODELAY
    Copy to Clipboard Toggle word wrap

    TCP 会正确发送缓冲区,禁用组合小数据包的算法。这提高了性能,但可能会导致每个逻辑数据包发送大量小数据包。

  • 要收集多个数据包并通过一个系统调用发送它们,请配置 TCP_CORK 套接字选项。

    $ /tcp_nodelay_client localhost --port=5001 --nr_logical_packets=10000 --cork --verbose
    
     10000 packets (100 buffers) sent in 669.514221 ms: 2987.240479 bytes/ms using TCP_CORK
    Copy to Clipboard Toggle word wrap

    使用 cork 技术可显著减少发送数据包所需的时间,因为它将完整逻辑数据包合并到其缓冲区中,并发送较少的整个网络数据包。您必须确保在适当的时间删除 cork

    在开发程序时,如果必须从文件发送批量数据,请考虑使用带有 sendfile () 选项的 TCP_CORK

  • 在不使用套接字选项的情况下测量性能。

    $ ./tcp_nodelay_client localhost --port=5001 --nr_logical_packets=10000 --verbose
    
     10000 packets (100 buffers) sent in 410403.718750 ms: 4.873250 bytes/ms
    Copy to Clipboard Toggle word wrap

    这是当 TCP 组合缓冲区写入并等待检查网络数据包的最佳数据时的基准措施。

第 10 章 用于 Real Time 调度程序的 RHEL

RHEL for Real Time 使用命令行实用程序可帮助您配置和监控进程配置。

10.1. 用于设置调度程序的 chrt 工具

chrt 实用程序检查并调整调度程序策略和优先级。它可以启动具有所需属性的新进程,或更改正在运行的进程的当前属性。

chrt 实用程序使用 --pid-p 选项指定进程 ID (PID)。

chrt 工具采用以下策略选项:

  • -f--fifo :将调度设置为 SCHED_FIFO
  • -O--other :将调度设置为 SCHED_OTHER
  • -r--rr :将调度设置为 SCHED_RR
  • -d--deadline :将调度设置为 SCHED_DEADLINE.

以下示例显示了指定进程的属性。

# chrt -p 468
pid 468’s current scheduling policy: SCHED_FIFO
pid 468’s current scheduling priority: 85
Copy to Clipboard Toggle word wrap

10.2. 抢占调度

实时抢占是一种临时中断执行任务的机制,希望稍后恢复它。当优先级较高的进程中断 CPU 用量时,会发生它。抢占可能会对性能造成负面影响,持续的抢占可导致名为"垃圾箱"的状态。当进程不断抢占且任何进程需要完全运行时,会出现这个问题。更改任务的优先级可帮助减少非自愿抢占。

您可以通过查看 /proc/PID/status 文件的内容(其中 PID 是进程标识符),检查单个进程上发生的 voluntary 和 involuntary 抢占。

以下示例显示了 PID 为 1000 的进程的抢占状态。

# grep voluntary /proc/1000/status
voluntary_ctxt_switches: 194529
nonvoluntary_ctxt_switches: 195338
Copy to Clipboard Toggle word wrap

10.3. 调度程序优先级的库函数

实时进程使用一组不同的库调用来控制策略和优先级。功能需要包含 sched.h 标头文件。符合 SCHED_OTHER, SCHED_RRSCHED_FIFO 也需要在 sched.h header 文件中定义。

表列出了为实时调度程序设置策略和优先级的功能。

Expand
表 10.1. 用于实时调度程序的库函数
Functions描述

sched_getscheduler()

检索特定进程标识符(PID)的调度程序策略

sched_setscheduler()

设置调度程序策略和其他参数。此功能需要三个参数: sched_setscheduler (pid_t pid,int policy,const struct sched_param *sp);

sched_getparam()

检索调度策略的调度参数。

sched_setparam()

设置与已经设置的调度策略关联的参数,可以使用 sched_getparam () 函数验证。

sched_get_priority_max()

返回与调度策略关联的最大有效优先级。

sched_get_priority_min()

返回与调度策略关联的最低有效优先级。

sched_rr_get_interval()

显示每个进程的分配 时间片

第 11 章 RHEL for Real Time 的系统调用

实时系统调用是应用程序程序用来与内核通信的功能。它是程序从内核订购资源的机制。

11.1. sched_yield() function

sched_yield () 函数是为处理器选择运行进程以外的进程。从不编写的应用程序内发布,这种类型的请求容易失败。

当在具有实时优先级的进程中使用 sched_yield () 函数时,它可能会显示意外行为。调用 sched_yield () 的进程将移到以相同优先级运行的进程队列的尾部。如果没有以同样优先级运行其他进程,名为 sched_yield () 的进程将继续运行。如果该进程的优先级较高,它可能会创建一个忙碌循环,从而导致机器不可用。

通常,不要在实时进程中使用 sched_yield ()

11.2. getrusage() function

getrusage () 函数从指定的进程或其线程中检索重要信息。它报告以下信息,例如:

  • 自愿上下文切换的数量。
  • 主页面和次要页面错误。
  • 使用的内存量。

Getrusage () 允许您查询应用程序,以提供与性能调优和调试活动相关的信息。Getrusage () 检索需要从 /proc/ 目录中的几个不同文件目录的信息,并难以与应用程序上的特定操作或事件同步。

注意

并非所有填充的 getrusage () 结果中包含的字段都由内核设置。其中一些只出于兼容性的原因而保持。

第 12 章 在实时内核和解决方案中调度问题

在实时内核中的调度有时会有后果。通过使用提供的信息,您可以了解调度策略、调度程序节流和线程不足状态的问题,以及实时内核以及潜在的解决方案。

12.1. 实时内核的调度策略

实时调度策略共享一个主要特征:它们运行,直到优先级更高的线程中断线程或线程等待,或者通过睡眠或执行 I/O 来中断线程。

对于 SCHED_RR,操作系统会中断正在运行的线程,以便运行相等的 SCHED_RR 优先级的另一个线程。在这两种情形中,POSIX 规范不会进行调配,以定义允许低优先级线程获得任何 CPU 时间的策略。这种实时线程的特征意味着可轻松编写一个应用程序,这会使给定 CPU 的 100% 保持了。但是,这会导致操作系统出现问题。例如,操作系统负责管理系统范围和每个 CPU 资源,必须定期检查描述这些资源的数据结构,并执行日常活动。但是,如果某个内核由 SCHED_FIFO 线程进行单调,则无法执行其内务任务。最终,整个系统变得不稳定,并可能导致崩溃。

在 RHEL for Real Time 内核中,中断处理程序作为具有 SCHED_FIFO 优先级的线程运行。默认优先级为 50。具有 SCHED_FIFOSCHED_RR 策略高于中断处理器线程的 cpu-hog 线程可防止中断处理程序运行。这会导致程序等待这些中断信号的数据失败,并失败。

12.2. 实时内核中的调度程序节流

实时内核包含一个保护机制,它允许分配供实时任务使用的带宽。保护机制被称为实时调度程序节流。

实时节流机制的默认值定义实时任务可以使用 95% 的 CPU 时间。其余的 5% 将被视为非实时任务,例如在 SCHED_OTHER 和类似调度策略下运行的任务。务必要注意,如果单个实时任务占据了 95% 的 CPU 时间插槽,则该 CPU 上的剩余实时任务将不会运行。只有非实时任务会使用剩余的 CPU 时间的 5%。默认值可能会有以下性能影响:

  • 实时任务最多为它们提供 95% 的 CPU 时间,这可能会影响其性能。
  • 实时任务不允许运行非实时任务来锁定系统。

实时调度程序节流由 /proc 文件系统的以下参数控制:

/proc/sys/kernel/sched_rt_period_us 参数
定义 CPU 带宽 100% 中的周期(微秒)。默认值为 1,000,000 crius,其值为 1 秒。必须仔细考虑对句点值的更改,因为一个非常高或低的值可能会导致问题。
/proc/sys/kernel/sched_rt_runtime_us 参数
定义可用于所有实时任务的总带宽。默认值为 950,000 crius (0.95 s),即 CPU 带宽的 95%。将值设为 -1, 将实时任务配置为使用最多 100% 的 CPU 时间。这只适用于实时任务,且没有明显的注意事项,如未绑定的轮询循环。

12.3. 实时内核中的线程不足

当线程位于 CPU 运行队列中超过 starvation 阈值且不会进行进度时,线程不足发生。线程不足的常见原因是运行固定优先级轮询应用,如 SCHED_FIFO 或绑定到 CPU 的 SCHED_RR。由于轮询应用不阻止 I/O,因此这可以防止其他线程(如 kworkers )在那个 CPU 上运行。

早期尝试减少线程星化会作为实时节流调用。在实时节流中,每个 CPU 具有一部分执行时间,专用于非实时任务。节流的默认设置是 95% 的 CPU,用于实时任务,为非实时任务保留 5%。如果您有单个实时任务导致出现不足的情况,则这可以正常工作,但如果分配给 CPU 有多个实时任务,则无法正常工作。您可以使用以下方法解决这个问题:

stalld 机制

stalld 机制是实时节流的替代,并避免某些节流缺陷。stalld 是一个守护进程,用于定期监控系统中每个线程的状态,并查找位于运行队列中的指定长度的线程,而无需运行。stalld 临时将线程更改为使用 SCHED_DEADLINE 策略,并在指定的 CPU 上分配线程的一小部分时间。然后,线程会运行,在使用时间片段时,线程会返回其原始调度策略,而 stalld 会继续监控线程状态。

日常 CPU 是运行所有守护进程、shell 进程、内核线程、中断处理程序以及可从隔离 CPU 分配的所有工作的 CPU。对于禁用了实时节流的内务 CPU,stalld 会监控运行主工作负载的 CPU,并使用 SCHED_FIFO 忙碌循环分配 CPU,这有助于检测停滞线程,并根据之前定义的可接受的 noise 要求改进线程优先级。如果实时节流机制导致主工作负载不合理的,则 stalld 可能是首选。

使用 stalld 时,您可以通过提高星级线程来更精确地控制引入的 noise。shell 脚本 /usr/bin/throttlectl 会在运行 stalld 时自动禁用实时节流。您可以使用 /usr/bin/throttlectl show 脚本列出当前的节流值。

禁用实时节流

/proc 文件系统中的以下参数控制实时节流:

  • /proc/sys/kernel/sched_rt_period_us 参数指定一段时间内的微秒数,默认为 100万,默认值为 1 秒。
  • /proc/sys/kernel/sched_rt_runtime_us 参数指定在节流发生前实时任务可以使用的微秒数,它默认为可用 CPU 周期的 950,000 或 95%。您可以使用 echo -1 > /proc/sys/kernel/ sched_rt_runtime_us 命令,将 -1 的值传递给 sched_rt_runtime_us 文件来禁用节流。

法律通告

Copyright © 2025 Red Hat, Inc.
The text of and illustrations in this document are licensed by Red Hat under a Creative Commons Attribution–Share Alike 3.0 Unported license ("CC-BY-SA"). An explanation of CC-BY-SA is available at http://creativecommons.org/licenses/by-sa/3.0/. In accordance with CC-BY-SA, if you distribute this document or an adaptation of it, you must provide the URL for the original version.
Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.
Red Hat, Red Hat Enterprise Linux, the Shadowman logo, the Red Hat logo, JBoss, OpenShift, Fedora, the Infinity logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.
Linux® is the registered trademark of Linus Torvalds in the United States and other countries.
Java® is a registered trademark of Oracle and/or its affiliates.
XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.
MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.
Node.js® is an official trademark of Joyent. Red Hat is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.
The OpenStack® Word Mark and OpenStack logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.
All other trademarks are the property of their respective owners.
返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2025 Red Hat