为低延迟操作优化 RHEL
在 RHEL 上优化 Real Time 内核以提高性能和效率
摘要
对红帽文档提供反馈
我们感谢您对我们文档的反馈。让我们了解如何改进它。
通过 Jira 提交反馈(需要帐户)
- 登录到 Jira 网站。
- 在顶部导航栏中点 Create
- 在 Summary 字段中输入描述性标题。
- 在 Description 字段中输入您对改进的建议。包括文档相关部分的链接。
- 点对话框底部的 Create。
第 1 章 RHEL 10 中的实时内核调整
延迟或响应时间是指事件和系统响应的时间。它通常以微秒为单位(swigs)测量。
对于在 Linux 环境中运行的大多数应用程序,基本的性能调优可以满足对性能提高的要求。对于延迟低、可负责且可预测的行业,红帽有一个可调整的替代内核,以便延迟满足这些要求。RHEL for Real Time 内核提供与 RHEL 10 的无缝集成,并为客户提供在机构中测量、配置和记录延迟时间的机会。
在精心调优的系统上使用 RHEL for Real Time 内核,适用于具有非常高确定性要求的应用程序。通过内核系统调优,您可以在确定性方面获得良好改进。开始之前,对标准 RHEL 10 系统执行常规系统调优,然后部署 RHEL for Real Time 内核。
如果无法执行这些任务,则可能会阻止 RHEL Real Time 部署具有一致的性能。
1.1. 调优指南
实时调优是一个迭代过程;您几乎无法调整几个变量,并且知道该更改是最佳实现的。准备花费几天或周时间,缩减最适合您系统的调优配置集合。
另外,始终使长时间测试运行。更改一些调优参数后,执行五分钟测试运行并不是一组特定的调优更改的良好验证。使测试的长度可调整运行,并在几分钟后运行它们。您可以缩小到几个不同的调优配置集,测试会运行几小时,然后一次运行这些集合,以检测最高延迟或资源耗尽的基线的情况。
- 在应用程序中构建测量机制,以便您可以准确衡量一组特定的调优更改会影响应用程序的性能。例如,Aecdotal 证据(例如,"鼠标移动更加顺畅"通常是错误的,可能会有所不同)。进行硬测量并记录它们以便稍后进行分析。
- 在测试运行之间对调整变量进行多次更改非常困难,但这样做意味着您没有办法缩小影响您的测试结果的调优参数。保持测试之间的调优更改尽可能小运行。
- 在调整时,也会尝试进行大量更改,但几乎总是最好进行增量更改。您会发现,从最低到最高优先级值开始的工作将会产生长远的运行结果。
-
使用可用的工具。
tuna
调优工具可让您轻松更改线程和中断的处理器相关性、线程优先级和隔离处理器以供应用使用。taskset
和chrt
命令行工具允许您执行大多数tuna
的作用。如果您遇到性能问题,ftrace
和perf
工具可帮助查找延迟问题。 - 使用外部工具更改策略、优先级和关联性,而不是硬编码的值。通过使用外部工具,您可以尝试许多不同的组合并简化您的逻辑。找到一些提供良好结果的设置后,您可以将它们添加到应用程序中,或者设置启动逻辑以在应用程序启动时实施设置。
1.2. 线程调度策略
Linux 使用三个主要线程调度策略。
SCHED_OTHER
(有时称为SCHED_NORMAL
)这是默认的线程策略,具有由内核控制的动态优先级。优先级会根据线程活动更改。具有此策略的线程被视为实时优先级 0 (零)。
SCHED_FIFO
(首先为 out)优先级范围为
1 - 99
的实时策略,1
为最低,99
为最高。SCHED_FIFO
线程的优先级始终高于SCHED_OTHER
线程(例如,优先级为1
的SCHED_FIFO
线程将具有高于 任何SCHED_OTHER
线程的优先级)。作为SCHED_FIFO
线程创建的任何线程都具有固定优先级,并将运行,直到被优先级更高的线程阻止或抢占为止。SCHED_RR
(Round-Robin)SCHED_RR
是对SCHED_FIFO
的修改。具有相同优先级的线程具有量子,并且在所有优先级SCHED_RR
线程之间进行循环调度。此策略很少使用。
1.3. 平衡日志记录参数
syslog
服务器通过网络转发来自程序的日志消息。发生这种情况的频率较少,待处理的事务越大。如果事务非常大,可能会导致 I/O spike。要防止这种情况,请保持间隔小。
系统日志记录守护进程 syslogd
用于从不同程序收集信息。它还从内核日志记录守护进程 klogd
收集内核报告的信息。通常,syslogd 记录到本地文件,但也可以将其配置为通过网络记录到远程记录服务器。
流程
启用远程日志记录:
- 配置将日志发送到的计算机。如需更多信息,请参阅 Red Hat Enterprise Linux 中的 rsyslog 远程 Syslogging。
配置将将日志发送到远程日志服务器的每个系统,以便其
syslog
输出写入服务器,而不是写入本地文件系统。为此,请编辑每个客户端系统上的/etc/rsyslog.conf
文件。对于该文件中定义的每个日志记录规则,请将本地日志文件替换为远程记录服务器的地址。Log all kernel messages to remote logging host.
# Log all kernel messages to remote logging host. kern.* @my.remote.logging.server
Copy to Clipboard Copied! 上面的示例将客户端系统配置为将所有内核消息记录到位于
@my.remote.logging.server
的远程机器中。另外,您可以通过在
/etc/rsyslog.conf
文件中添加以下行,将syslogd
配置为记录所有本地生成的系统信息:Log all messages to a remote logging server:
# Log all messages to a remote logging server: . @my.remote.logging.server
Copy to Clipboard Copied!
syslogd
守护进程不包括其生成的网络流量的内置速率限制。因此,红帽建议在使用 RHEL for Real Time 系统时,只包括您的机构远程日志记录所需的日志消息。例如:内核警告、身份验证请求等。其他消息应在本地记录。
1.4. 通过避免运行不必要的应用程序来提高性能
每个运行的应用程序都使用系统资源。确保系统上没有运行不必要的应用程序可能会显著提高性能。
先决条件
- 您在系统上具有 root 权限。
流程
不要运行 图形界面,而这并不是绝对需要的,特别是在服务器中。
检查系统是否默认引导进入 GUI:
systemctl get-default
# systemctl get-default
Copy to Clipboard Copied! 如果命令的输出是
graphical.target
,请将系统配置为引导到文本模式:systemctl set-default multi-user.target
# systemctl set-default multi-user.target
Copy to Clipboard Copied! 除非您要调整的系统上主动使用 邮件传输代理(MTA),请禁用它。如果需要 MTA,请确保其经过精心调优,或考虑将其移至专用机器。
如需更多信息,请参阅 MTA 文档。
重要MTA 用于发送由
cron
等程序执行的系统生成的消息。这包括日志函数(如logwatch ()
)生成的报告。如果机器上的 MTA 已禁用,您将无法接收这些邮件。外设设备 (如鼠标、键盘、webcams )发送可能会影响延迟的中断。如果您不使用图形界面,请删除所有未使用的外设设备并禁用它们。
如需更多信息,请参阅设备的文档。
检查可能会影响性能的自动
cron
作业。crontab -l
# crontab -l
Copy to Clipboard Copied! 禁用
crond
服务或任何不需要的cron
作业。- 检查您的系统是否有第三方应用程序和外部硬件供应商添加的任何组件,并删除所有不必要的组件。
其他资源
-
您系统上的
cron (8)
手册页
1.5. 非统一内存访问
taskset
工具仅适用于 CPU 关联性,且不知道内存节点等其他 NUMA 资源。如果要与 NUMA 结合使用进程绑定,请使用 numactl
命令而不是 taskset
。
有关 NUMA API 的更多信息,请参阅 Andi Kleen's 白皮书 Linux 的 NUMA API。
1.6. 确保 debugfs 已被挂载
debugfs
文件系统特别设计用于调试和为用户提供信息。它在 RHEL 8 中自动挂载到 /sys/kernel/debug/
目录中。
debugfs
文件系统使用 ftrace
和 trace-cmd
命令挂载。
流程
验证 debugfs
是否已挂载:
运行以下命令:
mount | grep ^debugfs
# mount | grep ^debugfs debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime,seclabel)
Copy to Clipboard Copied! 如果挂载了
debugfs
,命令会显示debugfs
的挂载点和属性。如果没有挂载
debugfs
,命令不会返回任何内容。
1.7. RHEL for Real Time 中的 InfiniBand
InfiniBand 是一种通信架构,通常用于提高带宽、提高服务质量(QOS)并提供故障转移。它还可使用 Remote Direct Memory Access (RDMA)机制来改进延迟。
在 RHEL for Real Time 上支持 InfiniBand 与 Red Hat Enterprise Linux 10 中的支持相同。如需更多信息,请参阅配置 InfiniBand 和 RDMA 网络。
1.8. 使用 RoCEE 和高性能网络
RoCEE
(通过融合增强以太网的RDMA)是通过以太网网络实现远程直接内存访问(RDMA)的协议。它允许您在数据中心中维护一致、高速的环境,同时为关键事务提供确定性、低延迟数据传输。
高性能网络
(HPN)是一组共享库,可在内核中提供 RoCEE
接口。HPN
不通过独立的网络基础架构,而是使用标准以太网基础架构将数据直接放入远程系统内存中,从而减少了 CPU 开销并降低基础架构成本。
在 RHEL for Real Time 下支持 RoCEE
和 HPN
与 RHEL 10 提供的支持不同。
1.9. 为 RHEL 实时调优容器
第 2 章 RHEL for Real Time 的调度策略
在实时中,调度程序是用来决定要运行的线程的内核组件。每个线程都有一个关联的调度策略和静态调度优先级,称为 sched_priority
。调度是抢占的,因此当具有较高静态优先级的线程准备好运行时,当前运行的线程将停止。然后,运行的线程会返回到其静态优先级的 waitlist
。
所有 Linux 线程都有以下调度策略之一:
-
SCHED_OTHER
或SCHED_NORMAL
: 是默认策略。 -
SCHED_BATCH
: 与SCHED_OTHER
类似,但具有增量性调整。 -
SCHED_IDLE
: 是优先级低于SCHED_OTHER
的策略。 -
SCHED_FIFO
: 是第一个 in 和第一个实时策略。 -
SCHED_RR
: 是循环实时策略。 -
SCHED_DEADLINE
: 是根据作业截止时间排列任务优先级的调度程序策略。最早的绝对期限的作业首先运行。
2.1. 调度程序策略
实时线程的优先级高于标准线程。策略具有调度优先级值,范围从最小值为 1 到最大值 99。
以下策略对实时至关重要:
SCHED_OTHER
或SCHED_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_FIFO
和SCHED_RR
线程运行,直到发生以下事件之一:- 线程进入睡眠状态或等待事件。
高优先级的实时线程准备好运行。
除非发生上述事件之一,否则线程在指定的处理器中无限期运行,而较低优先级线程保留在等待运行的队列中。这可能导致系统服务线程处于驻留状态,并阻止交换出文件系统数据并导致文件系统数据清除失败。
SCHED_DEADLINE
策略SCHED_DEADLINE
策略指定时间要求。它根据任务的截止时间调度每个任务。预计截止时间最早(EDF)调度的任务首先运行。内核需要
runtime swigdeadlineCloneperiod
变为 true。所需选项之间的关系是runtime swigdeadline swigperiod
。
2.2. SCHED_DEADLINE 策略的参数
每个 SCHED_DEADLINE
任务都是 以句点
、运行时和
截止时间
参数的特征。这些参数的值为纳秒的整数。
参数 | 描述 |
---|---|
|
例如,如果视频处理任务每秒处理 60 帧,每 16 毫秒为服务排队。因此, |
|
例如,如果视频处理工具在最糟糕的情况下,处理图像的五毫秒,则 |
|
例如,如果任务需要在十毫秒内提供已处理帧, |
2.3. 配置 SCHED_DEADLINE 参数
Red Hat Enterprise Linux 中的 sched_deadline_period_max_us
和 sched_deadline_period_min_us
参数是 SCHED_DEADLINE 调度策略的内核可调参数。这些参数通过使用此实时调度类来控制任务的最大允许周期(以微秒为单位)。
sched_deadline_period_max_us
和 sched_deadline_period_min_us
一起工作,为 SCHED_DEADLINE 任务的 period 值定义一个可接受的范围。
-
min_us
可防止可能使用过量资源的高频率任务。 -
max_us
会阻止极长的任务,它们可能会导致其他任务的性能。
使用参数的默认配置。如果需要更改参数的值,您必须在实时环境中配置自定义值前测试它们。
参数的值以微秒为单位。例如,1 秒等于 100000 微秒。
先决条件
- 您的系统必须具有 root 权限。
流程
使用其中一个
sysctl
命令临时设置所需的值。要使用
sched_deadline_period_max_us
参数,请运行以下命令:sysctl -w kernel.sched_deadline_period_max_us=2000000
# sysctl -w kernel.sched_deadline_period_max_us=2000000
Copy to Clipboard Copied! 要使用
sched_deadline_period_min_us
参数,请运行以下命令:sysctl -w kernel.sched_deadline_period_min_us=100
# sysctl -w kernel.sched_deadline_period_min_us=100
Copy to Clipboard Copied!
永久设置值。
对于
max_us
,编辑/etc/sysctl.conf
并添加以下行:kernel.sched_deadline_period_max_us = 2000000
kernel.sched_deadline_period_max_us = 2000000
Copy to Clipboard Copied! 对于
min_us
,请编辑/etc/sysctl.conf
并添加以下行:kernel.sched_deadline_period_min_us = 100
kernel.sched_deadline_period_min_us = 100
Copy to Clipboard Copied!
应用更改:
sysctl -p
# sysctl -p
Copy to Clipboard Copied!
验证
验证
max_us
的自定义值:cat /proc/sys/kernel/sched_deadline_period_max_us 2000000
$ cat /proc/sys/kernel/sched_deadline_period_max_us 2000000
Copy to Clipboard Copied! 验证
min_us
的自定义值:cat /proc/sys/kernel/sched_deadline_period_min_us 100
$ cat /proc/sys/kernel/sched_deadline_period_min_us 100
Copy to Clipboard Copied!
第 3 章 设置持久性内核调整参数
当您决定用于系统的调优配置时,您可以在重新启动后保留所做的更改。
默认情况下,编辑的内核调优参数仅保持有效,直到系统重启或参数被明确更改为止。这可用于建立初始调优配置。它还提供了一个安全机制。如果编辑过的参数导致计算机的行为正常,则重新启动计算机会将参数返回到以前的配置。
3.1. 进行持久性内核调整参数更改
您可以通过在 /etc/sysctl.conf
文件中添加参数来更改内核调整参数。
这个过程 不会更改 当前会话中的任何内核调整参数。在 /etc/sysctl.conf
中输入的更改只会影响将来的会话。
先决条件
- 您在系统上具有 root 权限。
流程
-
在文本编辑器中打开
/etc/sysctl.conf
。 使用参数的值将新条目插入到文件中。
通过删除
/proc/sys/
路径,将剩余的斜杠(/
)更改为句点(.
),并包含参数的值来修改参数名称。例如,要使命令
echo 0 > /proc/sys/kernel/hung_task_panic
持久,请输入以下内容:Enable gettimeofday(2)
# Enable gettimeofday(2) kernel.hung_task_panic = 0
Copy to Clipboard Copied! - 保存并关闭该文件。
- 重启系统以使更改生效。
验证
验证配置:
cat /proc/sys/kernel/hung_task_panic 0
# cat /proc/sys/kernel/hung_task_panic 0
Copy to Clipboard Copied!
第 4 章 应用程序调整和部署
使用最佳配置和设置组合调整实时内核可帮助增强和开发 RHEL for Real Time 应用程序。
通常,尝试使用 POSIX
定义的 API (应用程序编程接口)。RHEL for Real Time 符合 POSIX
标准。RHEL for Real Time 内核减少的延迟也基于 POSIX
。
4.1. 实时应用中的信号处理
传统 UNIX
和 POSIX
信号有其用途,特别是用于错误处理,但它们不适合实时应用程序中作为事件交付机制。这是因为当前的 Linux 内核信号处理代码非常复杂,主要是因为旧行为和需要支持的许多 API。这种复杂性意味着,在提供信号时所用的代码路径并非始终最佳,应用程序可能会经历较长的延迟。
UNIX 信号后的原始动机是执行的不同"线程"之间的多路控制(进程)。信号的行为与操作系统中断类似。也就是说,当向应用发送信号时,应用的上下文会被保存,并开始执行之前注册的信号处理程序。信号处理程序完成后,应用将返回到在发送信号时执行它的位置。在实践中,这可能会变得复杂。
在实时应用程序中,信号无法信任。一个更好的选择是使用 POSIX 线程(线程)来分发工作负载,并在各种组件之间进行通信。您可以使用 mutexes、条件变量和障碍的 pthreads 机制协调线程组。这些相对新结构的代码路径比传统的处理代码更加干净。
4.2. 同步线程
sched_yield
函数是一种同步机制,允许较低优先级线程有机会运行。在较差的应用程序中发出不良的应用程序时,这种类型的请求容易出错。
较高的优先级线程可以调用 sched_yield ()
,以允许其他线程有机会运行。调用进程将移到该优先级中运行的进程的尾部。当发生这种情况时,如果没有其他进程以同一优先级运行,调用进程将继续运行。如果该进程的优先级很高,它可能会创建一个忙碌的循环,从而导致机器无法使用。
当 SCHED_DEADLINE
任务调用 sched_yield ()
时,它会提供配置的 CPU,剩余的运行时会立即节流,直到下一个周期为止。sched_yield ()
行为允许任务在下一个时间段内唤醒。
调度程序可以更好地决定何时以及是否实际还有其他线程等待运行。避免在任何实时任务中使用 sched_yield ()
。
流程
要调用
sched_yield ()
函数,请运行以下代码:for(;;) { do_the_computation(); /* * Notify the scheduler at the end of the computation * This syscall will block until the next replenishment */ sched_yield(); }
for(;;) { do_the_computation(); /* * Notify the scheduler at the end of the computation * This syscall will block until the next replenishment */ sched_yield(); }
Copy to Clipboard Copied! SCHED_DEADLINE
任务受基于冲突的搜索(CBS)算法节流,直到下一个周期(在循环的下一次执行开始)。
4.3. 实时调度程序优先级
systemd
命令可用于在引导过程中为启动的服务设置实时优先级。一些内核线程可以被赋予一个非常高的优先级。这允许默认优先级与 Java (RTSJ)实时规范的要求良好集成。RTSJ 需要 10 到 89 范围内的优先级。
对于不使用 RTSJ 的部署,应用程序可以使用在 90 以下的调度优先级。在调度优先级 49 的任何应用程序线程时,请特别小心,因为它可以防止必要的系统服务运行,因为它可以防止重要的系统服务运行。这可能会导致无法预计的行为,包括阻止网络流量、阻止虚拟内存分页和数据崩溃,因为文件系统日志记录造成数据崩溃。
如果任何应用程序线程调度到优先级 89,请确保线程仅运行非常短的代码路径。如果不这样做,会降低 RHEL for Real Time 内核的低延迟功能。
为没有强制权限的用户设置实时优先级
默认情况下,只有具有应用 root 权限的用户才能更改优先级和调度信息。要提供 root 权限,您可以修改设置,首选方法是将用户添加到 realtime
组中。
您还可以通过编辑 /etc/security/limits.conf
文件来更改用户权限。但是,这可能会导致重复,并使系统对常规用户不可用。如果您决定编辑此文件,请谨慎操作,并在进行更改之前创建副本。
4.4. 加载动态库
在开发实时应用程序时,请考虑在启动时解析符号,以避免程序执行过程中出现非确定的延迟。在启动时解析符号可能会减慢程序初始化的速度。您可以通过使用 ld.so
设置 LD_BIND_NOW
变量(动态链接器/加载器)来指示动态 Libraries 在应用程序启动时加载。
例如,以下 shell 脚本使用值 1
导出 LD_BIND_NOW
变量,然后运行具有 FIFO
的调度程序策略和优先级 1
的程序。
#!/bin/sh LD_BIND_NOW=1 export LD_BIND_NOW chrt --fifo 1 /opt/myapp/myapp-server &
#!/bin/sh
LD_BIND_NOW=1
export LD_BIND_NOW
chrt --fifo 1 /opt/myapp/myapp-server &
第 5 章 为系统调整设置 BIOS 参数
BIOS 在系统正常工作过程中扮演了关键角色。通过正确配置 BIOS 参数,您可以显著提高系统性能。
每个系统和 BIOS 供应商使用不同的术语和导航方法。有关 BIOS 设置的更多信息,请参阅 BIOS 文档或联系 BIOS 供应商。
5.1. 禁用电源管理以改进响应时间
BIOS 电源管理选项通过更改系统时钟频率或将 CPU 置于各种睡眠状态之一来节省电源。这些操作可能会影响系统响应外部事件的速度。
要改进响应时间,请在 BIOS 中禁用所有电源管理选项。
5.2. 禁用错误检测和更正单元来提高响应时间
错误检测和修正(EDAC)单元是用于检测和更正来自 Error Correcting Code (ECC)内存的错误的设备。通常 EDAC 选项的范围包括没有 ECC 检查所有内存节点的定期扫描错误。EDAC 级别越长,BIOS 使用的时间越长。这可能会导致缺少关键事件期限。
要改进响应时间,请关闭 EDAC。如果这不可能,请将 EDAC 配置为最低功能级别。
5.3. 通过配置系统管理中断来缩短响应时间
系统管理中断(SMI)是一个硬件厂商工具,可确保系统正常运行。BIOS 代码通常服务 SMI 中断。SMI 通常用于热管理、远程控制台管理(IPMI)、EDAC 检查和各种其他内务任务。
如果 BIOS 包含 SMI 选项,请检查厂商和相关文档,以确定可以安全地禁用它们的范围。
虽然可以完全禁用 SMI,但红帽强烈建议您不要这样做。删除系统以生成和服务 SMI 的功能可能会导致严重硬件故障。
第 6 章 实时内核运行时验证
运行时验证是一种轻量级且严格的方法,用于检查系统事件及其正式规格之间的行为等效性。运行时验证有集成在附加到 tracepoints
的内核中的监控。如果系统状态与定义的规格不同,则运行时验证程序会激活响应器来通知或启用反应,如在日志文件或系统关闭时捕获事件以防止极端情况下的传播。
6.1. 运行时监控和响应器
运行时验证(RV)监控器封装在 RV 监控抽象中,并在定义的规格和内核追踪之间协调,以在 trace 文件中捕获运行时事件。RV 监控器包括:
- 参考模型是系统的参考模型。
- monitor 实例是 monitor 的一组实例,如每个 CPU 监视器或每个任务监控器。
- 将监控器连接到系统的帮助程序功能。
除了在运行时验证和监控系统外,您还可以启用对意外系统事件的响应。反应形式的反应可能与在 trace 文件中捕获事件不同,以启动非常反应,如关闭,以避免在安全关键系统上出现系统故障。
Reactors 是 RV 监视器可用的反应方法,根据需要定义对系统事件的反应。默认情况下,监控器提供操作的追踪输出。
6.2. 在线运行时监控器
运行时验证(RV)监控器分为以下类型:
在系统运行时,在线监控 trace 中的捕获事件。
如果事件处理附加到系统执行中,则在线监视器会同步。这将在事件监控期间阻止系统。如果执行与系统分离,且在不同机器上运行,在线监视器是异步的。但是,这需要保存的执行日志文件。
离线监控事件发生后生成的进程跟踪。
通过从持久性存储读取保存的 trace 日志文件,离线运行时验证捕获信息。只有在文件中保存了事件时,离线 monitor 才能正常工作。
6.3. 用户界面
用户界面位于 /sys/kernel/tracing/rv
,类似于追踪接口。用户界面包含上述文件和文件夹。
设置 | 描述 | 示例命令 |
---|---|---|
| 显示每行一个可用的监视器。 |
|
| 显示每行一个可用的响应器。 |
|
| 显示已启用的 monitor 每行一个。您可以同时启用多个 monitor。 使用 '!' 前缀编写 monitor 名称将禁用监控并截断文件会禁用所有已启用的 monitor。 |
|
|
|
|
|
使用 "[]" 中特定 MONITOR 选择的反应者列出可用的响应器。默认为 no operation ( 编写响应器的名称,将它集成到特定的 MONITOR 中。 |
|
|
在 trace 界面中启动
编写 | |
|
启用 reactors。编写 | |
| 显示 Monitor 描述 | |
|
显示 monitor 的当前状态。编写 |
第 7 章 运行并解释硬件和固件延迟测试
使用 hwlatdetect
程序,您可以测试并验证潜在的硬件平台是否适合使用实时操作。
先决条件
-
确保安装了
RHEL-RT
(RHEL for Real Time)和realtime-tests
软件包。 有关低延迟操作所需的任何调整步骤,请查看厂商文档。
供应商文档可以提供减少或删除任何将系统转换为系统管理模式(SMM)的系统管理中断(SMI)的说明。虽然系统处于 SMM,但它运行固件而不是操作系统代码。这意味着,在 SMM 等待期间过期的任何计时器,直到系统转换为正常操作为止。这可能导致不解释的延迟,因为 Linux 无法阻止 SMI,并且唯一表示我们实际在特定于供应商的性能计数器寄存器中可以找到 SMI。
警告红帽强烈建议您不要完全禁用 SMI,因为它可能会导致出现灾难性硬件故障。
7.1. 运行硬件和固件延迟测试
在运行 hwlatdetect
程序时,不需要在系统中运行任何负载,因为测试会查找硬件架构或 BIOS 或 EFI 固件带来的延迟。hwlatdetect
的默认值是每秒轮询 0.5 秒,并报告连续调用之间超过 10 微秒的差距,以获取时间。hwlatdetect
返回 系统上可能的最大延迟。因此,如果您有一个应用程序需要最大延迟值小于 10us,hwlatdetect
会报告其中一个差距为 20us,则系统只能保证延迟 20us。
如果 hwlatdetect
显示系统无法满足应用程序的延迟要求,请尝试更改 BIOS 设置或使用系统供应商获得满足应用程序延迟要求的新固件。
流程
运行
hwlatdetect
,以秒为单位指定测试持续时间。hwlatdetect
通过轮询时钟源并查找无法解释的差距来查找硬件和固件延迟。hwlatdetect --duration=60s
# hwlatdetect --duration=60s hwlatdetect: test duration 60 seconds detector: tracer parameters: Latency threshold: 10us Sample window: 1000000us Sample width: 500000us Non-sampling period: 500000us Output File: None Starting test test finished Max Latency: Below threshold Samples recorded: 0 Samples exceeding threshold: 0
Copy to Clipboard Copied!
7.2. 解释硬件和固件延迟测试结果
硬件延迟检测器(hwlatdetect
)使用 tracer 机制来检测硬件架构或 BIOS/EFI 固件带来的延迟。通过检查 hwlatdetect
测量的延迟,您可以确定潜在的硬件是否适合支持 RHEL for Real Time 内核。
例子
示例结果表示系统经过调优,以最大程度降低固件系统中断。在这种情况下,
hwlatdetect
的输出类似如下:hwlatdetect --duration=60s
# hwlatdetect --duration=60s hwlatdetect: test duration 60 seconds detector: tracer parameters: Latency threshold: 10us Sample window: 1000000us Sample width: 500000us Non-sampling period: 500000us Output File: None Starting test test finished Max Latency: Below threshold Samples recorded: 0 Samples exceeding threshold: 0
Copy to Clipboard Copied! 示例结果代表了一个无法调优的系统,以便最大程度降低固件的系统中断。在这种情况下,
hwlatdetect
的输出类似如下:hwlatdetect --duration=10s
# hwlatdetect --duration=10s hwlatdetect: test duration 10 seconds detector: tracer parameters: Latency threshold: 10us Sample window: 1000000us Sample width: 500000us Non-sampling period: 500000us Output File: None Starting test test finished Max Latency: 18us Samples recorded: 10 Samples exceeding threshold: 10 SMIs during run: 0 ts: 1519674281.220664736, inner:17, outer:15 ts: 1519674282.721666674, inner:18, outer:17 ts: 1519674283.722667966, inner:16, outer:17 ts: 1519674284.723669259, inner:17, outer:18 ts: 1519674285.724670551, inner:16, outer:17 ts: 1519674286.725671843, inner:17, outer:17 ts: 1519674287.726673136, inner:17, outer:16 ts: 1519674288.727674428, inner:16, outer:18 ts: 1519674289.728675721, inner:17, outer:17 ts: 1519674290.729677013, inner:18, outer:17----
Copy to Clipboard Copied! 输出显示,在连续读取
系统时钟源
的过程中有 10 个延迟,在 15-18 us 范围内显示有 10 个延迟。注意之前的版本使用内核模块而不是
ftrace
tracer。
了解结果
有关测试方法、参数和结果的信息可帮助您了解延迟参数和 hwlatdetect
工具检测到的延迟值。
测试方法、参数和结果的表列出了 hwlatdetect
工具检测到的参数和延迟值。
参数 | value | 描述 |
---|---|---|
|
| 测试的持续时间(以秒为单位) |
|
|
运行 |
| ||
|
| 允许的最大延迟 |
|
| 1 秒 |
|
| 0.05 秒 |
|
| 0.05 秒 |
|
| 保存输出的文件。 |
| ||
|
|
测试期间超过 |
|
| 测试记录的样本数量。 |
|
|
由延迟超过 |
|
| 测试运行期间发生的系统管理中断(SMI)的数量。 |
hwlatdetect
工具为 inner 和 outer 输出的值是最大延迟值。它们是连续读取当前系统时钟源(通常为 TSC 或 TSC 寄存器,但可能是 HPET 或 ACPI 电源管理时钟)之间的增量,以及硬件固件组合引入连续读取之间的任何延迟。
找到合适的 hardware-firmware 组合后,下一步是在负载下测试系统的实时性能。
第 8 章 运行和解释系统延迟测试
RHEL for Real Time 提供 rteval
工具,用来在负载下测试系统实时性能。
8.1. 运行系统延迟测试
使用 rteval
工具,您可以在负载下测试系统的实时性能。
先决条件
-
已安装
RHEL for Real Time
软件包组。 - 您在系统上具有 root 权限。
流程
运行
rteval
工具。rteval
# rteval
Copy to Clipboard Copied! rteval
工具启动SCHED_OTHER
任务的大量系统负载。然后,它会对每个在线 CPU 测量实时响应。负载是循环中 Linux 内核树的并行形式,而hackbench
合成了基准。目标是使系统进入 状态,每个内核始终都有一个要调度的作业。作业会执行各种任务,如内存分配/自由、磁盘 I/O、计算任务、内存副本等。
加载开始后,rt
eval
会启动cyclictest
measurement 程序。该程序在每个在线内核上启动SCHED_FIFO
实时线程。然后,它会测量实时调度响应时间。每个测量线程都需要一个时间戳,休眠间隔,然后在唤醒后需要另一个时间戳。测量的延迟是
t1 -(t0 + i)
,这是实际唤醒时间t1
之间的差别,以及第一个时间戳t0
的理论唤醒时间加上 sleep 间隔i
i。rteval
运行的详情会写入 XML 文件以及系统的引导日志。此报告显示在屏幕上,并保存到压缩文件中。文件名采用
rteval- <date>-N-tar.bz2
格式,其中<date
> 是生成报告的日期,N
是在 <date
> 上运行 Nth 的计数器。以下是
rteval
报告的示例:System: Statistics: Samples: 1440463955 Mean: 4.40624790712us Median: 0.0us Mode: 4us Range: 54us Min: 2us Max: 56us Mean Absolute Dev: 1.0776661507us Std.dev: 1.81821060672us CPU core 0 Priority: 95 Statistics: Samples: 36011847 Mean: 5.46434910711us Median: 4us Mode: 4us Range: 38us Min: 2us Max: 40us Mean Absolute Dev: 2.13785341159us Std.dev: 3.50155558554us
System: Statistics: Samples: 1440463955 Mean: 4.40624790712us Median: 0.0us Mode: 4us Range: 54us Min: 2us Max: 56us Mean Absolute Dev: 1.0776661507us Std.dev: 1.81821060672us CPU core 0 Priority: 95 Statistics: Samples: 36011847 Mean: 5.46434910711us Median: 4us Mode: 4us Range: 38us Min: 2us Max: 40us Mean Absolute Dev: 2.13785341159us Std.dev: 3.50155558554us
Copy to Clipboard Copied! 报告包括系统硬件、运行长度、所使用的选项以及时间结果的详细信息,包括每个cpu 和系统范围内的时间结果。
注意要从其生成的文件中重新生成
rteval
报告,请运行# rteval --summarize rteval- <date>-N.tar.bz2
第 9 章 使用 rteval
容器进行实时任务执行
Red Hat Enterprise Linux (RHEL) for Real Time 中的 rteval
(实时评估)容器可确保关键任务的低延迟执行。它测量各种系统负载下的计时器唤醒时间,以保持实时响应并确保及时的任务执行。
rteval
工具将测量过程(使用 cyclictest
或 rtla
)设置为高优先级任务。这种测量进程的优先级高于计算机上生成的负载。因此,rteval 容器会测量不同负载下实时任务的唤醒时间,确保系统可以有效地处理实时工作负载。
9.1. 为 rteval
容器测试主机
要在对延迟敏感的工作负载上运行 rteval
容器,您必须调整主机机器,因为容器技术不需要虚拟化堆栈中的其他内核。大多数适用于裸机的调优策略也适用于容器环境。
您必须使用 tuned-adm
及
文件中定义的默认参数来应用 realtime 配置集。
realtime
-variables.conf
realtime
配置集执行以下任务:
- 设置各种内核命令行选项。
- 检测非统一内存访问(NUMA)拓扑。
-
当存在多个 NUMA 节点时,将除每个节点的第一个 CPU 之外的所有 CPU 分配给
isolcpus
设置。
为 rteval
容器配置主机机器。
先决条件
- 主机在 Red Hat Enterprise Linux 9.6 及更新版本中运行。
-
已安装
tuned
和tuned-profiles-realtime
软件包。 -
tuned
服务正在运行。 -
podman
应用程序已安装并运行。
流程
安装所需的软件包:
sudo dnf install rteval kernel-rt podman -y
$ sudo dnf install rteval kernel-rt podman -y
Copy to Clipboard Copied! 查看安装的内核:
sudo grubby --info=ALL
$ sudo grubby --info=ALL index=0 kernel="/boot/vmlinuz-5.XX.0-XX.X.X.el9_6.x86_64+rt" args="ro crashkernel=2G-64G:256M,64G-:512M resume=UUID=3e14acf4-a359-4045-b8fc-990ff83743ec rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81 $tuned_params" root="/dev/mapper/rhel_rt--qe--11-root" initrd="/boot/initramfs-5.XX.0-XX.X.X.el9_6.x86_64+rt.img $tuned_initrd" title="Red Hat Enterprise Linux (5.XX.0-XX.X.X.el9_6.x86_64+rt) 9.6 (Plow)" id="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-5.XX.0-XX.X.X.el9_6.x86_64+rt" index=1 kernel="/boot/vmlinuz-5.XX.0-XX.X.X.el9_6.x86_64" args="ro crashkernel=2G-64G:256M,64G-:512M resume=UUID=3e14acf4-a359-4045-b8fc-990ff83743ec rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81 $tuned_params" root="/dev/mapper/rhel_rt--qe--11-root" initrd="/boot/initramfs-5.XX.0-XX.X.X.el9_6.x86_64.img $tuned_initrd" title="Red Hat Enterprise Linux (5.XX.0-XX.X.X.el9_6.x86_64) 9.6 (Plow)" id="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-5.XX.0-XX.X.X.el9_6.x86_64" index=2 kernel="/boot/vmlinuz-0-rescue-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" args="ro crashkernel=2G-64G:256M,64G-:512M resume=UUID=3e14acf4-a359-4045-b8fc-990ff83743ec rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81" root="/dev/mapper/rhel_rt--qe--11-root" initrd="/boot/initramfs-0-rescue-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.img" title="Red Hat Enterprise Linux (0-rescue-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX) 9.6 (Plow)" id="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-0-rescue"
Copy to Clipboard Copied! 将 Real Time 内核设置为默认内核:
select a in /boot/vmlinuz-*rt*; do grubby --set-default=$a; break; done
$ select a in /boot/vmlinuz-*rt*; do grubby --set-default=$a; break; done
Copy to Clipboard Copied! 使用
tuned-adm
应用realtime
配置集:sudo tuned-adm profile realtime
$ sudo tuned-adm profile realtime
Copy to Clipboard Copied! 重启主机机器:
sudo reboot
$ sudo reboot
Copy to Clipboard Copied!
验证
验证内核版本和调优参数:
sudo uname -r
$ sudo uname -r 5.XX.0-XX.X.X.el9_6.x86_64+rt
Copy to Clipboard Copied! sudo cat /proc/cmdline BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.XX.0-XX.X.X.el9_6.x86_64+rt root=/dev/mapper/rhel_rt--qe--11-root ro crashkernel=2G-64G:256M,64G-:512M resume=UUID=3e14acf4-a359-4045-b8fc-990ff83743ec rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81 skew_tick=1 tsc=reliable rcupdate.rcu_normal_after_boot=1 isolcpus=managed_irq,domain,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47 intel_pstate=disable nosoftlockup
$ sudo cat /proc/cmdline BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.XX.0-XX.X.X.el9_6.x86_64+rt root=/dev/mapper/rhel_rt--qe--11-root ro crashkernel=2G-64G:256M,64G-:512M resume=UUID=3e14acf4-a359-4045-b8fc-990ff83743ec rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81 skew_tick=1 tsc=reliable rcupdate.rcu_normal_after_boot=1 isolcpus=managed_irq,domain,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47 intel_pstate=disable nosoftlockup
Copy to Clipboard Copied!
9.2. 为基准结果测试裸机
在裸机上运行系统,无需额外软件,但基本操作系统和 rteval
容器除外。这样可确保系统针对低延迟性能进行了优化,结果不会受到其他软件或进程的影响。
先决条件
- 配置 NUMA 策略。如需更多信息,请参阅使用 systemd 配置 NUMA 策略。
流程
运行
rteval
容器有两种场景:在单个 NUMA 节点上:
rteval --duration 2h
$ rteval --duration 2h
Copy to Clipboard Copied! 在多个 NUMA 节点上运行
rteval
容器:rteval --duration 2h --loads-cpulist 0,1 --measurement-cpulist 2-47
$ rteval --duration 2h --loads-cpulist 0,1 --measurement-cpulist 2-47
Copy to Clipboard Copied!
完成裸机测试后,使用 rteval
容器作为基准来配置应用程序和服务的实际场景。
9.3. 使用容器放置优化 CPU 性能
使用实时配置集调整主机后,您可以通过选择将容器放置到特定 CPU 并调整容器运行时行为来进一步优化性能。使用这些策略,您可以探索 CPU 隔离和 cgroup
配置如何影响容器化工作负载中的延迟。
9.3.1. 在所有 CPU 上运行 podman
要使用 rteval
容器运行 podman
,请使用 tuned
realtime 配置集或自定义系统调整来调优您的系统。确定您是否需要对您要测量的场景进行 CPU 隔离。确保正确设置 CPU 隔离以避免在某些情况下运行容器时出现问题。
检查 /proc/cmdline
中的 isolcpus=
参数。如果没有设置 isolcpus
,您的系统不会隔离任何 CPU,您可以在所有 CPU 上运行容器。
先决条件
-
/proc/cmdline
中的isolcpus=
参数设为在所有 CPU 之间运行容器。 - 主机在 Red Hat Enterprise Linux 9.6 或更高版本中运行。
-
podman
服务正在运行。 -
rteval
容器已安装并运行。
流程
登录到
podman
registry:podman login registry.redhat.io
$ podman login registry.redhat.io
Copy to Clipboard Copied! 运行
rteval
容器。选择以下方法之一来运行容器:在单个 NUMA 节点框中的所有 CPU 上:
podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h'
$ podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h'
Copy to Clipboard Copied! 在多 NUMA 节点机器中:
podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --loads-cpulist 0,1 --measurement-cpulist 2-47
$ podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --loads-cpulist 0,1 --measurement-cpulist 2-47
Copy to Clipboard Copied! --pids-limit=0
kcompile
可在不达到容器运行时的默认限制的情况下运行。kcompile
是一个命令行工具,用于为当前运行的内核编译内核模块,而无需重建整个内核。--privileged
-
容器可以访问主机系统上的所有设备。这是
rteval
正确运行所必需的。
这些命令在所有可用节点上运行单个容器。tuned
服务管理主机性能优化,可让您在仅使用单个 CPU 时评估裸机性能。
验证
在新终端中,列出所有包括
rteval
容器的容器,以确保它正确运行:podman ps -a
$ podman ps -a
Copy to Clipboard Copied!
9.3.2. 运行带有分割 CPU 分配的 podman
您可以将不同的容器分配给不同的 CPU 集,以测试负载分离和测量。例如,当只有一个 NUMA 节点且您想要将负载和测量分隔到容器中时,您可以运行两个不同的容器。在这种情况下,两个容器都在每个 CPU 上运行,且没有分区用于调整。
示例命令:
加载容器 :
podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload'
$ podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload'
Copy to Clipboard Copied! 测量容器 :
podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlymeasure'
$ podman run -it --rm --privileged --pids-limit=0 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlymeasure'
Copy to Clipboard Copied!
对于在有多个 NUMA 节点或手动分区机器的框上分区的情况,示例命令有:
加载容器 :
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 0,1 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 0,1'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 0,1 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 0,1'
Copy to Clipboard Copied! 测量容器 :
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 2-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 2-47'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 2-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 2-47'
Copy to Clipboard Copied!
运行这些命令后,加载容器会在内务核上生成负载,而测量容器则在 isol_cpu
集上运行。
如果没有配置分区,则一个容器会在系统上的所有 CPU 之间生成负载,另一个容器会在所有节点上测量延迟。
在这两种情况下,负载和测量会在两个容器之间成功分开。
9.3.3. 在实时配置集中调整每个 NUMA 的内务操作
您可以在实时配置集中调整每个 NUMA 节点设置的内务 CPU。这通过确保内务务任务在 NUMA 节点之间平均分配,从而优化系统性能。
这对具有多个 NUMA 节点的系统特别有用,因为它有助于减少争用并改进整体性能。
默认 realtime tuned
配置集为每个 NUMA 节点保留一个内务 CPU (hk_per_numa=1)
。如果需要更多可用于容器工作负载的 CPU,您可以修改此行为。
先决条件
- 主机在 Red Hat Enterprise Linux 9.6 或更高版本中运行。
-
tuned
服务正在运行。 -
rteval
容器已安装并运行。 -
podman
服务正在运行。 -
已安装
tuned-profiles-realtime
软件包。
流程
修改
realtime-variables.conf
文件,以调整每个 NUMA 节点设置的内务 CPU。在文本编辑器中打开位于
/etc/tuned
的realtime-variables.conf
文件:sudo vi /etc/tuned/realtime-variables.conf
$ sudo vi /etc/tuned/realtime-variables.conf
Copy to Clipboard Copied! 找到
isolated_cores
变量。默认情况下,这设置为1
,这意味着每个 NUMA 节点保留了一个内核,用于隔离或非内部维护。您可以增加这个值,但它必须小于每个 NUMA 节点的 CPU 总数。以下示例将每个 NUMA 节点有 24 个内核的系统中将
isolated_cores
设置为3
:isolated_cores=${f:calc_isolated_cores:3}
isolated_cores=${f:calc_isolated_cores:3}
Copy to Clipboard Copied!
- 保存您的更改并关闭该文件。
重新应用
tuned
实时配置集:sudo tuned-adm profile realtime
$ sudo tuned-adm profile realtime
Copy to Clipboard Copied!
这会导致测试过程中产生 6 个 CPU (每个 NUMA 节点 3)生成负载,而系统则保留了 isolcpus
集的剩余内核。此配置用于测量。在某些情况下,混合优先级配置可能会在自定义拓扑上部署容器,而不是在设置 isolcpus
上。
或者,您可以手动指定自定义 CPU 范围,而不依赖于每个节点的自动计数。这样可确保对隔离内核的完整控制,从而更易于使用非统一拓扑或专用 CPU 布局调整系统。
验证
-
验证
realtime-variables.conf
文件中的更改。 - 重启系统以应用更改。
查看
/proc/cmdline
文件,以确认isolcpus
设置:cat /proc/cmdline BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.XX.X-XX.X.X.el9_6.x86_64+rt root=/dev/mapper/rhel_rt--qe--11-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=UUID=00cbf36d-ffaa-4285-a381-5c1d868eb3e3 rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81 skew_tick=1 tsc=reliable rcupdate.rcu_normal_after_boot=1 isolcpus=managed_irq,domain,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47 intel_pstate=disable nosoftlockup
$ cat /proc/cmdline BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.XX.X-XX.X.X.el9_6.x86_64+rt root=/dev/mapper/rhel_rt--qe--11-root ro crashkernel=1G-4G:192M,4G-64G:256M,64G-:512M resume=UUID=00cbf36d-ffaa-4285-a381-5c1d868eb3e3 rd.lvm.lv=rhel_rt-qe-11/root rd.lvm.lv=rhel_rt-qe-11/swap console=ttyS0,115200n81 skew_tick=1 tsc=reliable rcupdate.rcu_normal_after_boot=1 isolcpus=managed_irq,domain,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47 intel_pstate=disable nosoftlockup
Copy to Clipboard Copied!
9.3.4. 在隔离的 CPU 之间分散多个容器
要在隔离的 CPU 之间运行多个容器,您可以使用 --cpuset-cpus
选项指定每个容器应使用哪些 CPU。这会划分多个隔离的 CPU 的负载,从而提高性能并减少争用。
您可以将设置在多个容器间划分的 isolcpus
模拟以下任务:
- 并发对延迟敏感的任务。
- 跨分区系统进行多个负载。
9.3.4.1. 模拟并发对延迟敏感的任务
要模拟并发对延迟敏感的任务,您可以将特定的隔离 CPU 分配给每个容器。以下示例演示了如何在不同的 CPU 集间配置和运行容器。
在 CPU 0-6 上运行一个容器,在 CPU 7-28 上运行另一个容器,在 CPU 29-47 上运行第三个容器。使用以下命令:
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 0-6 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 0-6'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 0-6 registry.redhat.io/rhel10/rteval \
/bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 0-6'
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 7-28 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 7-28'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 7-28 registry.redhat.io/rhel10/rteval \
/bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 7-28'
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 29-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 29-47'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 29-47 registry.redhat.io/rhel10/rteval \
/bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 29-47'
9.3.4.2. 在分区系统中模拟多个负载
在非隔离 CPU 集上启动 rteval
负载生成器。接下来,在 isolcpus
设置的一部分上模拟高吞吐量的应用,如高速数据库容器。在本例中,CPU 7-28 用于代表高速数据库容器。在单独的终端会话中运行以下命令以启动负载。
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 0-6 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 0-6'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 0-6 registry.redhat.io/rhel10/rteval \
/bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 0-6'
然后,在一个单独的终端中,在隔离 CPU 的子集上生成一些负载:
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 20-30 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 20-30'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 20-30 registry.redhat.io/rhel10/rteval \
/bin/bash -c 'rteval --duration 2h --onlyload --loads-cpulist 20-30'
现在,要在剩余的 CPU 上运行测量线程,有两个选项。您可以将隔离 CPU 的两个剩余子集部署到单独的容器,或者运行使用两个剩余的 CPU 子集的单个测量容器。
选项 1:部署两个测量容器
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 7-19 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 7-19'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 7-19 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 7-19'
Copy to Clipboard Copied! podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 31-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 31-47'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 31-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 31-47'
Copy to Clipboard Copied! 选项 2:部署单个测量容器
podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 7-19,31-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 7-19,31-47'
$ podman run -it --rm --privileged --pids-limit=0 --cpuset-cpus 7-19,31-47 registry.redhat.io/rhel10/rteval \ /bin/bash -c 'rteval --duration 2h --noload --measurement-cpulist 7-19,31-47'
Copy to Clipboard Copied!
第 10 章 使用 cgroupfs 手动管理 cgroup
您可以通过在 cgroupfs
虚拟文件系统中创建目录来管理系统上的 cgroup
层次结构。文件系统默认挂载到 /sys/fs/cgroup/
目录中,您可以在专用控制文件中指定所需的配置。
通常,红帽建议您使用 systemd
来控制系统资源的使用。您应该只在特殊情况下手动配置 cgroups
虚拟文件系统。例如,当您需要使用在 cgroup-v2
层次结构中没有对应的 cgroup-v1
控制器时。
10.1. 在 cgroups-v2 文件系统中创建 cgroup 和启用控制器
您可以通过创建和删除目录,并通过写入 cgroup
虚拟文件系统中的文件来管理 控制组 (cgroups
)。文件系统默认挂载到 /sys/fs/cgroup/
目录中。要使用 cgroups
控制器中的设置,您还需要为子 cgroup
启用所需的控制器。在默认情况下,root cgroup
会为其子 cgroups
启用 memory
和 pids
。因此,您必须在 /sys/fs/
root cgroup 中创建至少两个级别的子 cgroup
/cgroup
。这样,您可以选择从子 cgroup
中删除 memory
和 pids
控制器,并更好地组织 cgroup
文件。
先决条件
- 您在系统上具有 root 权限。
流程
创建
/sys/fs/cgroup/Example/
目录:mkdir /sys/fs/cgroup/Example/
# mkdir /sys/fs/cgroup/Example/
Copy to Clipboard Copied! /sys/fs/cgroup/Example/
目录定义一个子组。当您创建/sys/fs/cgroup/Example/
目录时,目录中会自动创建一些cgroups-v2
接口文件。/sys/fs/cgroup/Example/
目录还包含memory
和pids
控制器的特定于控制器的文件。可选:检查新创建的子控制组:
ll /sys/fs/cgroup/Example/
# ll /sys/fs/cgroup/Example/ -r—r—r--. 1 root root 0 Jun 1 10:33 cgroup.controllers -r—r—r--. 1 root root 0 Jun 1 10:33 cgroup.events -rw-r—r--. 1 root root 0 Jun 1 10:33 cgroup.freeze -rw-r--r--. 1 root root 0 Jun 1 10:33 cgroup.procs … -rw-r—r--. 1 root root 0 Jun 1 10:33 cgroup.subtree_control -r—r—r--. 1 root root 0 Jun 1 10:33 memory.events.local -rw-r—r--. 1 root root 0 Jun 1 10:33 memory.high -rw-r—r--. 1 root root 0 Jun 1 10:33 memory.low … -r—r—r--. 1 root root 0 Jun 1 10:33 pids.current -r—r—r--. 1 root root 0 Jun 1 10:33 pids.events -rw-r—r--. 1 root root 0 Jun 1 10:33 pids.max
Copy to Clipboard Copied! 示例输出显示常规
cgroup
控制接口文件,如cgroup.procs
或cgroup.controllers
。无论启用控制器是什么,这些文件都是所有控制组通用的。memory.high
和pids.max
等文件与memory
和pids
控制器有关,它们是 root 控制组 (/sys/fs/cgroup/
) ,默认情况下会被systemd
启用。默认情况下,新创建的子组从父
cgroup
继承所有设置。在这种情况下,来自 rootcgroup
没有限制。验证
/sys/fs/cgroup/cgroup.controllers
文件中是否有所需的控制器:cat /sys/fs/cgroup/cgroup.controllers cpuset cpu io memory hugetlb pids rdma
# cat /sys/fs/cgroup/cgroup.controllers cpuset cpu io memory hugetlb pids rdma
Copy to Clipboard Copied! 启用所需的控制器。在本例中是
cpu
和cpuset
控制器:echo "+cpu" >> /sys/fs/cgroup/cgroup.subtree_control echo "+cpuset" >> /sys/fs/cgroup/cgroup.subtree_control
# echo "+cpu" >> /sys/fs/cgroup/cgroup.subtree_control # echo "+cpuset" >> /sys/fs/cgroup/cgroup.subtree_control
Copy to Clipboard Copied! 这些命令为
/sys/fs/cgroup/
root 控制组的直接子组启用cpu
和cpuset
控制器。包含新创建的Example
控制组。子组 是可以指定进程,并根据标准对每个进程应用控制检查的位置。用户可以在任意级别读取
cgroup.subtree_control
文件的内容,以了解即时子组中哪些控制器可用于启用。注意默认情况下,根控制组中的
/sys/fs/cgroup/cgroup.subtree_control
文件包含memory
和pids
控制器。为
Example
控制组群的子cgroup
启用所需的控制器:echo "+cpu +cpuset" >> /sys/fs/cgroup/Example/cgroup.subtree_control
# echo "+cpu +cpuset" >> /sys/fs/cgroup/Example/cgroup.subtree_control
Copy to Clipboard Copied! 这些命令可确保,直接的子组仅具有与 CPU 时间分发相关的控制器,而不是
memory
或pids
控制器。创建
/sys/fs/cgroup/Example/tasks/
目录:mkdir /sys/fs/cgroup/Example/tasks/
# mkdir /sys/fs/cgroup/Example/tasks/
Copy to Clipboard Copied! /sys/fs/cgroup/Example/tasks/
目录定义了一个子组,它带有只与cpu
和cpuset
控制器相关的文件。现在,您可以将进程分配到此控制组,并将cpu
和cpuset
控制器选项用于您的进程。可选:检查子控制组:
ll /sys/fs/cgroup/Example/tasks
# ll /sys/fs/cgroup/Example/tasks -r—r—r--. 1 root root 0 Jun 1 11:45 cgroup.controllers -r—r—r--. 1 root root 0 Jun 1 11:45 cgroup.events -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.freeze -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.max.depth -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.max.descendants -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.procs -r—r—r--. 1 root root 0 Jun 1 11:45 cgroup.stat -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.subtree_control -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.threads -rw-r—r--. 1 root root 0 Jun 1 11:45 cgroup.type -rw-r—r--. 1 root root 0 Jun 1 11:45 cpu.max -rw-r—r--. 1 root root 0 Jun 1 11:45 cpu.pressure -rw-r—r--. 1 root root 0 Jun 1 11:45 cpuset.cpus -r—r—r--. 1 root root 0 Jun 1 11:45 cpuset.cpus.effective -rw-r—r--. 1 root root 0 Jun 1 11:45 cpuset.cpus.partition -rw-r—r--. 1 root root 0 Jun 1 11:45 cpuset.mems -r—r—r--. 1 root root 0 Jun 1 11:45 cpuset.mems.effective -r—r—r--. 1 root root 0 Jun 1 11:45 cpu.stat -rw-r—r--. 1 root root 0 Jun 1 11:45 cpu.weight -rw-r—r--. 1 root root 0 Jun 1 11:45 cpu.weight.nice -rw-r—r--. 1 root root 0 Jun 1 11:45 io.pressure -rw-r—r--. 1 root root 0 Jun 1 11:45 memory.pressure
Copy to Clipboard Copied!
cpu
控制器只有在相关子控制组至少有 2 个在单个 CPU 上竞争时间的进程时,才会被激活 。
验证
可选:确认您是否已创建了一个只有所需的控制器处于活跃状态的新的
cgroup
:cat /sys/fs/cgroup/Example/tasks/cgroup.controllers cpuset cpu
# cat /sys/fs/cgroup/Example/tasks/cgroup.controllers cpuset cpu
Copy to Clipboard Copied!
10.2. 通过调整 CPU 权重来控制应用程序的 CPU 时间分布
您需要为 cpu
控制器的相关文件分配值,以控制分发到特定 cgroup 树下应用程序的 CPU 时间。
先决条件
- 您在系统上具有 root 权限。
- 您有要控制 CPU 时间分布的应用程序。
-
您已挂载了
cgroups-v2
文件系统。 您在
/sys/fs/cgroup/
根控制组 中创建了两级 子控制组,如下例所示:… ├── Example │ ├── g1 │ ├── g2 │ └── g3 …
… ├── Example │ ├── g1 │ ├── g2 │ └── g3 …
Copy to Clipboard Copied! -
您已在父控制组和子控制组中启用
cpu
控制器,类似于在 cgroups-v2 文件系统中创建 cgroups 并启用控制器。
流程
配置所需的 CPU 权重,以便在控制组内实现资源限制:
echo "150" > /sys/fs/cgroup/Example/g1/cpu.weight echo "100" > /sys/fs/cgroup/Example/g2/cpu.weight echo "50" > /sys/fs/cgroup/Example/g3/cpu.weight
# echo "150" > /sys/fs/cgroup/Example/g1/cpu.weight # echo "100" > /sys/fs/cgroup/Example/g2/cpu.weight # echo "50" > /sys/fs/cgroup/Example/g3/cpu.weight
Copy to Clipboard Copied! 将应用程序的 PID 添加到
g1
、g2
和g3
子组中:echo "33373" > /sys/fs/cgroup/Example/g1/cgroup.procs echo "33374" > /sys/fs/cgroup/Example/g2/cgroup.procs echo "33377" > /sys/fs/cgroup/Example/g3/cgroup.procs
# echo "33373" > /sys/fs/cgroup/Example/g1/cgroup.procs # echo "33374" > /sys/fs/cgroup/Example/g2/cgroup.procs # echo "33377" > /sys/fs/cgroup/Example/g3/cgroup.procs
Copy to Clipboard Copied! 这些命令确保所需的应用程序成为
Example/g*/
子 cgroup 的成员,并根据这些 cgroup 的配置获得其分配的 CPU 时间。已运行进程的子 cgroup (
g1
,g2
,g3
) 的权重在父 cgroup(Example
)级别上相加。然后根据分配的权重按比例分配 CPU 资源。因此,当所有进程在同一时间运行时,内核会根据分配的 cgroup 的
cpu.weight
文件,为每个进程分配相应的 CPU 时间:子 cgroup cpu.weight
文件CPU 时间分配 g1
150
~50% (150/300)
g2
100
~33% (100/300)
g3
50
~16% (50/300)
cpu.weight
控制器文件的值不是一个百分比。如果一个进程停止运行,使 cgroup
g2
没有运行进程,则计算将省略 cgroupg2
,仅计算 cgroupg1
和g3
的帐户权重:子 cgroup cpu.weight
文件CPU 时间分配 g1
150
~75% (150/200)
g3
50
~25% (50/200)
重要如果子 cgroup 有多个正在运行的进程,则分配给 cgroup 的 CPU 时间在其成员进程中平均分配。
验证
验证应用程序是否运行在指定的控制组中:
cat /proc/33373/cgroup /proc/33374/cgroup /proc/33377/cgroup 0::/Example/g1 0::/Example/g2 0::/Example/g3
# cat /proc/33373/cgroup /proc/33374/cgroup /proc/33377/cgroup 0::/Example/g1 0::/Example/g2 0::/Example/g3
Copy to Clipboard Copied! 命令输出显示了运行在
Example/g*/
子 cgroup 中指定的应用程序的进程。检查节流应用程序的当前 CPU 消耗:
top
# top top - 05:17:18 up 1 day, 18:25, 1 user, load average: 3.03, 3.03, 3.00 Tasks: 95 total, 4 running, 91 sleeping, 0 stopped, 0 zombie %Cpu(s): 18.1 us, 81.6 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.3 hi, 0.0 si, 0.0 st MiB Mem : 3737.0 total, 3233.7 free, 132.8 used, 370.5 buff/cache MiB Swap: 4060.0 total, 4060.0 free, 0.0 used. 3373.1 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 33373 root 20 0 18720 1748 1460 R 49.5 0.0 415:05.87 sha1sum 33374 root 20 0 18720 1756 1464 R 32.9 0.0 412:58.33 sha1sum 33377 root 20 0 18720 1860 1568 R 16.3 0.0 411:03.12 sha1sum 760 root 20 0 416620 28540 15296 S 0.3 0.7 0:10.23 tuned 1 root 20 0 186328 14108 9484 S 0.0 0.4 0:02.00 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.01 kthread ...
Copy to Clipboard Copied! 注意为了清晰地说明,所有进程都在一个 CPU 上运行。当在多个 CPU 上使用时,CPU 权重会应用同样的原则。
请注意,
PID 33373
、PID 33374
和PID 33377
的 CPU 资源是根据您分配给子 cgroup 的 150、100 和 50 权重分配的。权重对应于每个应用程序分配的 CPU 时间的大约 50%、33% 和 16%。
10.3. 挂载 cgroups-v1
在启动过程中,RHEL 10 默认挂载 cgroup-v2
虚拟文件系统。要在限制应用程序的资源中使用 cgroup-v1
功能,请手动配置系统。
内核中完全启用了 cgroup-v1
和 cgroup-v2
。从内核的角度来看,没有默认的控制组版本,并且由 systemd
决定在启动时挂载。
先决条件
- 您在系统上具有 root 权限。
流程
将系统配置为,在系统引导过程中,默认由
systemd
系统和服务管理器挂载cgroups-v1
:grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
# grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
Copy to Clipboard Copied! 这会在当前引导条目中添加所需的内核命令行参数。
在所有内核引导条目中添加相同的参数:
grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
# grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
Copy to Clipboard Copied! - 重启系统以使更改生效。
验证
验证
cgroups-v1
文件系统是否已挂载:mount -l | grep cgroup
# mount -l | grep cgroup tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,size=4096k,nr_inodes=1024,mode=755,inode64) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset) cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio) cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices) cgroup on /sys/fs/cgroup/misc type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,misc) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer) cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
Copy to Clipboard Copied! 与各种
cgroup-v1
控制器对应的cgroups-v1
文件系统已被成功挂载到/sys/fs/cgroup/
目录中。检查
/sys/fs/cgroup/
目录的内容:ll /sys/fs/cgroup/
# ll /sys/fs/cgroup/ dr-xr-xr-x. 10 root root 0 Mar 16 09:34 blkio lrwxrwxrwx. 1 root root 11 Mar 16 09:34 cpu → cpu,cpuacct lrwxrwxrwx. 1 root root 11 Mar 16 09:34 cpuacct → cpu,cpuacct dr-xr-xr-x. 10 root root 0 Mar 16 09:34 cpu,cpuacct dr-xr-xr-x. 2 root root 0 Mar 16 09:34 cpuset dr-xr-xr-x. 10 root root 0 Mar 16 09:34 devices dr-xr-xr-x. 2 root root 0 Mar 16 09:34 freezer dr-xr-xr-x. 2 root root 0 Mar 16 09:34 hugetlb dr-xr-xr-x. 10 root root 0 Mar 16 09:34 memory dr-xr-xr-x. 2 root root 0 Mar 16 09:34 misc lrwxrwxrwx. 1 root root 16 Mar 16 09:34 net_cls → net_cls,net_prio dr-xr-xr-x. 2 root root 0 Mar 16 09:34 net_cls,net_prio lrwxrwxrwx. 1 root root 16 Mar 16 09:34 net_prio → net_cls,net_prio dr-xr-xr-x. 2 root root 0 Mar 16 09:34 perf_event dr-xr-xr-x. 10 root root 0 Mar 16 09:34 pids dr-xr-xr-x. 2 root root 0 Mar 16 09:34 rdma dr-xr-xr-x. 11 root root 0 Mar 16 09:34 systemd
Copy to Clipboard Copied! 默认情况下,
/sys/fs/cgroup/
目录(也称为 root 控制组 )包含特定于控制器的目录,如cpuset
。另外,还有一些与systemd
相关的目录。
10.4. 使用 cgroups-v1 为应用程序设置 CPU 限制
要使用 控制组版本 1 ( cgroups-v1
)配置对应用程序的 CPU 限制,请使用 /sys/fs/
虚拟文件系统。
先决条件
- 您在系统上具有 root 权限。
- 您有一个安装在系统上的限制其 CPU 消耗的应用程序。
您将系统配置为,在系统引导过程中,默认由
systemd
系统和服务管理器挂载cgroups-v1
:grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
# grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"
Copy to Clipboard Copied! 这会在当前引导条目中添加所需的内核命令行参数。
流程
识别您要限制 CPU 消耗的应用程序的进程 ID (PID):
top
# top top - 11:34:09 up 11 min, 1 user, load average: 0.51, 0.27, 0.22 Tasks: 267 total, 3 running, 264 sleeping, 0 stopped, 0 zombie %Cpu(s): 49.0 us, 3.3 sy, 0.0 ni, 47.5 id, 0.0 wa, 0.2 hi, 0.0 si, 0.0 st MiB Mem : 1826.8 total, 303.4 free, 1046.8 used, 476.5 buff/cache MiB Swap: 1536.0 total, 1396.0 free, 140.0 used. 616.4 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6955 root 20 0 228440 1752 1472 R 99.3 0.1 0:32.71 sha1sum 5760 jdoe 20 0 3603868 205188 64196 S 3.7 11.0 0:17.19 gnome-shell 6448 jdoe 20 0 743648 30640 19488 S 0.7 1.6 0:02.73 gnome-terminal- 1 root 20 0 245300 6568 4116 S 0.3 0.4 0:01.87 systemd 505 root 20 0 0 0 0 I 0.3 0.0 0:00.75 kworker/u4:4-events_unbound ...
Copy to Clipboard Copied! PID 6955
的sha1sum
示例应用程序消耗大量的 CPU 资源。在
cpu
资源控制器目录中创建子目录:mkdir /sys/fs/cgroup/cpu/Example/
# mkdir /sys/fs/cgroup/cpu/Example/
Copy to Clipboard Copied! 此目录代表控制组,您可以在其中放置特定进程,并向进程应用某些 CPU 限制。同时,目录中将创建多个
cgroups-v1
接口文件和cpu
特定于控制器的文件。可选:检查新创建的控制组:
ll /sys/fs/cgroup/cpu/Example/
# ll /sys/fs/cgroup/cpu/Example/ -rw-r—r--. 1 root root 0 Mar 11 11:42 cgroup.clone_children -rw-r—r--. 1 root root 0 Mar 11 11:42 cgroup.procs -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.stat -rw-r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage_all -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage_percpu -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage_percpu_sys -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage_percpu_user -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage_sys -r—r—r--. 1 root root 0 Mar 11 11:42 cpuacct.usage_user -rw-r—r--. 1 root root 0 Mar 11 11:42 cpu.cfs_period_us -rw-r—r--. 1 root root 0 Mar 11 11:42 cpu.cfs_quota_us -rw-r—r--. 1 root root 0 Mar 11 11:42 cpu.rt_period_us -rw-r—r--. 1 root root 0 Mar 11 11:42 cpu.rt_runtime_us -rw-r—r--. 1 root root 0 Mar 11 11:42 cpu.shares -r—r—r--. 1 root root 0 Mar 11 11:42 cpu.stat -rw-r—r--. 1 root root 0 Mar 11 11:42 notify_on_release -rw-r—r--. 1 root root 0 Mar 11 11:42 tasks
Copy to Clipboard Copied! cpuacct.usage
,cpu.cfs._period_us
等文件代表特定的配置和/或限制,可以为Example
控制组中的进程设置它们。请注意,文件名是以它们所属的控制组控制器的名称为前缀的。默认情况下,新创建的控制组继承对系统整个 CPU 资源的访问权限,且无限制。
为控制组群配置 CPU 限制:
echo "1000000" > /sys/fs/cgroup/cpu/Example/cpu.cfs_period_us echo "200000" > /sys/fs/cgroup/cpu/Example/cpu.cfs_quota_us
# echo "1000000" > /sys/fs/cgroup/cpu/Example/cpu.cfs_period_us # echo "200000" > /sys/fs/cgroup/cpu/Example/cpu.cfs_quota_us
Copy to Clipboard Copied! -
cpu.cfs_period_us
文件代表控制组对 CPU 资源的访问多久需要重新分配一次。时间段为微秒(µs, "us")。上限为 1000 000 微秒,下限为 1000 微秒。 cpu.cfs_quota_us
文件表示控制组中的所有进程在一段时间内共同运行的总时间(以微秒为单位),如cpu.cfs_period_us
所定义的那样。当控制组中的进程在单个时间段内用完配额指定的所有时间时,它们在该时段的剩余时间内受到限制,并且不允许运行,直到下一个时间段为止。下限为 1000 微秒。上面的示例命令设定 CPU 时间限值,使得
Example
控制组中的所有进程仅能每 1 秒(cpu.cfs_quota_us
定义)每 1 秒(由cpu.cfs_period_us
定义)运行 0.2 秒。
-
可选:验证限制:
cat /sys/fs/cgroup/cpu/Example/cpu.cfs_period_us /sys/fs/cgroup/cpu/Example/cpu.cfs_quota_us 1000000 200000
# cat /sys/fs/cgroup/cpu/Example/cpu.cfs_period_us /sys/fs/cgroup/cpu/Example/cpu.cfs_quota_us 1000000 200000
Copy to Clipboard Copied! 将应用程序的 PID 添加到
Example
控制组群中:echo "6955" > /sys/fs/cgroup/cpu/Example/cgroup.procs
# echo "6955" > /sys/fs/cgroup/cpu/Example/cgroup.procs
Copy to Clipboard Copied! 此命令可确保特定的应用成为
Example
控制组的一员,并且不超过为Example
控制组配置的 CPU 限制。PID 必须代表系统中一个存在的进程。此处的PID 6955
被分配给sha1sum /dev/zero &
进程,用来演示cpu
控制器的用例。
验证
验证应用程序是否在指定的控制组群中运行:
cat /proc/6955/cgroup 12:cpuset:/ 11:hugetlb:/ 10:net_cls,net_prio:/ 9:memory:/user.slice/user-1000.slice/user@1000.service 8:devices:/user.slice 7:blkio:/ 6:freezer:/ 5:rdma:/ 4:pids:/user.slice/user-1000.slice/user@1000.service 3:perf_event:/ 2:cpu,cpuacct:/Example 1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
# cat /proc/6955/cgroup 12:cpuset:/ 11:hugetlb:/ 10:net_cls,net_prio:/ 9:memory:/user.slice/user-1000.slice/user@1000.service 8:devices:/user.slice 7:blkio:/ 6:freezer:/ 5:rdma:/ 4:pids:/user.slice/user-1000.slice/user@1000.service 3:perf_event:/ 2:cpu,cpuacct:/Example 1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service
Copy to Clipboard Copied! 应用程序的进程在
Example
控制组中运行,后者将 CPU 限制应用到应用程序的进程。确定节流应用程序的当前 CPU 消耗:
top
# top top - 12:28:42 up 1:06, 1 user, load average: 1.02, 1.02, 1.00 Tasks: 266 total, 6 running, 260 sleeping, 0 stopped, 0 zombie %Cpu(s): 11.0 us, 1.2 sy, 0.0 ni, 87.5 id, 0.0 wa, 0.2 hi, 0.0 si, 0.2 st MiB Mem : 1826.8 total, 287.1 free, 1054.4 used, 485.3 buff/cache MiB Swap: 1536.0 total, 1396.7 free, 139.2 used. 608.3 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6955 root 20 0 228440 1752 1472 R 20.6 0.1 47:11.43 sha1sum 5760 jdoe 20 0 3604956 208832 65316 R 2.3 11.2 0:43.50 gnome-shell 6448 jdoe 20 0 743836 31736 19488 S 0.7 1.7 0:08.25 gnome-terminal- 505 root 20 0 0 0 0 I 0.3 0.0 0:03.39 kworker/u4:4-events_unbound 4217 root 20 0 74192 1612 1320 S 0.3 0.1 0:01.19 spice-vdagentd ...
Copy to Clipboard Copied! 请注意,
PID 6955
的 CPU 消耗从 99% 减少到 20%。
cpu.cfs_period_us
和 cpu.cfs_quota_us
的 cgroups-v2
对应项是 cpu.max
文件。cpu.max
文件可以通过 cpu
控制器获得。
使用控制组(cgroups
)内核功能,您可以控制应用程序的资源使用情况来更有效地使用它们。
您可以为以下任务使用 cgroups
:
- 为系统资源分配设置限制。
- 将硬件资源优先分配给特定的进程。
- 防止某些进程获取硬件资源。
10.5. 控制组简介
使用 控制组 Linux 内核功能,您可以将进程组织为按层排序的组 - cgroups
。您可以通过为 cgroup
虚拟文件系统提供结构来定义层次结构(控制组树),默认挂载到 /sys/fs/cgroup/
目录。
systemd
服务管理器使用 cgroups
来组织它管理的所有单元和服务。您可以通过创建和删除 /sys/fs/cgroup/
目录中的子目录来手动管理 cgroups
的层次结构。
然后,内核中的资源控制器通过限制、优先处理或分配这些进程的系统资源来在 cgroups
中修改进程的行为。这些资源包括以下内容:
- CPU 时间
- 内存
- 网络带宽
- 这些资源的组合
cgroups
的主要用例是聚合系统进程,并在应用程序和用户之间划分硬件资源。这样可以提高环境的效率、稳定性和安全性。
- 控制组群版本 1
控制组版本 1 (
cgroups-v1
) 提供按资源控制器层次结构。每个资源(如 CPU、内存或 I/O)都有自己的控制组层次结构。您可以组合不同的控制组层次结构,使一个控制器可以与另一个控制器协调管理各自的资源。但是,当两个控制器属于不同的进程层次结构时,协调受到限制。cgroups-v1
控制器的开发时间跨度很大,导致其控制文件的行为和命名不一致。- 控制组群版本 2
控制组版本 2 (
cgroups-v2
)提供单一控制组层次结构,用于挂载所有资源控制器。控制文件行为和命名在不同控制器之间保持一致。
10.6. 内核资源控制器简介
内核资源控制器启用控制组的功能。RHEL 10 支持用于 控制组版本 1 (cgroups-v1
)和 控制组版本 2 (cgroups-v2
)的各种控制器。
资源控制器也称为控制组子系统,是一个代表单一资源的内核子系统,如 CPU 时间、内存、网络带宽或磁盘 I/O。Linux 内核提供由 systemd
服务管理器自动挂载的一系列资源控制器。
您可以在 /proc/cgroups
文件中找到当前挂载的资源控制器的列表。
cgroups-v1
提供的控制器:
blkio
- 设置对块设备的输入/输出访问的限制。
cpu
-
调整控制组任务的默认调度程序的参数。
cpu
控制器与cpuacct
控制器一起挂载在同一挂载上。 cpuacct
-
创建控制组群中任务所使用的有关 CPU 资源的自动报告。
cpuacct
控制器与cpu
控制器一起挂载在同一挂载上。 cpuset
- 将控制组任务限制为仅在指定 CPU 子集上运行,并指示任务仅使用指定内存节点上的内存。
devices
- 控制控制组群中任务对设备的访问。
freezer
- 暂停或恢复控制组中的任务。
内存
- 设置控制组中任务对内存使用的限制,并对这些任务使用的内存资源生成自动报告。
net_cls
-
使用类标识符(
classid
)标记网络数据包,使 Linux 流量控制器(tc
命令)能够识别来自特定控制组任务的数据包。net_cls
子系统net_filter
(iptables) 也可使用此标签对此类数据包执行操作。net_filter
使用防火墙标识符(fwid
)标记网络套接字,它允许 Linux 防火墙识别来自特定控制组任务的数据包(通过使用iptables
命令)。 net_prio
- 设置网络流量的优先级。
pids
- 为控制组群中的多个进程及其子进程设置限制。
perf_event
-
通过
perf
性能监控和报告工具对监控的任务进行分组。 rdma
- 对控制组群中远程直接内存访问/InfiniBand 特定资源设置限制。
hugetlb
- 按控制组群中的任务限制大型虚拟内存页的使用。
cgroups-v2
提供的控制器:
io
- 设置对块设备的输入/输出访问的限制。
内存
- 设置控制组中任务对内存使用的限制,并对这些任务使用的内存资源生成自动报告。
pids
- 为控制组群中的多个进程及其子进程设置限制。
rdma
- 对控制组群中远程直接内存访问/InfiniBand 特定资源设置限制。
cpu
- 调整控制组任务的默认调度程序的参数,并创建对控制组中任务所使用的 CPU 资源的自动报告。
cpuset
-
将控制组任务限制为仅在指定 CPU 子集上运行,并指示任务仅使用指定内存节点上的内存。仅支持具有新分区功能的核心功能(
cpus{,.effective}
,mems{,.effective}
)。 perf_event
-
通过
perf
性能监控和报告工具对监控的任务进行分组。perf_event
在 v2 层次结构上自动启用。
资源控制器可以在 cgroups-v1
层次结构或 cgroups-v2
层次结构中使用,不能同时在两者中使用。
10.7. 命名空间简介
命名空间为组织和识别软件对象创建单独的空间。这使得它们不会互相影响。因此,每个软件对象都包含其自己的一组资源,如挂载点、网络设备或主机名,即使它们共享同样的系统。
使用命名空间的最常见技术是容器。
对特定全局资源的更改仅对该命名空间中的进程可见,不影响系统或其他命名空间的其余部分。
要检查进程所属的命名空间,您可以在 /proc/<PID>/ns/
目录中检查符号链接。
Namespace | Isolates |
---|---|
Mount | 挂载点 |
UTS | 主机名和 NIS 域名 |
IPC | 系统 V IPC, POSIX 消息队列 |
PID | 进程 ID |
Network | 网络设备、堆栈、端口等 |
User | 用户和组群 ID |
Control groups | 控制组群根目录 |
第 11 章 在 RHEL for Real Time 上设置 CPU 关联性
系统中的所有线程和中断源都有一个处理器关联性属性。操作系统调度程序使用此信息来确定要在 CPU 上运行的线程和中断。通过设置处理器关联性以及有效的策略和优先级设置,您可以实现最大可能的性能。应用程序始终与其他进程竞争资源(特别是 CPU 时间)。根据应用程序,相关的线程通常在同一核心上运行。或者,可以将一个应用程序线程分配给一个内核。
执行多任务的系统容易容易出错。当较低优先级的应用程序位于代码的关键部分时,即使高优先级的应用程序也会延迟执行。在低优先级应用程序退出 critical 部分后,内核可以安全地抢占低优先级应用程序,并在处理器上调度高优先级应用程序。另外,因为缓存无效,将进程从一个 CPU 迁移到另一个 CPU 可能会昂贵。RHEL for Real Time 包括解决其中一些问题的工具,并允许更好地控制延迟。
关联性表示为位掩码,掩码中的每个位代表一个 CPU 内核。如果位设置为 1,则线程或中断在该内核上运行;如果 0,则线程或中断将排除在核心上运行。关联性位掩码的默认值为 all,这意味着线程或中断可以在系统的任何核心上运行。
默认情况下,进程可以在任何 CPU 上运行。但是,通过更改进程的关联性,您可以定义要在预先确定的 CPU 集合上运行的进程。子进程继承其父进程的 CPU 相关性。
设置以下典型的关联性设置可达到最大可能的性能:
- 将单个 CPU 内核用于所有系统进程,并将应用程序设置为在内核的其余部分上运行。
-
在同一 CPU 中配置线程应用程序和特定内核线程,如 network
softirq
或驱动程序线程。 - 对每个 CPU 上的 producer-consumer 线程配对。生产者和消费者是两类线程,生产者将数据插入到缓冲区中,消费者将其从缓冲区中删除。
在实时系统上调优关联性的常见做法是确定运行应用程序所需的内核数,然后隔离这些内核。您可以使用 Tuna 工具或使用 shell 脚本修改位掩码值(如 taskset
命令)来达到此目的。taskset
命令更改进程的关联性,并修改 /proc/
文件系统条目会更改中断的关联性。
11.1. 使用 taskset
命令调整处理器关联性
在实时,taskset
命令有助于设置或检索正在运行的进程的 CPU 关联性。taskset
命令采用 -p
和 -c
选项。The -p
or-pid
选项可处理现有进程,且不会启动新的任务。-c
or --cpu-list
指定处理器的数字列表,而不是 bitmask
。列表中可以包含多个项目,用逗号分开,以及一系列处理器。例如: 0,5,7,9-11。
先决条件
- 您在系统上具有 root 权限。
流程
验证特定进程的进程关联性:
taskset -p -c 1000
# taskset -p -c 1000 pid 1000’s current affinity list: 0,1
Copy to Clipboard Copied! 命令打印 PID 为 1000 的进程的关联性。进程设置为使用 CPU 0 或 CPU 1。
可选:要将特定 CPU 配置为绑定进程:
taskset -p -c 1 1000
# taskset -p -c 1 1000 pid 1000’s current affinity list: 0,1 pid 1000’s new affinity list: 1
Copy to Clipboard Copied! 可选:定义多个 CPU 关联性:
taskset -p -c 0,1 1000
# taskset -p -c 0,1 1000 pid 1000’s current affinity list: 1 pid 1000’s new affinity list: 0,1
Copy to Clipboard Copied! 可选: 要在特定 CPU 上配置优先级级别和策略:
taskset -c 5 chrt -f 78 /bin/my-app
# taskset -c 5 chrt -f 78 /bin/my-app
Copy to Clipboard Copied! 如需进一步的粒度,您还可以指定优先级和策略。在示例中,命令在带有
SCHED_FIFO
策略的 CPU 5 上运行/bin/my-app
应用程序,其优先级值为 78。
11.2. 使用 sched_setaffinity ()系统调用设置处理器关联性
您还可以使用实时 sched_setaffinity ()
系统调用来设置处理器关联性。
先决条件
- 您在系统上具有 root 权限。
流程
使用
sched_setaffinity ()
设置处理器关联性:#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sched.h> int main(int argc, char **argv) { int i, online=0; ulong ncores = sysconf(_SC_NPROCESSORS_CONF); cpu_set_t *setp = CPU_ALLOC(ncores); ulong setsz = CPU_ALLOC_SIZE(ncores); CPU_ZERO_S(setsz, setp); if (sched_getaffinity(0, setsz, setp) == -1) { perror("sched_getaffinity(2) failed"); exit(errno); } for (i=0; i < CPU_COUNT_S(setsz, setp); i) { if (CPU_ISSET_S(i, setsz, setp)) online; } printf("%d cores configured, %d cpus allowed in affinity mask\n", ncores, online); CPU_FREE(setp); }
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sched.h> int main(int argc, char **argv) { int i, online=0; ulong ncores = sysconf(_SC_NPROCESSORS_CONF); cpu_set_t *setp = CPU_ALLOC(ncores); ulong setsz = CPU_ALLOC_SIZE(ncores); CPU_ZERO_S(setsz, setp); if (sched_getaffinity(0, setsz, setp) == -1) { perror("sched_getaffinity(2) failed"); exit(errno); } for (i=0; i < CPU_COUNT_S(setsz, setp); i) { if (CPU_ISSET_S(i, setsz, setp)) online; } printf("%d cores configured, %d cpus allowed in affinity mask\n", ncores, online); CPU_FREE(setp); }
Copy to Clipboard Copied!
11.3. 隔离单个 CPU 以运行高利用率任务
使用 cpusets
机制,您可以为 SCHED_DEADLINE
任务分配一组 CPU 和内存节点。在使用任务具有高和低 CPU 的任务集合中,隔离 CPU 以在不同的 CPU 上运行高利用率任务和将小利用率任务调度到不同的 CPU 集合中,使所有任务能够满足所分配的 运行时
。您必须手动为 'cpusets' 添加配置
先决条件
- 您在系统上具有 root 权限。
流程
创建两个名为 cluster 和 partition 的控制组:
cd /sys/fs/cgroup echo +cpuset > cgroup.subtree_control mkdir cluster mkdir partition echo +cpuset | tee cluster/cgroup.subtree_control partition/cgroup.subtree_control
# cd /sys/fs/cgroup # echo +cpuset > cgroup.subtree_control # mkdir cluster # mkdir partition # echo +cpuset | tee cluster/cgroup.subtree_control partition/cgroup.subtree_control
Copy to Clipboard Copied! 在集群控制组群中,将低利用率任务调度到 CPU 1 到 7。验证内存大小,并将控制组命名为 exclusive :
cd cluster echo 1-7 | tee cpuset.cpus cpuset.cpus.exclusive echo root > cpuset.cpus.partition
# cd cluster # echo 1-7 | tee cpuset.cpus cpuset.cpus.exclusive # echo root > cpuset.cpus.partition
Copy to Clipboard Copied! 将所有低利用率任务移到集群控制组群中:
ps -eLo lwp | while read thread; do echo $thread > cgroup.procs ; done
# ps -eLo lwp | while read thread; do echo $thread > cgroup.procs ; done
Copy to Clipboard Copied! 在
分区
控制组群中,分配高利用率任务:echo 0 | tee cpuset.cpus cpuset.cpus.exclusive echo isolated > cpuset.cpus.partition
# echo 0 | tee cpuset.cpus cpuset.cpus.exclusive # echo isolated > cpuset.cpus.partition
Copy to Clipboard Copied! 将 shell 添加到分区控制组中并启动:
echo $$ > cgroup.procs
# echo $$ > cgroup.procs
Copy to Clipboard Copied! 在这个版本中,
在分区
控制组群中隔离的任务不会影响集群
控制组群中的任务。这可让所有实时任务满足调度程序期限。如果您使用截止时间调度程序,则在没有此更改的情况下通常会满足截止时间。请注意,其他任务都有自己的期限。
如果应用程序已准备好使用正确的固定,可以通过调整 cgroups 来进一步减少对 分区
cgroup 的 cpus,并为它分配所有实时任务:
cd .. echo 4-7 | tee cluster/{cpuset.cpus,cpuset.cpus.exclusive} echo 0-3 | tee partition/{cpuset.cpus,cpuset.cpus.exclusive}
# cd ..
# echo 4-7 | tee cluster/{cpuset.cpus,cpuset.cpus.exclusive}
# echo 0-3 | tee partition/{cpuset.cpus,cpuset.cpus.exclusive}
11.4. 减少 CPU 性能激增
常见延迟高峰来源是内核计时器循环处理器中常见锁定的多个 CPU 持续时。负责争用的常见锁定是 xtime_lock
,由计时系统和 Read-Copy-Update (RCU)结构锁定使用。通过使用 skew_tick=1
,您可以偏移每个 CPU 的计时器循环,以在不同时间启动,并避免潜在的锁定冲突。
skew_tick
内核命令行参数可能会阻止低到具有大型核心数的大型系统的延迟波动,并具有对延迟敏感的工作负载。
先决条件
- 有管理员权限。
流程
使用
grubby
启用skew_tick=1
参数。grubby --update-kernel=ALL --args="skew_tick=1"
# grubby --update-kernel=ALL --args="skew_tick=1"
Copy to Clipboard Copied! 重启以使更改生效。
reboot
# reboot
Copy to Clipboard Copied!
启用 skew_tick=1
会导致功耗显著增加,因此只有在您运行对延迟敏感实时工作负载且一致性延迟时,必须启用 skew
引导参数。
验证
显示 /proc/cmdline
文件,并确保指定了 skew_tick=1
。/proc/cmdline
文件显示传递给内核的参数。
检查
/proc/cmdline
文件中的新设置。cat /proc/cmdline
# cat /proc/cmdline
Copy to Clipboard Copied!
11.5. 禁用 PC 卡守护进程来降低 CPU 使用量
pcscd
守护进程管理到并行通信(PC 或 PCMCIA)和智能卡(SC)读取器的连接。虽然 pcscd
通常是一个低优先级的任务,但它通常使用比任何其他守护进程更多的 CPU。因此,额外的背景 noise 可能会导致更高的抢占成本进行实时任务,并会对确定性造成其他不良影响。
先决条件
- 您在系统上具有 root 权限。
流程
检查
pcscd
守护进程的状态。systemctl status pcscd
# systemctl status pcscd ● pcscd.service - PC/SC Smart Card Daemon Loaded: loaded (/usr/lib/systemd/system/pcscd.service; indirect; vendor preset: disabled) Active: active (running) since Mon 2021-03-01 17:15:06 IST; 4s ago TriggeredBy: ● pcscd.socket Docs: man:pcscd(8) Main PID: 2504609 (pcscd) Tasks: 3 (limit: 18732) Memory: 1.1M CPU: 24ms CGroup: /system.slice/pcscd.service └─2504609 /usr/sbin/pcscd --foreground --auto-exit
Copy to Clipboard Copied! Active
参数显示pcsd
守护进程的状态。如果
pcsd
守护进程正在运行,请停止它。systemctl stop pcscd
# systemctl stop pcscd Warning: Stopping pcscd.service, but it can still be activated by: pcscd.socket
Copy to Clipboard Copied! 将系统配置为确保
pcsd
守护进程在系统启动时不会重启。systemctl disable pcscd
# systemctl disable pcscd Removed /etc/systemd/system/sockets.target.wants/pcscd.socket.
Copy to Clipboard Copied!
验证
检查
pcscd
守护进程的状态。systemctl status pcscd
# systemctl status pcscd ● pcscd.service - PC/SC Smart Card Daemon Loaded: loaded (/usr/lib/systemd/system/pcscd.service; indirect; vendor preset: disabled) Active: inactive (dead) since Mon 2021-03-01 17:10:56 IST; 1min 22s ago TriggeredBy: ● pcscd.socket Docs: man:pcscd(8) Main PID: 4494 (code=exited, status=0/SUCCESS) CPU: 37ms
Copy to Clipboard Copied! -
确保
Active
参数的值是inactive (dead)
。
第 12 章 在 RHEL for Real Time 中使用 mlock ()系统调用
RHEL for Real-Time 内存锁定(mlock
())函数可让实时调用进程锁定或解锁指定的地址空间范围。这个范围可防止 Linux 在交换内存空间时分页锁定的内存。将物理页面分配给页表条目后,对该页面的引用就会变得快速。mlock ()
系统调用包含两个函数: mlock ()
和 mlockall ()
。同样,munlock ()
系统调用包含 munlock ()
和 munlockall ()
函数。
12.1. 使用 mlock ()系统调用锁定页面
实时 mlock ()
系统调用使用 addr
参数指定地址范围的开头,len
则以字节为单位定义地址空间长度。alloc_workbuf ()
函数动态分配内存缓冲区并锁定它。内存分配由 posix_memalig ()
函数完成,以将内存区域与页面保持一致。函数 free_workbuf ()
解锁内存区域。
先决条件
-
您有 root 特权或
CAP_IPC_LOCK
功能在大型缓冲区上使用mlockall ()
或mlock ()
流程
以下代码使用
mlock ()
系统调用锁定页面:#include <stdlib.h> #include <unistd.h> #include <sys/mman.h> void *alloc_workbuf(size_t size) { void *ptr; int retval; // alloc memory aligned to a page, to prevent two mlock() in the same page. retval = posix_memalign(&ptr, (size_t) sysconf(_SC_PAGESIZE), size); // return NULL on failure if (retval) return NULL; // lock this buffer into RAM if (mlock(ptr, size)) { free(ptr); return NULL; } return ptr; } void free_workbuf(void *ptr, size_t size) { // unlock the address range munlock(ptr, size); // free the memory free(ptr); }
#include <stdlib.h> #include <unistd.h> #include <sys/mman.h> void *alloc_workbuf(size_t size) { void *ptr; int retval; // alloc memory aligned to a page, to prevent two mlock() in the same page. retval = posix_memalign(&ptr, (size_t) sysconf(_SC_PAGESIZE), size); // return NULL on failure if (retval) return NULL; // lock this buffer into RAM if (mlock(ptr, size)) { free(ptr); return NULL; } return ptr; } void free_workbuf(void *ptr, size_t size) { // unlock the address range munlock(ptr, size); // free the memory free(ptr); }
Copy to Clipboard Copied!
验证
成功时,实时 mlock ()
和 munlock ()
调用返回 0。如果出现错误,则返回 -1 并设置 errno
来指示错误。
12.2. 使用 mlockall ()系统调用锁定所有映射的页面
要使用 mlockall ()
和 munlockall ()
系统调用锁定和解锁实时内存,请将 flags
参数设置为 0 或一个常量: MCL_CURRENT
或 MCL_FUTURE
。使用 MCL_FUTURE
时,未来系统调用,如 mmap (2)
、sbrk (2)
或 malloc (3)
可能会失败,因为它会导致锁定字节数超过允许的最大值。
先决条件
- 您在系统上具有 root 权限。
流程
使用
mlockall ()
和munlockall ()
实时系统调用:使用
mlockall ()
系统调用锁定所有映射的页面:#include <sys/mman.h> int mlockall (int flags)
#include <sys/mman.h> int mlockall (int flags)
Copy to Clipboard Copied! 使用
munlockall ()
系统调用解锁所有映射的页面:#include <sys/mman.h> int munlockall (void)
#include <sys/mman.h> int munlockall (void)
Copy to Clipboard Copied!
12.3. 使用 mmap ()系统调用将文件或设备映射到内存中
对于实时系统中的大型内存分配,内存分配(malloc
)方法使用 mmap ()
系统调用来查找内存空间。您可以通过在 flags
参数中设置 MAP_LOCKED
来分配和锁定内存区域。因为 mmap ()
以页为基础分配内存,因此它会避免在同一页面中出现两个锁定,这样可防止双锁或单解锁问题。
先决条件
- 您在系统上具有 root 权限。
流程
映射特定的 process-address 空间:
#include <sys/mman.h> #include <stdlib.h> void *alloc_workbuf(size_t size) { void *ptr; ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0); if (ptr == MAP_FAILED) return NULL; return ptr; } void free_workbuf(void *ptr, size_t size) { munmap(ptr, size); }
#include <sys/mman.h> #include <stdlib.h> void *alloc_workbuf(size_t size) { void *ptr; ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED, -1, 0); if (ptr == MAP_FAILED) return NULL; return ptr; } void free_workbuf(void *ptr, size_t size) { munmap(ptr, size); }
Copy to Clipboard Copied!
验证
-
当
mmap ()
函数成功完成时,它会将指针返回到映射区域。出错时,它会返回MAP_FAILED
值,并设置errno
来指示错误。 -
当
munmap ()
函数成功完成时,它返回0。
出错时,它会返回-1
,并设置errno
来指示错误。
12.4. mlock ()系统调用的参数
在 mlock
参数表中列出了内存锁定系统调用的参数及其执行功能。
参数 | 描述 |
---|---|
|
指定要锁定或解锁的进程地址空间。当 NULL 时,内核会选择内存中数据的页对齐安排。如果 |
| 指定映射的长度,它必须大于 0。 |
| 指定文件描述符。 |
|
|
|
控制映射到映射相同文件的其他进程的映射可见性。它采用其中一个值: |
| 锁定当前映射到进程中的所有页面。 |
| 将模式设置为锁定后续内存分配。这些可能是增大堆和堆栈、新内存映射文件或共享内存区域所需的新页面。 |
第 13 章 使用 RHEL for Real Time 中的 timerlat 测量调度延迟
rtla-timerlat
工具是 timerlat
追踪器的接口。timerlat
追踪器查找实时线程的 wake-up 延迟源。timerlat
追踪器为每个 CPU 创建一个具有实时优先级的内核线程,这些线程将定期计时器设置为 wake,并返回到 sleep。在唤醒时,timerlat
会查找并收集信息,这对于调试操作系统计时器延迟非常有用。timerlat
追踪器生成输出,并在每次激活时打印以下两行:
-
timerlat
追踪程序定期打印计时器中断请求(IRQ)处理程序中看到的计时器延迟。这是在线程激活前在hardirq
上下文中看到的第一个输出。 -
第二个输出是线程的计时器延迟。
ACTIVATION ID
字段显示中断请求(IRQ)性能,以及它们对应的线程执行。
13.1. 配置 timerlat 追踪器来测量调度延迟
您可以通过在追踪系统的 curret_tracer
文件中添加 timerlat
来配置 timerlat
追踪器。current_tracer
文件通常挂载到 /sys/kernel/tracing
目录中。timerlat
追踪器测量中断请求(IRQ),并在线程延迟超过 100 微秒时保存用于分析的 trace 输出。
流程
列出当前的 tracer:
cat /sys/kernel/tracing/current_tracer nop
# cat /sys/kernel/tracing/current_tracer nop
Copy to Clipboard Copied! 没有操作
(nop
)是默认的 tracer。在追踪系统的
current_tracer
文件中添加timerlat
追踪器:cd /sys/kernel/tracing/ echo timerlat > current_tracer
# cd /sys/kernel/tracing/ # echo timerlat > current_tracer
Copy to Clipboard Copied! 生成追踪输出:
cat trace tracer: timerlat
# cat trace # tracer: timerlat
Copy to Clipboard Copied!
验证
输入以下命令检查
timerlat
是否作为当前的 tracer 启用:cat /sys/kernel/tracing/current_tracer timerlat
# cat /sys/kernel/tracing/current_tracer timerlat
Copy to Clipboard Copied!
13.2. timerlat 追踪器选项
timerlat
追踪器基于 osnoise
tracer 构建。因此,您可以将 /osnoise/config
目录中的选项设置为 trace 并捕获线程调度延迟的信息。
timerlat 选项
- cpus
-
为要执行的
计时器线程
设置 CPU。 - timerlat_period_us
-
以微秒为单位设置
timerlat
线程的持续时间。 - stop_tracing_us
-
如果
irq
上下文的计时器延迟超过配置的值,则停止系统追踪。编写 0 可禁用这个选项。 - stop_tracing_total_us
- 如果总 noise 大于配置的值,则停止系统追踪。编写 0 可禁用这个选项。
- print_stack
- 保存出现的中断请求的堆栈(IRQ)。堆栈会在线程上下文事件后保存 IRQ 发生,或者 IRQs 处理程序超过配置的值。
13.3. 使用 rtla-timerlat-top 测量计时器延迟
rtla-timerlat-top
tracer 显示 timerlat tracer
中定期输出的摘要。tracer 输出还提供有关每个操作系统声明和事件的信息,如 osnoise
和 追踪点
。您可以使用 the -t
选项查看此信息。
流程
测量计时器延迟:
rtla timerlat top -s 30 -T 30 -t
# rtla timerlat top -s 30 -T 30 -t
Copy to Clipboard Copied!
13.4. rtla timerlat 顶部 tracer 选项
通过使用 rtla timerlat top --help
命令,您可以查看 rtla-timerlat-top tracer
选项的帮助用法。
timerlat-top-tracer 选项
- -p,--period us
-
以微秒为单位设置
timerlat
追踪器周期。 - -i, --irq us
- 如果中断请求(IRQ)延迟超过微秒中的参数,则停止追踪。
- -t, --thread us
- 如果线程延迟超过微秒中的参数,则停止追踪。
- -t,--trace
-
将已停止的 trace 保存到
timerlat_trace.txt
文件。 - -s, --stack us
- 如果线程延迟超过参数,请在中断请求(IRQ)中保存堆栈跟踪。
第 14 章 使用 RHEL for Real Time 中的 rtla-osnoise 测量调度延迟
低延迟是一个环境,它经过优化来处理具有低容错(延迟)的大量数据。为应用程序提供专用资源(包括 CPU)是在大量低延迟环境中进行的。例如,对于网络功能虚拟化(NFV)应用中的高性能网络处理,单个应用具有 CPU 电源限制集来持续运行任务。
Linux 内核包含实时分析(rtla
)工具,它为操作系统 noise (osnoise
) tracer 提供了一个接口。操作系统声明是应用中因操作系统内活动而进行的干扰。Linux 系统可能会因为以下原因而体验:
- 不可屏蔽中断(NMI)
- 中断请求(IRQ)
- 软中断请求(SoftIRQ)
- 其他系统线程活动
- 与硬件相关的作业,如不可屏蔽的高优先级系统管理中断(SMI)
14.1. rtla-osnoise tracer
Linux 内核包含实时分析(rtla
)工具,它为操作系统 noise (osnoise
) tracer 提供了一个接口。rtla-osnoise
tracer 创建一个在指定时间段内定期运行的线程。在 句点
开始时,线程会禁用中断,启动抽样,并在循环中捕获时间。
rtla-osnoise
tracer 提供以下功能:
- 测量 CPU 接收的操作量。
- 特征在 CPU 中发生的操作系统声明类型。
- 输出优化的 trace 报告,以帮助定义意外结果的根本原因。
- 节省每个干扰源的干扰计数器。非可屏蔽中断(NMI)、中断请求(IRQ)、软件中断请求(SoftIRQ)和线程的干扰计数器,当工具检测到这些干扰的条目事件时,线程会增加。
rtla-osnoise
tracer 会打印一个运行报告,并在周期的结论中提供有关 noise 源的以下信息:
- 无权总额.
- 最多声明量。
- 分配给线程的 CPU 百分比。
- noise 源的计数器。
14.2. 配置 rtla-osnoise tracer 以测量调度延迟
您可以通过在追踪系统的 curret_tracer
文件中添加 osnoise
来配置 rtla-osnoise
tracer。current_tracer
文件通常挂载到 /sys/kernel/tracing/
目录中。rtla-osnoise
追踪器测量中断请求(IRQ),并在线程延迟超过 20 微秒内保存分析的 trace 输出。
流程
列出当前的 tracer:
cat /sys/kernel/tracing/current_tracer nop
# cat /sys/kernel/tracing/current_tracer nop
Copy to Clipboard Copied! 没有操作
(nop
)是默认的 tracer。在追踪系统的
current_tracer
文件中添加timerlat
追踪器:cd /sys/kernel/tracing/ echo osnoise > current_tracer
# cd /sys/kernel/tracing/ # echo osnoise > current_tracer
Copy to Clipboard Copied! 生成追踪输出:
cat trace tracer: osnoise
# cat trace # tracer: osnoise
Copy to Clipboard Copied!
14.3. 配置的 rtla-osnoise 选项
rtla-osnoise
tracer 的配置选项位于 /sys/kernel/tracing/
目录中。
rtla-osnoise
的配置选项
- osnoise/cpus
-
配置要运行的
osnoise
线程的 CPU。 - osnoise/period_us
-
配置
osnoise
线程的期间
。 - osnoise/runtime_us
-
配置
osnoise
线程的运行持续时间。 - osnoise/stop_tracing_us
-
如果单个 noise 大于配置的值,则停止系统追踪。设置
0
可禁用这个选项。 - osnoise/stop_tracing_total_us
-
如果总 noise 大于配置的值,则停止系统追踪。设置
0
可禁用这个选项。 - tracing_thresh
-
设置两个
time ()
调用读取之间的最小 delta,以微秒为单位被视为 noise。当设置为0
时,tracing_thresh
将使用默认值,即 5 微秒。
14.4. rtla-osnoise 追踪点
rtla-osnoise
包括一组用来识别操作系统发行源的 追踪点
(osnoise
)。
rtla-osnoise
的 trace 点
- osnoise:sample_threshold
-
当 noise 超过配置的阈值(
tolerance_ns
)时,显示 noise。 - osnoise:nmi_noise
- 显示不可屏蔽中断(NMI)的 noise 和 noise 持续时间。
- osnoise:irq_noise
- 显示中断请求(IRQ)的 noise 和 noise 持续时间。
- osnoise:softirq_noise
- 显示来自软中断请求的 noise 和 noise 持续时间(SoftIRQ)。
- osnoise:thread_noise
- 显示线程中的 noise 和 noise 持续时间。
14.5. rtla-osnoise tracer 选项
osnoise/options
文件包括一组用于 rtla-osnoise
tracer 的配置选项。
rtla-osnoise
的选项
- 默认值
- 将选项重置为默认值。
- OSNOISE_WORKLOAD
-
停止
osnoise
工作负载分配。 - PANIC_ON_STOP
-
如果 tracer 停止,则设置
panic ()
调用。这个选项捕获vmcore
转储文件。 - OSNOISE_PREEMPT_DISABLE
-
禁用对
osnoise
工作负载的抢占,仅允许中断请求(IRQ)和与硬件相关的通知。 - OSNOISE_IRQ_DISABLE
-
为
osnoise
工作负载禁用中断请求(IRQ),这只允许不可屏蔽中断(NMI)和硬件相关的 noise。
14.6. 使用 rtla-osnoise-top tracer 测量操作系统noise
rtla osnoise-top
tracer 测量并显示 osnoise
tracer 的定期摘要,以及有关 interference 源发生计数器的信息。
流程
测量系统 noise:
rtla osnoise top -P F:1 -c 0-3 -r 900000 -d 1M -q
# rtla osnoise top -P F:1 -c 0-3 -r 900000 -d 1M -q
Copy to Clipboard Copied! 命令输出显示定期摘要,其中包含有关实时优先级的信息、要运行线程的 CPU 以及以微秒为单位运行的周期。
14.7. rtla-osnoise-top tracer 选项
通过使用 rtla osnoise top --help
命令,您可以查看 rtla-osnoise-top
tracer 可用选项的帮助用法。
rtla-osnoise-top
的选项
- -a,--auto us
-
设置自动追踪模式。此模式在调试系统时设置一些常用的选项。它相当于使用
-s
us
-T
1
和-t
。 - -p,--period us
-
以微秒为单位设置
osnoise
tracer 持续时间。 - -r, --runtime us
-
以微秒为单位设置
osnoise
tracer 运行时。 - -s, --stop us
-
如果单个示例超过微秒中的参数,则停止追踪。使用
,命令可将 trace 保存到输出中。
- -s, --stop-total us
-
如果示例总数超过微秒中的参数,则停止追踪。使用
-T
时,命令会将 trace 保存到输出中。 - -t, --threshold us
- 指定两个时间读取之间的最小 delta 被视为 noise。默认阈值是 5 us。
- -q, --quiet
- 仅在运行结束时打印摘要。
- -c, --cpus cpu-list
-
设置
osnoise
tracer,以在分配的cpu-list
上运行示例线程。 - -d, --duration time[s|m|h|d]
- 设置运行的持续时间。
- -D, --debug
- 打印调试信息。
- -t, --trace[=file]
-
将已停止的 trace 保存到
[file|osnoise_trace.txt]
文件。 - -e, --event sys:event
-
在 trace (
-t
)会话中启用事件。参数可以是特定的事件,如sched:sched_switch
,或者系统组的所有事件,如sched
系统组。 - --filter < ;filter>
-
使用过滤器表达式过滤 previous
-e sys:event
系统事件。 - --trigger <trigger>
-
启用对 previous
-e sys:event
系统事件的 trace 事件触发器。 - -p, --priority o:prio|r:prio|f:prio|d:runtime:period
-
将调度参数设置为
osnoise
tracer 线程。 - -h, --help
- 打印帮助菜单。
第 15 章 尽量减少或避免系统因为日志而减慢的问题
将日志更改写入到磁盘的顺序可能与它们到达的顺序不同。内核 I/O 系统可以重新排序日志更改,以优化可用存储空间的使用。日志活动可以通过重新排序日志更改并提交数据和元数据导致系统延迟。因此,日志记录文件系统可能会减慢系统的速度。
XFS
是 RHEL 8 使用的默认文件系统。这是一个日志文件系统。名为 ext2
的旧文件系统不使用日志。除非您的机构特别需要日志记录,否则请考虑 ext2
文件系统。在许多红帽最佳基准结果中,会使用 ext2
文件系统。这是顶级初始调优建议之一。
XFS
等日志记录文件系统记录上次访问文件的时间( atime
属性)。如果您需要使用日志记录文件系统,请考虑禁用 atime
。
15.1. 禁用 atime
禁用 atime
属性通过限制对文件系统日志的写入数量来提高性能并降低功耗。
流程
使用您选择的文本编辑器打开
/etc/fstab
文件,并找到 root 挂载点的条目。/dev/mapper/rhel-root / xfs defaults…
/dev/mapper/rhel-root / xfs defaults…
Copy to Clipboard Copied! 编辑 options 部分,使其包含术语
noatime
和nodiratime
。noatime
选项可防止在文件读取时更新访问时间戳,nodiratime 选项会停止更新目录内节点访问时间。/dev/mapper/rhel-root / xfs noatime,nodiratime…
/dev/mapper/rhel-root / xfs noatime,nodiratime…
Copy to Clipboard Copied!
有些应用程序 依赖于
一次更新。因此,这个选项只适用于没有使用此类应用程序的系统。
或者,您可以使用 relatime
挂载选项,该选项可确保仅在以前的访问时间超过当前修改时间时才更新访问时间。
第 16 章 禁用对延迟敏感工作负载的图形控制台输出
内核会在系统启动时马上将消息传递给 printk ()
。内核将消息发送到日志文件,也显示在图形控制台中,即使没有附加到无头服务器的监控器。
在一些系统中,发送到图形控制台的输出可能会在管道中引入停滞。这可能会在等待数据传输时造成任务执行的潜在延迟。例如,发送到 teletype0
(/dev/tty0)
的输出可能会在某些系统中造成潜在的停止状态。
要防止意外的停滞,您可以通过以下方法限制或禁用发送到图形控制台的信息:
-
删除
tty0
定义。 - 更改控制台定义的顺序.
-
关闭大多数
printk
() 函数,并确保将ignore_loglevel
内核参数设置为未配置
。
通过禁用图形控制台输出的日志记录和控制图形控制台上打印的消息,您可以提高敏感工作负载的延迟。
16.1. 禁用图形控制台日志记录到图形适配器
teletype
(tty
)默认内核控制台通过将输入数据传递给系统并显示有关图形控制台的输出信息来启用与系统交互。
没有配置图形控制台,防止它在图形适配器上记录。这使得系统无法使用 tty0
,帮助禁用图形控制台上的打印消息。
禁用图形控制台输出不会删除信息。这些信息会在系统日志中打印,您可以使用 journalctl
或 dmesg
实用程序访问它们。
流程
从内核配置中删除
console=tty0
选项:grubby --update-kernel=ALL --remove-args="console=tty0"
# grubby --update-kernel=ALL --remove-args="console=tty0"
Copy to Clipboard Copied!
16.2. 禁用在图形控制台中打印的消息
您可以通过在 /proc/sys/kernel/printk
文件中配置所需的日志级别来控制发送到图形控制台的输出消息量。
流程
查看当前的控制台日志级别:
cat /proc/sys/kernel/printk 7 4 1 7
$ cat /proc/sys/kernel/printk 7 4 1 7
Copy to Clipboard Copied! 命令打印系统日志级别的当前设置。数字对应于系统日志记录器的当前、默认、最小和最大值。
在
/proc/sys/kernel/printk
文件中配置所需的日志级别。echo "1” > /proc/sys/kernel/printk
$ echo "1” > /proc/sys/kernel/printk
Copy to Clipboard Copied! 命令更改当前的控制台日志级别。例如,设置日志级别 1 将仅打印警报消息,并防止在图形控制台中显示其他消息。
第 17 章 管理系统时钟以满足应用程序的需求
NUMA 或 SMP 等多处理器系统有多个硬件时钟实例。在启动过程中,内核会发现可用的时钟源并选择一个要使用的源。要提高性能,您可以更改用于满足实时系统的最低要求的时钟源。
17.1. 硬件时钟
在多处理器系统中发现多个时钟源的实例,如非统一内存访问(NUMA)和 Symmetric 多处理(SMP),以及它们对系统事件做出反应,如 CPU 频率扩展或进入能源 economy 模式,确定它们是否适合实时内核时钟源。
首选时钟源是时间戳计数器(TSC)。如果 TSC 不可用,则高精度事件时间(HPET)是第二个最佳选项。但是,并非所有系统都有 HPET 时钟,一些 HPET 时钟可能并不可靠。
如果没有 TSC 和 HPET,其他选项包括 ACPI Power Management Timer (ACPI_PM)、Programmable Interval Timer (PIT)和 Real Time Clock (RTC)。最后两个选项可昂贵地读取或具有低分辨率(时间粒度),因此它们适合用于实时内核。
17.2. 查看当前正在使用的时钟源
系统中当前使用的时钟源保存在 /sys/devices/system/clocksource/clocksource0/current_clocksource
文件中。
流程
显示
current_clocksource
文件。cat /sys/devices/system/clocksource/clocksource0/current_clocksource tsc
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource tsc
Copy to Clipboard Copied! 在本例中,系统中的当前时钟源是 TSC。
17.3. 临时更改要使用的时钟源
有时,由于时钟中已知的问题,系统主应用程序的最佳时钟不会被使用。在完成所有有问题的时钟后,系统可以保留无法满足实时系统的最低要求的硬件时钟。
关键应用程序的要求因每个系统而异。因此,每个应用程序的最佳时钟,因此每个系统都有所不同。有些应用程序依赖于时钟解析,提供可靠的纳秒读取的时钟可能更合适。读取时钟经常读取时钟的应用程序经常会受益于具有较小的读取成本的时钟(读取请求和结果之间的时间)。
在这些情况下,可以覆盖内核选择的时钟,前提是您了解覆盖的副作用,并可创建环境来触发给定硬件时钟的已知不足。
内核会自动选择最适合的时钟源。不建议覆盖所选时钟源,除非有很好的理解。
先决条件
- 您在系统上具有 root 权限。
流程
查看可用的时钟源。
cat /sys/devices/system/clocksource/clocksource0/available_clocksource tsc hpet acpi_pm
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource tsc hpet acpi_pm
Copy to Clipboard Copied! 例如,考虑系统中可用的时钟源是 TSC、HPET 和 ACPI_PM。
将您要使用的时钟源名称写入
/sys/devices/system/clocksource/clocksource0/current_clocksource
文件。echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource
# echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource
Copy to Clipboard Copied!
验证
显示
current_clocksource
文件,以确保当前时钟源是指定的时钟源。cat /sys/devices/system/clocksource/clocksource0/current_clocksource hpet
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource hpet
Copy to Clipboard Copied! 这个示例使用 HPET 作为系统中当前的时钟源。
17.4. 读取硬件时钟源的成本比较
您可以比较系统中的时钟速度。从 TSC 读取涉及从处理器读取寄存器。从 HPET 时钟读取涉及读取内存区域。从 TSC 读取速度快,当每秒时间戳上数以千计的信息时,这提供了显著的性能优势。
先决条件
- 您在系统上具有 root 权限。
-
clock_timing
程序必须在系统上。如需更多信息,请参阅 clock_timing 程序。
流程
进入保存
clock_timing
程序的目录。cd clock_test
# cd clock_test
Copy to Clipboard Copied! 查看系统中的可用时钟源。
cat /sys/devices/system/clocksource/clocksource0/available_clocksource tsc hpet acpi_pm
# cat /sys/devices/system/clocksource/clocksource0/available_clocksource tsc hpet acpi_pm
Copy to Clipboard Copied! 在本例中,系统中的可用时钟源为
TSC
、HPET
和ACPI_PM
。查看当前使用的时钟源。
cat /sys/devices/system/clocksource/clocksource0/current_clocksource tsc
# cat /sys/devices/system/clocksource/clocksource0/current_clocksource tsc
Copy to Clipboard Copied! 在本例中,系统中的当前时钟源是
TSC
。运行
time
工具以及 ./clock_timing
程序。输出显示了读取时钟源 1,000万次所需的持续时间。time ./clock_timing
# time ./clock_timing real 0m0.601s user 0m0.592s sys 0m0.002s
Copy to Clipboard Copied! 示例显示了以下参数:
-
实时
- 从程序调用开始的总时间,直到进程结束。real
包括用户和内核时间,并且通常大于后者的总和。如果此进程被优先级更高的应用中断,或者被系统事件(如硬件中断(IRQ))中断,那么此等待时间也被实时计算
。 -
用户
- 执行用户空间中进程执行不需要内核干预的任务所花费的时间。 -
sys
- 在执行用户进程所需任务的同时,内核花费的时间。这些任务包括打开文件、读取和写入文件或 I/O 端口、内存分配、线程创建和网络相关活动。
-
将您要测试的下一个时钟源的名称写入
/sys/devices/system/clocksource/clocksource0/current_clocksource
文件。echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource
# echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource
Copy to Clipboard Copied! 在本例中,当前的时钟源被改为
HPET
。- 对所有可用时钟源重复步骤 4 和 5。
- 比较所有可用时钟源的第 4 步结果。
17.5. 在 Opteron CPU 上同步 TSC 计时器
当前的 AMD64 Opteron 处理器对于一个大型 gettimeofday
skew 可能很易受。当 cpufreq
和 时间戳计数器 (TSC)都被使用时,会出现这个偏移。RHEL for Real Time 提供了通过强制所有处理器同时更改为相同频率来防止这种偏移的方法。因此,单个处理器上的 TSC 不会以不同于其他处理器上的 TSC 的速度增加。
先决条件
- 您在系统上具有 root 权限。
流程
启用
clocksource=tsc
和powernow-k8.tscsync=1
内核选项:grubby --update-kernel=ALL --args="clocksource=tsc powernow-k8.tscsync=1"
# grubby --update-kernel=ALL --args="clocksource=tsc powernow-k8.tscsync=1"
Copy to Clipboard Copied! 这会强制使用 TSC,并启用同时核心处理器频率转换。
- 重启机器。
17.6. clock_timing 程序
clock_timing
程序读取当前的时钟源 1,000万次。与 time
实用程序结合使用时,它会测量执行此操作所需的时间。
流程
创建 clock_timing
程序:
为程序文件创建一个目录。
mkdir clock_test
$ mkdir clock_test
Copy to Clipboard Copied! 更改到创建的目录。
cd clock_test
$ cd clock_test
Copy to Clipboard Copied! 创建源文件,并在文本编辑器中打开。
{EDITOR} clock_timing.c
$ {EDITOR} clock_timing.c
Copy to Clipboard Copied! 在文件中输入以下内容:
#include <time.h> void main() { int rc; long i; struct timespec ts; for(i=0; i<10000000; i++) { rc = clock_gettime(CLOCK_MONOTONIC, &ts); } }
#include <time.h> void main() { int rc; long i; struct timespec ts; for(i=0; i<10000000; i++) { rc = clock_gettime(CLOCK_MONOTONIC, &ts); } }
Copy to Clipboard Copied! - 保存文件并退出编辑器。
编译文件。
gcc clock_timing.c -o clock_timing -lrt
$ gcc clock_timing.c -o clock_timing -lrt
Copy to Clipboard Copied! clock_timing
程序已就绪,可以从保存它的目录运行。
第 18 章 控制电源管理转换
您可以控制电源管理转换来提高延迟。
先决条件
- 您在系统上具有 root 权限。
18.1. 节能状态
现代处理器主动从较低状态过渡到更高的节能状态(C-states)。不幸的是,从高节能状态转换到运行状态可能会消耗比实时应用程序的最佳时间。为防止这些转换,应用可以使用电源管理服务质量(PM QoS)接口。
通过 PM QoS 接口,系统可以模拟 idle=poll
和 processor.max_cstate=1
参数的行为,但更精细地控制节能状态。idle=poll
可防止处理器进入 空闲状态
。processor.max_cstate=1
可防止处理器进入更深的 C-states (energy-saving 模式)。
当应用程序打开 /dev/cpu_dma_latency
文件时,pm QoS 接口会阻止处理器进入深度睡眠状态,这会在退出时造成意外延迟。当文件关闭时,系统会返回节能状态。
18.2. 配置电源管理状态
您可以通过使用以下方法配置电源管理状态来控制电源管理转换:
-
将值写入
/dev/cpu_dma_latency
文件,以微秒为单位更改进程的最大响应时间,并保持文件描述符,直到需要低延迟为止。 -
引用应用程序或脚本中的
/dev/cpu_dma_latency
文件。
先决条件
- 有管理员特权。
流程
通过编写 32 位数字来指定延迟容错,它代表
/dev/cpu_dma_latency
中的最大响应时间,并使文件描述符保持通过低延迟操作打开。0
代表完全禁用 C-state。例如:
import os import signal import sys if not os.path.exists('/dev/cpu_dma_latency'): print("no PM QOS interface on this system!") sys.exit(1) try: fd = os.open('/dev/cpu_dma_latency', os.O_WRONLY) os.write(fd, b'\0\0\0\0') print("Press ^C to close /dev/cpu_dma_latency and exit") signal.pause() except KeyboardInterrupt: print("closing /dev/cpu_dma_latency") os.close(fd) sys.exit(0)
import os import signal import sys if not os.path.exists('/dev/cpu_dma_latency'): print("no PM QOS interface on this system!") sys.exit(1) try: fd = os.open('/dev/cpu_dma_latency', os.O_WRONLY) os.write(fd, b'\0\0\0\0') print("Press ^C to close /dev/cpu_dma_latency and exit") signal.pause() except KeyboardInterrupt: print("closing /dev/cpu_dma_latency") os.close(fd) sys.exit(0)
Copy to Clipboard Copied! 注意Power Management Quality of Service interface (
pm_qos
)接口仅在具有打开文件描述符时才活跃。因此,您用于访问/dev/cpu_dma_latency
的任何脚本或程序都必须保存文件打开,直到允许 power-state 转换。
第 19 章 通过隔离中断和用户进程来最小化系统延迟
在响应各种事件时,实时环境需要最小化或消除延迟。要做到这一点,您可以将中断(IRQ)与不同专用 CPU 上的用户进程隔离开来。
19.1. 中断和进程绑定
将中断(IRQ)与不同专用 CPU 上的用户进程隔离,可在实时环境中最小化或消除延迟。
中断通常在 CPU 之间平均共享。当 CPU 必须编写新数据和指令缓存时,这可能会延迟中断处理。这些中断延迟可能会导致与在同一 CPU 中执行的其他处理冲突。
可以将时间关键中断和进程分配到特定的 CPU (或一系列 CPU)。这样,用于处理此中断的代码和数据结构很可能在处理器和指令缓存中。因此,专用进程可以尽快运行,所有其他非关键进程都在其他 CPU 上运行。这特别重要,其中涉及的速度接近或内存限制以及可用的外围总线带宽。任何等待内存进入处理器缓存都会对整个处理时间和确定性造成显著影响。
实际上,最佳性能完全特定于应用程序。例如,为不同公司调优具有相似功能的应用程序,需要完全不同的性能调整。
- 当公司隔离 4 个 CPU 用于操作系统功能和中断处理时,公司看到了最佳结果。剩余的 2 个 CPU 专门用于应用程序处理。
- 当公司将网络相关应用程序进程绑定到处理网络设备驱动程序中断的单一 CPU 上时,公司发现了最佳确定性。
要将进程绑定到 CPU,通常需要知道给定 CPU 或 CPU 范围的 CPU 掩码。根据您使用的命令,CPU 掩码通常以 32 位位掩码、十进制数或十六进制数字表示。
CPU | bitmask | 十进制 | 十六进制 |
0 | 00000000000000000000000000000001 | 1 | 0x00000001 |
0, 1 | 00000000000000000000000000000011 | 3 | 0x00000011 |
19.2. 禁用 irqbalance 守护进程
irqbalance
守护进程默认是启用的,并定期强制由 CPU 处理中断。但是,在实时部署中,不需要 irqbalance
,因为应用程序通常绑定到特定的 CPU。
流程
检查
irqbalance
的状态。systemctl status irqbalance
# systemctl status irqbalance irqbalance.service - irqbalance daemon Loaded: loaded (/usr/lib/systemd/system/irqbalance.service; enabled) Active: active (running) …
Copy to Clipboard Copied! 如果
irqbalance
正在运行,请禁用它并停止它。systemctl disable irqbalance systemctl stop irqbalance
# systemctl disable irqbalance # systemctl stop irqbalance
Copy to Clipboard Copied!
验证
检查
irqbalance
的状态是否为不活动。systemctl status irqbalance
# systemctl status irqbalance
Copy to Clipboard Copied!
19.3. 从 IRQ 平衡中排除 CPU
您可以使用 IRQ 平衡服务指定您要排除哪些 CPU 进行中断(IRQ)平衡。/etc/sysconfig/irqbalance
配置文件中的 IRQBALANCE_BANNED_CPUS
参数控制这些设置。参数的值是一个 64 位十六进制位掩码,掩码的每个位代表 CPU 内核。
流程
在首选文本编辑器中打开
/etc/sysconfig/irqbalance
,找到名为IRQBALANCE_BANNED_CPUS
的文件的部分。IRQBALANCE_BANNED_CPUS 64 bit bitmask which allows you to indicate which cpu's should be skipped when reblancing irqs. Cpu numbers which have their corresponding bits set to one in this mask will not have any irq's assigned to them on rebalance
# IRQBALANCE_BANNED_CPUS # 64 bit bitmask which allows you to indicate which cpu's should # be skipped when reblancing irqs. Cpu numbers which have their # corresponding bits set to one in this mask will not have any # irq's assigned to them on rebalance # #IRQBALANCE_BANNED_CPUS=
Copy to Clipboard Copied! -
取消注释
IRQBALANCE_BANNED_CPUS
变量。 - 输入适当的位掩码,以指定 IRQ 平衡机制所忽略的 CPU。
- 保存并关闭该文件。
重启
irqbalance
服务以使更改生效:systemctl restart irqbalance
# systemctl restart irqbalance
Copy to Clipboard Copied!
如果您正在运行具有最多 64 个 CPU 内核的系统,请使用逗号分隔每组八个十六进制数字。例如: IRQBALANCE_BANNED_CPUS=00000001,0000ff00
CPU | bitmask |
0 | 00000001 |
8 - 15 | 0000ff00 |
8 - 15, 33 | 00000002,0000ff00 |
在 RHEL 7.2 及更高版本中,如果 /etc/sysconfig/ irqbalance
中没有在 /etc/sysconfig/irqbalance
中设置 IRQBALANCE_BANNED_CPUS
,则 irqbalance 工具会自动避免通过 isolcpus
内核参数隔离的 CPU 内核中的 IRQ。
19.4. 手动将 CPU 关联性分配给单个 IRQ
分配 CPU 关联性可让将进程和未绑定进程和线程绑定到指定的 CPU 或 CPU 范围。这可减少缓存问题。
流程
通过查看
/proc/interrupts
文件,检查每个设备使用的 IRQ。cat /proc/interrupts
# cat /proc/interrupts
Copy to Clipboard Copied! 每行都显示 IRQ 编号,每个 CPU 中发生中断数,后跟 IRQ 类型和描述。
CPU0 CPU1 0: 26575949 11 IO-APIC-edge timer 1: 14 7 IO-APIC-edge i8042
CPU0 CPU1 0: 26575949 11 IO-APIC-edge timer 1: 14 7 IO-APIC-edge i8042
Copy to Clipboard Copied! 将 CPU 掩码写入特定 IRQ 的
smp_affinity
条目。CPU 掩码必须以十六进制数字表示。例如,以下命令指示 IRQ 编号 142 仅在 CPU 0 上运行。
echo 1 > /proc/irq/142/smp_affinity
# echo 1 > /proc/irq/142/smp_affinity
Copy to Clipboard Copied! 更改仅在中断发生时生效。
验证
- 执行触发指定中断的活动。
检查
/proc/interrupts
的更改。配置的 IRQ 的指定 CPU 上的中断数量会增加,且在指定关联性外的 CPU 上配置的 IRQ 的中断数量不会增加。
19.5. 使用 taskset 工具将进程绑定到 CPU
taskset
实用程序使用任务的进程 ID (PID)来查看或设置其 CPU 关联性。您可以使用 实用程序运行带有所选 CPU 关联性的命令。
要设置关联性,您需要将 CPU 掩码设为十进制或十六进制数字。mask 参数是一个 位掩码
,用于指定修改的命令或 PID 的 CPU 内核是法律的。
taskset
工具在 NUMA (Non-Uniform Memory Access)系统上工作,但它不允许用户将线程绑定到 CPU 和最接近的 NUMA 内存节点。在这样的系统上,taskset 不是首选工具,而 numactl
实用程序则应用于其高级功能。
如需更多信息,请参阅您系统上的 numactl (8)
手册页。
流程
使用必要的选项和参数运行
taskset
。您可以使用 -c 参数而不是 CPU 掩码来指定 CPU 列表。在本例中,会指示
my_embedded_process
仅在 CPU 0,4,7-11 上运行。taskset -c 0,4,7-11 /usr/local/bin/my_embedded_process
# taskset -c 0,4,7-11 /usr/local/bin/my_embedded_process
Copy to Clipboard Copied! 在大多数情况下,此调用更为方便。
要设置当前没有运行的进程的关联性,请使用
taskset
并指定 CPU 掩码和进程。在本例中,会指示
my_embedded_process
仅使用 CPU 3 (使用 CPU 掩码的十进制版本)。taskset 8 /usr/local/bin/my_embedded_process
# taskset 8 /usr/local/bin/my_embedded_process
Copy to Clipboard Copied! 您可以在位掩码中指定多个 CPU。在本例中,将指示
my_embedded_process
在处理器 4、5、6 和 7 (使用 CPU 掩码的十六进制版本)上执行。taskset 0xF0 /usr/local/bin/my_embedded_process
# taskset 0xF0 /usr/local/bin/my_embedded_process
Copy to Clipboard Copied! 您可以使用 CPU 掩码和您要更改的进程的 PID 选项为已经运行的进程设置 CPU 关联性。
在本例中,PID 为 7013 的进程被指示仅在 CPU 0 上运行。
taskset -p 1 7013
# taskset -p 1 7013
Copy to Clipboard Copied!
您可以组合列出的选项。
第 20 章 管理内存不足状态
内存不足(OOM)是分配所有可用内存(包括交换空间)的计算状态。通常,这会导致系统 panic 并按预期停止工作。提供的说明有助于避免系统中的 OOM 状态。
先决条件
- 您在系统上具有 root 权限。
20.1. 更改内存不足值
/proc/sys/vm/panic_on_oom
文件包含一个控制内存不足(OOM)行为的交换机的值。如果文件包含 1
,则 OOM 上的内核 panics 并按预期停止运行。
默认值为 0,
它指示内核在系统处于 OOM 状态时调用 oom_killer ()
函数。通常,oom_killer ()
会终止不必要的进程,从而使系统可以存活。
您可以更改 /proc/sys/vm/panic_on_oom
的值。
流程
显示
/proc/sys/vm/panic_on_oom
的当前值。cat /proc/sys/vm/panic_on_oom 0
# cat /proc/sys/vm/panic_on_oom 0
Copy to Clipboard Copied! 要更改
/proc/sys/vm/panic_on_oom
中的值:向
/proc/sys/vm/panic_on_oom
回显新值。echo 1 > /proc/sys/vm/panic_on_oom
# echo 1 > /proc/sys/vm/panic_on_oom
Copy to Clipboard Copied!
建议您在 OOM 上进行 Real-Time 内核panic
。否则,当系统遇到 OOM 状态时,它不再确定。
验证
显示
/proc/sys/vm/panic_on_oom
的值。cat /proc/sys/vm/panic_on_oom 1
# cat /proc/sys/vm/panic_on_oom 1
Copy to Clipboard Copied! - 验证显示的值是否与指定的值匹配。
20.2. 在内存不足状态时,要终止的进程的优先级
您可以优先选择 oom_killer ()
函数终止的进程。这样可保证高优先级进程在 OOM 状态期间保持运行。每个进程都有一个目录 /proc/PID
。每个目录包括以下文件:
-
oom_score_adj ' - 'oom_score_adj 的有效分数
,范围为 -16 到 +15。这个值用于计算进程的性能空间,该算法也考虑了运行进程的时间以及其它因素。 -
oom_score
- 包含使用oom_score_adj
中的值计算的算法结果。
在内存不足状态下,oom_killer ()
函数终止具有最高 oom_score
的进程。
您可以通过编辑进程的 'oom_score_adj ' 文件来对进程终止的优先级。
先决条件
- 知道您要优先级的进程 ID (PID)。
流程
显示进程的当前
oom_score
。cat /proc/12465/oom_score 79872
# cat /proc/12465/oom_score 79872
Copy to Clipboard Copied! 为进程显示
oom_score_adj
的内容。*cat /proc/12465/oom_score_adj * 13
# *cat /proc/12465/oom_score_adj * 13
Copy to Clipboard Copied! 编辑
oom_score_adj
中的值。*echo -5 > /proc/12465/oom_score_adj *
# *echo -5 > /proc/12465/oom_score_adj *
Copy to Clipboard Copied!
验证
显示进程的当前
oom_score
。cat /proc/12465/oom_score 78
# cat /proc/12465/oom_score 78
Copy to Clipboard Copied! - 验证显示的值是否低于上一个值。
20.3. 为进程禁用内存不足终止程序
您可以通过将 oom_score_adj
设置为 -17
的保留值来禁用进程的 oom_killer ()
函数。这将使进程保持活动状态,甚至处于 OOM 状态。
流程
将
oom_score_adj
中的值设置为-17
。echo -17 > /proc/12465/oom_score_adj
# echo -17 > /proc/12465/oom_score_adj
Copy to Clipboard Copied!
验证
显示进程的当前
oom_score
。cat /proc/12465/oom_score 0
# cat /proc/12465/oom_score 0
Copy to Clipboard Copied! -
验证显示的值是否为
0。
第 21 章 使用 tuna CLI 改进延迟
您可以使用 tuna
CLI 来提高系统上的延迟。RHEL 10 的 tuna CLI 包括命令行,该命令行基于 argparse
解析模块。接口提供以下功能:
- 更标准化的命令和选项菜单
-
使用接口,您可以使用预定义的输入和
tuna
可确保输入正确类型 - 在如何使用参数时自动生成使用帮助消息,并提供带有无效参数的错误消息
21.1. 先决条件
-
tuna
和python-linux-procfs
软件包已安装。 - 您在系统上具有 root 权限。
21.2. tuna
CLI
tuna
命令行界面(CLI)是一个帮助您对系统进行调优更改的工具。
tuna
工具设计为在运行的系统中使用,并立即进行更改。这允许任何特定应用程序的测量工具在更改后马上查看和分析系统性能。
tuna
CLI 现在有一组命令,它们之前是操作选项。这些命令是:
- 隔离
-
将所有线程和 IRQ 移出
CPU-LIST
。 - Include
-
将所有线程配置为在
CPU LIST
上运行。 - Move
-
将特定的实体移到
CPU-LIST
。 - spread
-
将所选实体分散到
CPU LIST
上。 - priority
-
设置线程调度程序可调项,如
POLICY
和RTPRIO
。 - run
- 分叉新进程并运行 命令。
- save
-
将
kthreads
sched
可调项
保存到FILENAME
。 - apply
- 应用配置文件中定义的更改。
- show_threads
- 显示线程列表。
- show_irqs
-
显示
IRQ
列表。 - show_configs
- 显示现有配置文件列表。
- what_is
- 提供有关所选实体的帮助。
- GUI
- 启动图形用户界面(GUI)。
您可以使用 tuna -h
命令查看命令。对于每个命令,您可以使用 tuna < <command> -h
命令查看可选的参数。例如,使用 tuna isolate -h
命令,您可以查看 用于隔离
的选项。
21.3. 使用 tuna
CLI 隔离 CPU
您可以使用 tuna
CLI 将中断(IRQ)与不同专用 CPU 上的用户进程隔离,以最大程度降低实时环境中延迟。有关隔离 CPU 的更多信息,请参阅 中断和进程绑定。
先决条件
-
tuna
和python-linux-procfs
软件包已安装。 - 您在系统上具有 root 权限。
流程
隔离一个或多个 CPU。
tuna isolate --cpus=<cpu_list>
# tuna isolate --cpus=<cpu_list>
Copy to Clipboard Copied! cpu_list
是用逗号分开的列表或一系列要隔离的 CPU。例如:
tuna isolate --cpus=0,1
# tuna isolate --cpus=0,1
Copy to Clipboard Copied! 或者
tuna isolate --cpus=0-5
# tuna isolate --cpus=0-5
Copy to Clipboard Copied!
21.4. 使用 tuna
CLI 将中断移到指定的 CPU
您可以使用 tuna
CLI 将中断(IRQ)移到专用 CPU,以便在实时环境中最小化或消除延迟。有关移动 IRQ 的更多信息,请参阅 中断和进程绑定。
先决条件
-
tuna
和python-linux-procfs
软件包已安装。 - 您在系统上具有 root 权限。
流程
列出将 IRQ 列表附加到的 CPU。
tuna show_irqs --irqs=<irq_list>
# tuna show_irqs --irqs=<irq_list>
Copy to Clipboard Copied! irq_list
是您要列出附加 CPU 的 IRQ 的逗号分隔列表。例如:
tuna show_irqs --irqs=128
# tuna show_irqs --irqs=128
Copy to Clipboard Copied! 将 IRQ 列表附加到 CPU 列表。
tuna move --irqs=irq_list --cpus=<cpu_list>
# tuna move --irqs=irq_list --cpus=<cpu_list>
Copy to Clipboard Copied! irq_list
是您要附加的 IRQs 的逗号分隔列表,cpu_list
是要附加或一组 CPU 的以逗号分隔的 CPU 列表。例如:
tuna move --irqs=128 --cpus=3
# tuna move --irqs=128 --cpus=3
Copy to Clipboard Copied!
验证
在将任何 IRQ 移到指定的 CPU 前和之后,比较所选 IRQ 的状态。
tuna show_irqs --irqs=128
# tuna show_irqs --irqs=128
Copy to Clipboard Copied!
21.5. 使用 tuna
CLI 更改进程调度策略和优先级
您可以使用 tuna
CLI 更改进程调度策略和优先级。
先决条件
-
tuna
和python-linux-procfs
软件包已安装。 您在系统上具有 root 权限。
注意分配
OTHER
和BATCH
调度策略不需要 root 权限。
流程
查看线程的信息。
tuna show_threads --threads=<thread_list>
# tuna show_threads --threads=<thread_list>
Copy to Clipboard Copied! thread_list
是您要显示的进程的逗号分隔列表。例如:
tuna show_threads --threads=42369,42416,43859
# tuna show_threads --threads=42369,42416,43859
Copy to Clipboard Copied! 修改进程调度策略和线程的优先级。
tuna priority scheduling_policy:priority_number --threads=<thread_list>
# tuna priority scheduling_policy:priority_number --threads=<thread_list>
Copy to Clipboard Copied! -
thread_list
是您要显示的调度策略和优先级的以逗号分隔的进程列表。 scheduling_policy
是以下之一:- 其他
- BATCH
- FIFO - First Out
- RR - Round Robin
priority_number
是优先级号从 0 到 99,其中0
没有优先级,99
是最高优先级。注意OTHER
和BATCH
调度策略不需要指定优先级。此外,唯一有效的优先级(如果指定)是0。
FIFO
和RR
调度策略需要优先级1
或更多。例如:
-
tuna priority FIFO:1 --threads=42369,42416,43859
# tuna priority FIFO:1 --threads=42369,42416,43859
验证
- 查看线程的信息,以确保信息更改。
tuna show_threads --threads=42369,42416,43859
# tuna show_threads --threads=42369,42416,43859
第 22 章 设置调度程序优先级
Red Hat Enterprise Linux for Real Time 内核允许对调度程序优先级进行精细的控制。它还允许将应用程序级别的程序调度到高于内核线程的优先级。
设置调度程序优先级可能会导致系统变得无响应,或者如果关键内核进程无法根据需要运行,则行为不可预测。最终,正确的设置取决于工作负载。
22.1. 查看线程调度优先级
线程优先级使用一系列级别设置,范围从 0 (
最低优先级)到 99
(最高优先级)。systemd
服务管理器可用于在内核启动后更改线程的默认优先级。
流程
要查看正在运行的线程的调度优先级,请使用 tuna 工具:
tuna --show_threads
# tuna --show_threads thread ctxt_switches pid SCHED_ rtpri affinity voluntary nonvoluntary cmd 2 OTHER 0 0xfff 451 3 kthreadd 3 FIFO 1 0 46395 2 ksoftirqd/0 5 OTHER 0 0 11 1 kworker/0:0H 7 FIFO 99 0 9 1 posixcputmr/0 ...[output truncated]...
Copy to Clipboard Copied!
22.2. 在引导过程中更改服务优先级
使用 systemd
,您可以在引导过程中为启动的服务设置实时优先级。
单元配置指令用于在引导过程中更改服务的优先级。引导过程优先级更改通过使用 /etc/systemd/system/service.service.d/priority.conf 的 service
部分中的以下指令完成:
CPUSchedulingPolicy=
设置已执行进程的 CPU 调度策略。在 Linux 上获取一个调度类:
-
其他
-
batch
-
idle
-
fifo
-
rr
CPUSchedulingPriority=
设置已执行进程的 CPU 调度优先级。可用的优先级范围取决于所选的 CPU 调度策略。对于实时调度策略,可以使用 1
(最低优先级)和 99
(最高优先级)之间的整数。
先决条件
- 有管理员特权。
- 在启动时运行的服务。
流程
对于现有服务:
为服务创建补充的服务配置目录文件。
cat <<-EOF > /etc/systemd/system/mcelog.service.d/priority.conf
# cat <<-EOF > /etc/systemd/system/mcelog.service.d/priority.conf
Copy to Clipboard Copied! 将调度策略和优先级添加到
[Service]
部分中的 文件。例如:
[Service] CPUSchedulingPolicy=fifo CPUSchedulingPriority=20 EOF
[Service] CPUSchedulingPolicy=fifo CPUSchedulingPriority=20 EOF
Copy to Clipboard Copied! 重新加载
systemd
脚本配置。systemctl daemon-reload
# systemctl daemon-reload
Copy to Clipboard Copied! 重启该服务。
systemctl restart mcelog
# systemctl restart mcelog
Copy to Clipboard Copied!
验证
显示服务的优先级。
tuna -t mcelog -P
$ tuna -t mcelog -P
Copy to Clipboard Copied! 输出显示了服务配置的优先级。
例如:
thread ctxt_switches pid SCHED_ rtpri affinity voluntary nonvoluntary cmd 826 FIFO 20 0,1,2,3 13 0 mcelog
thread ctxt_switches pid SCHED_ rtpri affinity voluntary nonvoluntary cmd 826 FIFO 20 0,1,2,3 13 0 mcelog
Copy to Clipboard Copied!
22.3. 配置服务的 CPU 使用量
使用 systemd
,您可以指定可在哪些服务上运行的 CPU。
先决条件
- 有管理员特权。
流程
为服务创建补充的服务配置目录文件。
md sscd
# md sscd
Copy to Clipboard Copied! 使用
[Service]
部分中的CPUAffinity
属性将服务使用的 CPU 添加到 文件中。例如:
[Service] CPUAffinity=0,1 EOF
[Service] CPUAffinity=0,1 EOF
Copy to Clipboard Copied! 重新加载 systemd 脚本配置。
systemctl daemon-reload
# systemctl daemon-reload
Copy to Clipboard Copied! 重启该服务。
systemctl restart service
# systemctl restart service
Copy to Clipboard Copied!
验证
显示指定服务仅限于的 CPU。
tuna -t mcelog -P
$ tuna -t mcelog -P
Copy to Clipboard Copied! 其中
service
是指定的服务。以下输出显示
mcelog
服务仅限于 CPU 0 和 1。thread ctxt_switches pid SCHED_ rtpri affinity voluntary nonvoluntary cmd 12954 FIFO 20 0,1 2 1 mcelog
thread ctxt_switches pid SCHED_ rtpri affinity voluntary nonvoluntary cmd 12954 FIFO 20 0,1 2 1 mcelog
Copy to Clipboard Copied!
22.4. 优先级映射
优先级在组中定义,有一些组专用于特定内核功能。对于实时调度策略,使用 1 (最低优先级)和 99
(最高优先级)之间的整数。
下表描述了优先级范围,可在设置进程的调度策略时使用。
Priority | 线程 | 描述 |
---|---|---|
1 | 低优先级内核线程 |
此优先级通常为需要超过 |
2 - 49 | 可供使用 | 用于典型的应用程序优先级的范围。 |
50 | 默认 hard-IRQ 值 | |
51 - 98 | 高优先级线程 | 对定期执行的线程使用此范围,且必须快速响应时间。不要将此范围用于 CPU 密集型线程,因为您将中断。 |
99 | Watchdogs 和 migration | 必须以最高优先级运行的系统线程。 |
第 23 章 网络确定性提示
TCP 可能会对延迟产生较大影响。TCP 增加延迟,以获取效率、控制拥塞并确保可靠交付。在调整时,请考虑以下点:
- 您是否需要订购交付?
您需要保护数据包丢失吗?
传输超过一次的数据包可能会导致延迟。
您需要使用 TCP?
考虑通过在套接字中使用
TCP_NODELAY
来禁用 Nagle 缓冲算法。Nagle 算法收集小的传出数据包,以一次性发送所有,并可能会对延迟产生不利的影响。
23.1. 为延迟或吞吐量敏感的服务优化 RHEL
合并调优的目的是最小化给定工作负载所需的中断数量。在高吞吐量情况下,目标是有尽可能少的中断,同时保持高数据率。在低延迟情况下,可以使用更多中断来快速处理流量。
您可以调整网卡上的设置,以增加或减少组合到单个中断的数据包数。因此,您可以提高流量的吞吐量或延迟。
流程
识别遇到瓶颈的网络接口:
ethtool -S enp1s0
# ethtool -S enp1s0 NIC statistics: rx_packets: 1234 tx_packets: 5678 rx_bytes: 12345678 tx_bytes: 87654321 rx_errors: 0 tx_errors: 0 rx_missed: 0 tx_dropped: 0 coalesced_pkts: 0 coalesced_events: 0 coalesced_aborts: 0
Copy to Clipboard Copied! 识别在其名称中包含
drop
、discard
或error
数据包计数器。这些特定统计测量网络接口卡(NIC)数据包缓冲区的实际数据包丢失,这可能是 NIC 合并造成的。监控您在上一步中标识的数据包计数器的值。
将它们与网络的期望值进行比较,以确定任何特定接口是否遇到了瓶颈。网络瓶颈的一些常见迹象包括但不限于:
- 网络接口上的许多错误
- 高数据包丢失
网络接口的大量使用
注意在识别网络瓶颈时,其他重要因素包括 CPU 使用率、内存使用率和磁盘 I/O。
检查当前的中断合并设置:
ethtool -c enp1s0
# ethtool -c enp1s0 Coalesce parameters for enp1s0: Adaptive RX: off Adaptive TX: off RX usecs: 100 RX frames: 8 RX usecs irq: 100 RX frames irq: 8 TX usecs: 100 TX frames: 8 TX usecs irq: 100 TX frames irq: 8
Copy to Clipboard Copied! -
usecs
值指的是接收方或传送方在产生中断前等待的微秒数。 -
frames
值指的是接收方或传送方在产生中断前等待的帧数。 irq
值用于在网络接口已经处理中断时配置中断调节。注意不是所有网络接口卡都支持报告和更改示例输出中的所有值。
-
Adaptive RX/TX
值代表自适应中断合并机制,它动态调整中断合并设置。根据数据包条件,当Adaptive RX/TX
启用时,NIC 驱动程序会自动计算合并值(每个 NIC 驱动程序的算法都不一样)。
-
根据需要修改合并设置。例如:
在
ethtool.coalesce-adaptive-rx
被禁用时,请将ethtool.coalesce-rx-usecs
配置,来在产生中断前将 RX 数据包的延迟设为 100 微秒:nmcli connection modify enp1s0 ethtool.coalesce-rx-usecs 100
# nmcli connection modify enp1s0 ethtool.coalesce-rx-usecs 100
Copy to Clipboard Copied! 启用
ethtool.coalesce-adaptive-rx
,而ethtool.coalesce-rx-usecs
设为其默认值:nmcli connection modify enp1s0 ethtool.coalesce-adaptive-rx on
# nmcli connection modify enp1s0 ethtool.coalesce-adaptive-rx on
Copy to Clipboard Copied! 修改 Adaptive-RX 设置,如下所示:
-
关注低延迟(低于 50us)的用户不应启用
Adaptive-RX
。 -
关注吞吐量的用户可能启用
Adaptive-RX
,而不会造成任何损害。如果他们不希望使用自适应中断合并机制,他们可以尝试将ethtool.coalesce-rx-usecs
设置为大值,如 100us 或 250us 。 - 在出现问题前,不确定其需求的用户不应修改此设置。
-
关注低延迟(低于 50us)的用户不应启用
重新激活连接:
nmcli connection up enp1s0
# nmcli connection up enp1s0
Copy to Clipboard Copied!
验证
监控网络性能,并检查丢弃的数据包:
ethtool -S enp1s0
# ethtool -S enp1s0 NIC statistics: rx_packets: 1234 tx_packets: 5678 rx_bytes: 12345678 tx_bytes: 87654321 rx_errors: 0 tx_errors: 0 rx_missed: 0 tx_dropped: 0 coalesced_pkts: 12 coalesced_events: 34 coalesced_aborts: 56 ...
Copy to Clipboard Copied! rx_errors
、rx_dropped
、tx_errors
和tx_dropped
字段的值应该为 0 或接近于 0 ,取决于网络流量和系统资源。这些字段中的高值表示有网络问题。您的计数器可以有不同的名称。严格监控其名称中包含"drop"、"discard"或"error"的数据包计数器。rx_packets
、tx_packets
、rx_bytes
和tx_bytes
的值应该随时间的推移而增加。如果值没有增加,则可能有网络问题。数据包计数器可以有不同的名称,具体取决于您的 NIC 驱动程序。重要ethtool
命令输出可能会因使用的 NIC 和驱动程序而变化。专注于非常低延迟的用户可以使用应用程序级指标或内核数据包时间戳 API 来进行监控。
23.2. 以太网网络的流控制
在以太网链路上,网络接口和交换机端口之间持续的数据传输可能会导致缓冲区容量满了。缓冲区容量满了会导致网络拥塞。在这种情况下,当发送方传输数据的速度高于接收方的处理能力时,可能会因为链接另一端(其是交换机端口)上网络接口的低数据处理能力而发生数据包丢失。
流控制机制管理以太网链路之间的数据传输,其中每个发送方和接收方都有不同的发送和接收能力。为避免数据包丢失,以太网流控制机制会临时暂停数据包传输,以便管理交换机端口的高传输率。请注意,交换机不会转发交换机端口之外的暂停帧。
当接收(RX)缓冲区变满时,接收方会向传送方发送暂停帧。然后,传送方会停止数据传输一个亚秒时间段,同时继续在此暂停期间缓冲传入的数据。此持续时间为接收方提供了足够时间来清空其接口缓冲区,并防止缓冲区溢出。
以太网链接的任一端都可以向另一个端发送暂停帧。如果网络接口的接收缓冲区已满,网络接口将向交换机端口发送暂停帧。类似,当交换机端口的接收缓冲区已满时,交换机端口会向网络接口发送暂停帧。
默认情况下,Red Hat Enterprise Linux 中的大多数网络驱动程序都启用了暂停帧支持。要显示网络接口的当前设置,请输入:
ethtool --show-pause enp1s0
# ethtool --show-pause enp1s0
Pause parameters for enp1s0:
...
RX: on
TX: on
...
与您的交换机厂商确认您的交换机是否支持暂停帧。
第 24 章 使用 trace-cmd 追踪延迟
trace-cmd
工具是 ftrace
工具的前端。通过使用 trace-cmd
,您可以启用 ftrace
操作,而无需写入 /sys/kernel/debug/tracing/
目录。trace-cmd
不会在安装时添加任何开销。
先决条件
- 有管理员特权。
24.1. 安装 trace-cmd
trace-cmd
工具为 ftrace
工具提供了一个前端。
先决条件
- 有管理员特权。
流程
安装
trace-cmd
工具。dnf install trace-cmd
# dnf install trace-cmd
Copy to Clipboard Copied!
24.2. 运行 trace-cmd
您可以使用 trace-cmd
工具来访问所有 ftrace
功能。
先决条件
- 有管理员特权。
流程
输入
trace-cmd 命令
其中
command
是ftrace
选项。注意有关命令和选项的完整列表,请参阅
trace-cmd (1)
手册页。大多数命令也具有自己的 man pagetrace-cmd-命令
。
24.3. trace-cmd 示例
命令示例演示了如何使用 trace-cmd
工具跟踪内核功能。
例子
在 myapp 运行时,启用并启动在内核中执行的记录功能。
trace-cmd record -p function myapp
# trace-cmd record -p function myapp
Copy to Clipboard Copied! 这记录了所有 CPU 和所有任务的功能,即使与 myapp 无关。
显示结果。
trace-cmd report
# trace-cmd report
Copy to Clipboard Copied! 仅记录在 myapp 运行时以 sched 开头的功能。
trace-cmd record -p function -l 'sched*' myapp
# trace-cmd record -p function -l 'sched*' myapp
Copy to Clipboard Copied! 启用所有 IRQ 事件。
trace-cmd start -e irq
# trace-cmd start -e irq
Copy to Clipboard Copied! 启动
wakeup_rt
tracer。trace-cmd start -p wakeup_rt
# trace-cmd start -p wakeup_rt
Copy to Clipboard Copied! 在禁用功能追踪时启动
preemptirqsoff
tracer。trace-cmd start -p preemptirqsoff -d
# trace-cmd start -p preemptirqsoff -d
Copy to Clipboard Copied! 注意RHEL 8 中的
trace-cmd
版本关闭ftrace_enabled
,而不是使用function-trace
选项。您可以使用trace-cmd start -p
功能再次启用ftrace
。恢复在
trace-cmd
启动修改前系统所处的状态。trace-cmd start -p nop
# trace-cmd start -p nop
Copy to Clipboard Copied! 如果要在使用
trace-cmd
后使用debugfs
文件系统(无论是在 meantime 中重启系统),这是很重要的。跟踪单个追踪点。
trace-cmd record -e sched_wakeup ls /bin
# trace-cmd record -e sched_wakeup ls /bin
Copy to Clipboard Copied! 停止追踪。
trace-cmd record stop
# trace-cmd record stop
Copy to Clipboard Copied!
第 25 章 使用 tuned-profiles-real-time 隔离 CPU
要为应用程序线程提供最可能执行时间,您可以隔离 CPU。因此,尽可能从 CPU 中删除尽可能多的任务。隔离 CPU 通常涉及:
- 删除所有用户空间线程。
- 删除所有 unbound 内核线程。与内核相关的绑定线程链接到特定 CPU,且无法移动。
-
通过修改系统中每个中断请求(IRQ)编号
N
的/proc/irq/N/smp_affinity
属性来删除中断。
通过使用 tuned-profiles-realtime
软件包的 isolated_cores=cpulist
配置选项,您可以自动执行操作来隔离 CPU。
先决条件
- 有管理员特权。
25.1. 选择要隔离的 CPU
选择要隔离的 CPU 需要仔细考虑系统的 CPU 拓扑。不同的用例需要不同的配置:
- 如果您有一个多线程应用程序,线程需要通过共享缓存相互通信,则需要在同一 NUMA 节点或物理套接字上保留它们。
- 如果您运行多个不相关的实时应用程序,请将 CPU 与 NUMA 节点或套接字分离。
hwloc
软件包提供用于获取 CPU 信息的工具,包括 lstopo-no-graphics
和 numactl
。
先决条件
-
已安装
hwloc
软件包。
流程
查看物理软件包中可用 CPU 的布局:
lstopo-no-graphics --no-io --no-legend --of txt
# lstopo-no-graphics --no-io --no-legend --of txt
Copy to Clipboard Copied! 图 25.1. 使用 lstopo-no-graphics 显示 CPU 布局
此命令对多线程应用程序很有用,因为它显示有多少内核和套接字可用,以及 NUMA 节点的逻辑距离。
另外,
hwloc-gui
软件包提供了lstopo
工具,它生成图形输出。查看 CPU 的更多信息,如节点之间的距离:
numactl --hardware
# numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 node 0 size: 16159 MB node 0 free: 6323 MB node 1 cpus: 4 5 6 7 node 1 size: 16384 MB node 1 free: 10289 MB node distances: node 0 1 0: 10 21 1: 21 10
Copy to Clipboard Copied!
25.2. 使用 TuneD 的 isolated_cores 选项隔离 CPU
隔离 CPU 的初始机制是在内核引导命令行中指定 boot 参数 isolcpus=cpulist
。为 RHEL for Real Time 执行此操作的建议方法是使用 TuneD
守护进程及其 tuned-profiles-realtime
软件包。
在 tuned-profiles-realtime
版本 2.19 及更高版本中,内置函数 calc_isolated_cores
会自动应用初始 CPU 设置。/etc/tuned/realtime-variables.conf
配置文件包含默认变量内容为 isolated_cores=${f:calc_isolated_cores:2}
。
默认情况下,calc_isolated_cores
为每个插槽保留一个内核,用于内务处理并隔离其余内核。如果必须更改默认配置,请在 /etc/tuned/realtime-variables.conf
配置文件中注释掉 isolated_cores=${f:calc_isolated_cores:2}
行,并按照流程步骤使用 TuneD 的 isolated_cores
选项隔离 CPU。
先决条件
-
TuneD
和tuned-profiles-realtime
软件包已安装。 - 您在系统上具有 root 权限。
流程
-
以 root 用户身份,在文本编辑器中打开
/etc/tuned/realtime-variables.conf
。 设置
isolated_cores=cpulist
,以指定您要隔离的 CPU。您可以使用 CPU 编号和范围。示例:
isolated_cores=0-3,5,7
isolated_cores=0-3,5,7
Copy to Clipboard Copied! 这将隔离内核 0、1、2、3、5 和 7。
在有 8 个内核的双插槽系统中,其中 NUMA 节点 0-3 和 NUMA 节点 1 具有内核 4-8,要为多线程应用程序分配两个内核,请指定:
isolated_cores=4,5
isolated_cores=4,5
Copy to Clipboard Copied! 这可防止任何用户空间线程分配给 CPU 4 和 5。
要为不相关的应用程序从不同的 NUMA 节点选择 CPU,请指定:
isolated_cores=0,4
isolated_cores=0,4
Copy to Clipboard Copied! 这可防止任何用户空间线程分配给 CPU 0 和 4。
使用
tuned-adm
工具激活实时TuneD
配置集。tuned-adm profile realtime
# tuned-adm profile realtime
Copy to Clipboard Copied! - 重启机器以使更改生效。
验证
在内核命令行中搜索
isolcpus
参数:cat /proc/cmdline | grep isolcpus BOOT_IMAGE=/vmlinuz-6.12.0-55.9.1.el10_0.x86_64 root=/dev/mapper/rhel_foo-root ro crashkernel=auto rd.lvm.lv=rhel_foo/root rd.lvm.lv=rhel_foo/swap console=ttyS0,115200n81 isolcpus=0,4
$ cat /proc/cmdline | grep isolcpus BOOT_IMAGE=/vmlinuz-6.12.0-55.9.1.el10_0.x86_64 root=/dev/mapper/rhel_foo-root ro crashkernel=auto rd.lvm.lv=rhel_foo/root rd.lvm.lv=rhel_foo/swap console=ttyS0,115200n81 isolcpus=0,4
Copy to Clipboard Copied!
25.3. 使用 nohz 和 nohz_full 参数隔离 CPU
nohz
和 nohz_full
参数修改指定 CPU 上的活动。要启用这些内核引导参数,您需要使用以下 TuneD 配置集之一: realtime-virtual-host
、realtime-virtual-guest
或 cpu-partitioning
。
nohz=on
减少特定 CPU 集合上的计时器活动。
nohz
参数主要用于减少空闲 CPU 上的计时器中断。这有助于通过允许空闲 CPU 以降低电源模式运行。虽然 nohz 参数不直接用于实时响应时间,但nohz
参数不会直接影响实时响应时间。但是,需要nohz
参数激活nohz_full
参数,该参数对实时性能有积极影响。nohz_full=cpulist
-
nohz_full
参数会以不同的方式对待指定 CPU 列表中的计时器 tick。如果将 CPU 指定为nohz_full
CPU,且 CPU 中只有一个可运行任务,则内核将停止向该 CPU 发送计时器 tick。因此,运行应用程序可能会花费更多时间,减少服务中断和上下文切换的时间。
第 26 章 限制 SCHED_OTHER 任务迁移
您可以使用 sched_nr_migrate
变量限制 SCHED_OTHER
迁移到其他 CPU 的任务。
先决条件
- 有管理员特权。
26.1. 任务迁移
如果 SCHED_OTHER
任务生成大量其他任务,则它们将在同一 CPU 上运行。迁移
任务或 softirq
将尝试平衡这些任务,以便它们可以在空闲的 CPU 上运行。
可以调整 sched_nr_migrate
选项,以指定每次要移动的任务数量。由于实时任务有不同的迁移方式,因此它们不会直接受到此问题的影响。但是,当 softirq
移动任务时,它会锁定运行队列 spinlock,从而禁用中断。
如果需要移动大量任务,它会在中断被禁用时发生,因此不允许同时发生计时器事件或唤醒。当 sched_nr_migrate
设置为一个大的值时,这可能会导致实时任务出现严重延迟。
26.2. 使用 sched_nr_migrate 变量限制 SCHED_OTHER 任务迁移
增加 sched_nr_migrate
变量提供来自 SCHED_OTHER
线程的高性能,这些线程会生成大量任务,但会牺牲实时延迟。
对于 SCHED_OTHER
任务性能的低实时任务延迟,必须降低该值。默认值为 8
。
流程
要调整
sched_nr_migrate
变量的值,请将值直接传给/proc/sys/kernel/sched_nr_migrate
:echo 2 > /proc/sys/kernel/sched_nr_migrate
# echo 2 > /proc/sys/kernel/sched_nr_migrate
Copy to Clipboard Copied!
验证
查看
/proc/sys/kernel/sched_nr_migrate
的内容:cat > /proc/sys/kernel/sched_nr_migrate 2
# cat > /proc/sys/kernel/sched_nr_migrate 2
Copy to Clipboard Copied!
第 27 章 减少 TCP 性能激增
生成 TCP 时间戳可能会导致 TCP 性能激增。sysctl
命令控制 TCP 相关条目的值,设置位于 /proc/sys/net/ipv4/tcp_timestamps
的时间戳内核参数。
先决条件
- 有管理员特权。
27.1. 关闭 TCP 时间戳
关闭 TCP 时间戳可减少 TCP 性能激增。
流程
关闭 TCP 时间戳:
sysctl -w net.ipv4.tcp_timestamps=0
# sysctl -w net.ipv4.tcp_timestamps=0 net.ipv4.tcp_timestamps = 0
Copy to Clipboard Copied! 输出显示
net.ip4.tcp_timestamps
选项的值是0。
也就是说,TCP 时间戳被禁用。
27.2. 打开 TCP 时间戳
生成时间戳可能会导致 TCP 性能激增。您可以通过禁用 TCP 时间戳来降低 TCP 性能激增。如果您发现生成 TCP 时间戳没有导致 TCP 性能激增,您可以启用它们。
流程
启用 TCP 时间戳。
sysctl -w net.ipv4.tcp_timestamps=1
# sysctl -w net.ipv4.tcp_timestamps=1 net.ipv4.tcp_timestamps = 1
Copy to Clipboard Copied! 输出显示
net.ip4.tcp_timestamps
的值为1
。也就是说,启用了 TCP 时间戳。
27.3. 显示 TCP 时间戳状态
您可以查看 TCP 时间戳生成的状态。
流程
显示 TCP 时间戳生成状态:
sysctl net.ipv4.tcp_timestamps
# sysctl net.ipv4.tcp_timestamps net.ipv4.tcp_timestamps = 0
Copy to Clipboard Copied! 值
1
表示正在生成时间戳。值0
表示没有生成时间戳。
第 28 章 使用 RCU 回调提高 CPU 性能
Read-Copy-Update
(RCU
)系统是一个无锁定机制,用于内核中线程的相互排除。由于执行 RCU 操作,在删除内存安全时,调用-backs 有时会在 CPU 上排队。
使用 RCU 回调提高 CPU 性能:
- 您可以从 Operator 中删除 CPU 来运行 CPU 回调。
- 您可以分配一个 CPU 来处理所有 RCU 回调。此 CPU 称为内务 CPU。
- 您可以从 RCU 卸载线程的责任中减轻 CPU。
这个组合减少了专用于用户工作负载的 CPU 的不同影响。
先决条件
- 有管理员特权。
-
tuna
软件包已安装
28.1. 卸载 RCU 回调
您可以使用 rcu_nocbs
和 rcu_nocb_poll
内核参数卸载 RCU
回调。
流程
要从运行 RCU 回调的候选中删除一个或多个 CPU,请在
rcu_nocbs
内核参数中指定 CPU 列表,例如:rcu_nocbs=1,4-6
rcu_nocbs=1,4-6
Copy to Clipboard Copied! 或者
rcu_nocbs=3
rcu_nocbs=3
Copy to Clipboard Copied! 第二个示例指示了 CPU 3 是一个 no-callback CPU 的内核。这意味着,RCU 回调不会在
rcuc/$CPU
线程固定到 CPU 3 中,而是在rcuo/$CPU
线程中进行。您可以将此 trhead 移到内务 CPU,以减轻 CPU 3 从被分配的 RCU 回调作业。
28.2. 移动 RCU 回调
您可以分配一个内务处理 CPU 来处理所有 RCU 回调线程。要做到这一点,请使用 tuna
命令,并将所有 RCU 回调移到内务 CPU。
流程
将 RCU 回调线程移到 housekeeping CPU 中:
tuna --threads=rcu --cpus=x --move
# tuna --threads=rcu --cpus=x --move
Copy to Clipboard Copied! 其中
x
是内务 CPU 的 CPU 号。
此操作减轻 CPU X 以外的所有 CPU 处理 RCU 回调线程。
28.3. 从 RCU 卸载线程中减轻 CPU
虽然 RCU 卸载线程可以在另一个 CPU 上执行 RCU 回调,但每个 CPU 负责处理对应的 RCU 卸载线程。您可以减轻这个职责的 CPU,
流程
设置
rcu_nocb_poll
内核参数。这个命令会导致计时器定期引发 RCU 卸载线程,以检查是否有回调在运行。
第 29 章 使用 ftrace 追踪延迟
ftrace
工具是 RHEL for Real Time 内核提供的诊断工具之一。ftrace
可以被开发人员用来分析和调试用户空间外发生的延迟和性能问题。ftrace
工具具有各种选项,允许您以不同的方式使用实用程序。它可用于跟踪上下文切换,测量高优先级任务启动所需的时间、中断的长度被禁用,或者列出给定期间执行的所有内核功能。
一些 ftrace
tracers (如功能 tracer)可能会导致大量数据生成,这可能会将 trace 日志分析转换为耗时的任务。但是,您可以指示 tracer 仅在应用程序到达关键代码路径时开始和结束。
先决条件
- 有管理员特权。
29.1. 使用 ftrace 工具来跟踪延迟
您可以使用 ftrace
工具跟踪延迟。
流程
查看系统上可用的 tracer。
cat /sys/kernel/debug/tracing/available_tracers function_graph wakeup_rt wakeup preemptirqsoff preemptoff irqsoff function nop
# cat /sys/kernel/debug/tracing/available_tracers function_graph wakeup_rt wakeup preemptirqsoff preemptoff irqsoff function nop
Copy to Clipboard Copied! ftrace
的用户界面是debugfs
中的一系列文件。ftrace
文件也位于/sys/kernel/debug/tracing/
目录中。移到
/sys/kernel/debug/tracing/
目录。cd /sys/kernel/debug/tracing
# cd /sys/kernel/debug/tracing
Copy to Clipboard Copied! 此目录中的文件只能由 root 用户修改,因为启用追踪可能会影响系统性能。
启动追踪会话:
-
从
/sys/kernel/debug/tracing/available_tracers
中的可用 tracer 列表中选择要使用的 tracer。 将选择器的名称插入到
/sys/kernel/debug/tracing/current_tracer
中。echo preemptoff > /sys/kernel/debug/tracing/current_tracer
# echo preemptoff > /sys/kernel/debug/tracing/current_tracer
Copy to Clipboard Copied! 注意如果您将单个 '>' 与 echo 命令搭配使用,它将覆盖文件中的任何现有值。如果要将值附加到文件,请使用 '>>'。
-
从
function-trace 选项很有用,因为使用
wakeup_rt
、preemptirqsoff
的追踪延迟,因此自动启用函数追踪
(这可能会增加开销)。检查是否启用了
function
和function_graph
tracing:cat /sys/kernel/debug/tracing/options/function-trace 1
# cat /sys/kernel/debug/tracing/options/function-trace 1
Copy to Clipboard Copied! -
值
1
表示启用了function
和function_graph
tracing。 -
值
0
表示禁用function
和function_graph
tracing。
-
值
默认情况下启用
function
和function_graph
tracing。要打开或关闭函数和function
_graph/sys/kernel/debug/tracing/options/function-trace
文件。echo 0 > /sys/kernel/debug/tracing/options/function-trace echo 1 > /sys/kernel/debug/tracing/options/function-trace
# echo 0 > /sys/kernel/debug/tracing/options/function-trace # echo 1 > /sys/kernel/debug/tracing/options/function-trace
Copy to Clipboard Copied! 重要使用
echo
命令时,请确保在值和 > 字符之间放置一个空格字符。在 shell 提示符下,使用
0&
gt;、1
&
gt; 和 2> (没有空格字符)指的是标准输入、标准输出和标准错误。错误地使用它们可能会导致意外的追踪输出。通过更改
/debugfs/tracing/
目录中各种文件的值来调整 tracer 的详细信息和参数。例如:
irqsoff
、preemptoff
、preempirqsoff
和wakeup
tracer 持续监控延迟。当它们记录大于tracing_max_latency
中记录的延迟时,会记录该延迟的 trace,tracing_max_latency
会更新为新的最长时间。这样,trace_max_latency
始终显示记录最高的延迟,因为它是上次重置的。要重置最大延迟,请在
tracing_max_latency
文件中回显0
:echo 0 > /sys/kernel/debug/tracing/tracing_max_latency
# echo 0 > /sys/kernel/debug/tracing/tracing_max_latency
Copy to Clipboard Copied! 要查看大于集合数量的延迟,以微秒为单位回显量:
echo 200 > /sys/kernel/debug/tracing/tracing_max_latency
# echo 200 > /sys/kernel/debug/tracing/tracing_max_latency
Copy to Clipboard Copied! 当设置追踪阈值时,它会覆盖最大延迟设置。当记录大于阈值的延迟时,无论最大延迟是什么,都会记录它。查看 trace 文件时,只会显示最后记录的延迟。
要设置阈值,请回显必须记录延迟的微秒数:
echo 200 > /sys/kernel/debug/tracing/tracing_thresh
# echo 200 > /sys/kernel/debug/tracing/tracing_thresh
Copy to Clipboard Copied!
查看追踪日志:
cat /sys/kernel/debug/tracing/trace
# cat /sys/kernel/debug/tracing/trace
Copy to Clipboard Copied! 要存储 trace 日志,请将它们复制到另一个文件中:
cat /sys/kernel/debug/tracing/trace > /tmp/lat_trace_log
# cat /sys/kernel/debug/tracing/trace > /tmp/lat_trace_log
Copy to Clipboard Copied! 查看要追踪的功能:
cat /sys/kernel/debug/tracing/set_ftrace_filter
# cat /sys/kernel/debug/tracing/set_ftrace_filter
Copy to Clipboard Copied! -
通过编辑
/sys/kernel/debug/tracing/set_ftrace_filter
中的设置来过滤要追踪的功能。如果在文件中没有指定过滤器,则会跟踪所有功能。 要更改过滤器设置,请回显要追踪的功能的名称。过滤器允许在搜索词的开头或结尾使用 '*' 通配符。
例如,请参阅 ftrace 示例。
29.2. ftrace 文件
以下是 /sys/kernel/debug/
目录中的主要文件。
ftrace 文件
- trace
-
显示
ftrace
跟踪输出的文件。这实际上是一个 trace 的快照,因为当该文件读取时,trace 会停止,且不会消耗读取的事件。也就是说,如果用户禁用追踪并读取此文件,它将在每次读取时都报告相同的内容。 - trace_pipe
-
在读取 trace live 时显示
ftrace
trace 的输出的文件。它是 producer/consumer 追踪。也就是说,每个读取都将消耗读取的事件。这可用于读取活跃的 trace,而不会在读取时停止 trace。 - available_tracers
- 已编译到内核中的 ftrace tracer 列表。
- current_tracer
-
启用或禁用
ftrace
tracer。 - events
- 包含要跟踪的事件的目录,可用于启用或禁用事件,以及为事件设置过滤器。
- tracing_on
-
禁用并启用对
ftrace
缓冲区的记录。通过tracing_on
文件禁用追踪不会禁用内核内发生的实际追踪。它只禁用写入缓冲区。仍然发生 trace 的工作,但数据不会在任何位置出现。
29.3. ftrace tracers
根据内核的配置方式,并非所有 tracer 都可能可用于给定的内核。对于 RHEL for Real Time 内核,trace 和 debug 内核具有与生产内核不同的 tracer。这是因为当 tracer 配置为内核中,但未激活时,一些 tracers 有显著的开销。这些 tracer 仅针对 trace
和 debug
内核启用。
tracers
- function
- 一个最广泛适用的 tracer。跟踪内核中的功能调用。这可能导致明显的开销,具体取决于跟踪的功能数量。如果没有激活,则会产生较少的开销。
- function_graph
function_graph
tracer 旨在以更视觉的方式显示结果。这个 tracer 还跟踪函数的退出,显示内核中函数调用的流。注意当启用时,这个 tracer 的开销比
函数
tracer 的更多开销,但在禁用时的开销相同。- Wakeup
- 报告所有 CPU 间发生活动的完整 CPU 追踪器。它记录系统中唤醒最高优先级任务所需的时间,无论任务是实时任务。记录唤醒非实时任务所需的时间会隐藏唤醒实时任务所需的时间。
- wakeup_rt
- 报告所有 CPU 间发生活动的完整 CPU 追踪器。它记录从当前最高优先级任务中获取的时间,以唤醒直到调度时间为止。此 tracer 仅记录实时任务的时间。
- preemptirqsoff
- 跟踪禁用抢占或中断的区域,并记录禁用抢占或中断的最大时间。
- preemptoff
- 与 preemptirqsoff 追踪程序类似,但只跟踪禁用了 pre-emption 的最大间隔。
- irqsoff
- 与 preemptirqsoff 追踪程序类似,但只跟踪禁用中断的最大间隔。
- nop
-
默认 tracer。它不提供任何追踪功能本身,但随着事件可能交入任何 tracer,nop tracer 用于追踪事件。
29.4. ftrace 示例
以下提供了一些更改要跟踪的功能过滤的示例。您可以在单词的开头和结尾使用 * 通配符。例如 :*irq\*
将选择名称中包含 irq 的所有功能。但是,通配符不能在单词内使用。
以双引号实施搜索词和通配符字符可确保 shell 不会尝试将搜索扩展到当前的工作目录。
过滤器示例
仅追踪
调度功能
:echo schedule > /sys/kernel/debug/tracing/set_ftrace_filter
# echo schedule > /sys/kernel/debug/tracing/set_ftrace_filter
Copy to Clipboard Copied! 跟踪以
锁定结尾的所有功能
:echo "*lock" > /sys/kernel/debug/tracing/set_ftrace_filter
# echo "*lock" > /sys/kernel/debug/tracing/set_ftrace_filter
Copy to Clipboard Copied! 跟踪以
spin_
开头的所有功能:echo "spin_*" > /sys/kernel/debug/tracing/set_ftrace_filter
# echo "spin_*" > /sys/kernel/debug/tracing/set_ftrace_filter
Copy to Clipboard Copied! 跟踪名称中
cpu
的所有功能:echo "cpu" > /sys/kernel/debug/tracing/set_ftrace_filter
# echo "cpu" > /sys/kernel/debug/tracing/set_ftrace_filter
Copy to Clipboard Copied!
第 30 章 应用程序时间戳
执行频繁时间戳的应用程序会受到读取时钟的 CPU 成本的影响。用于读取时钟的高成本和时间可能会对应用程序的性能造成负面影响。
您可以通过选择一个具有读机制的硬件时钟来降低读取时钟的成本,比默认时钟更快。
在 RHEL for Real Time 中,可以使用带有 clock_gettime ()
函数的 POSIX 时钟来获取更多性能,以生成可能最低 CPU 成本的时钟读取。
这些好处对于使用带有高读取成本的硬件时钟的系统更为明显。
30.1. POSIX 时钟
POSIX 是实施和代表时间源的标准。您可以为应用程序分配 POSIX 时钟,而不影响系统中的其他应用程序。这与内核选择并在系统中实施的硬件时钟相反。
用于读取给定 POSIX 时钟的函数是 clock_gettime ()
,它在 < time.h
> 中定义。内核与 clock_gettime ()
对应的是系统调用。当用户进程调用 clock_gettime ()
时:
-
对应的 C 库(
glibc
)调用sys_clock_gettime ()
系统调用。 -
sys_clock_gettime ()
执行请求的操作。 -
sys_clock_gettime ()
将结果返回给用户程序。
但是,从用户应用程序切换到内核的上下文具有 CPU 成本。虽然这个成本非常低,但如果操作重复了数千次,但累积的成本可能会对应用程序的整体性能产生影响。为了避免上下文切换到内核,从而更快地读取时钟,支持 CLOCK_MONOTONIC_COARSE
和 CLOCK_REALTIME_COARSE
POSIX 时钟,格式为虚拟动态共享对象(VDSO)库函数。
使用其中一个 _COARSE
时钟变体执行 clock_gettime ()
的时间读取不需要内核干预,且完全在用户空间中执行。这会显著提高性能。_COARSE
时钟读取的时间具有毫秒(ms)解析,这意味着不会记录小于 1 ms 的时间间隔。POSIX 时钟的 _COARSE
变体适合可容纳 millisecond 时钟分辨率的任何应用程序。
30.2. clock_gettime 中的 _COARSE 时钟变体
示例代码输出显示将 clock_gettime
功能与 CLOCK_MONOTONIC_COARSE
POSIX 时钟一起使用。
#include <time.h> main() { int rc; long i; struct timespec ts; for(i=0; i<10000000; i++) { rc = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); } }
#include <time.h>
main()
{
int rc;
long i;
struct timespec ts;
for(i=0; i<10000000; i++) {
rc = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
}
}
您可以改进上面的示例,添加检查来验证 clock_gettime()
的返回值,验证 rc
变量的值,或确保 ts
结构的内容被信任。
clock_gettime ()
man page 提供了有关编写更可靠的应用程序的更多信息。
使用 clock_gettime()
函数的程序必须通过将 -lrt
添加到 gcc
命令行来与 rt
库相关联。
$ gcc clock_timing.c -o clock_timing -lrt
第 31 章 使用 TCP_NODELAY 提高网络延迟
默认情况下,TCP
使用 Nagle 的算法收集小传出数据包,以一次性发送所有。这可能导致更高的延迟率。
先决条件
- 有管理员特权。
31.1. 使用 TCP_NODELAY 的影响
对发送的每个数据包需要低延迟的应用程序必须在启用了 TCP_NODELAY
选项的套接字上运行。这会在事件发生时立即向内核发送缓冲区写入。
- 备注
-
要使
TCP_NODELAY
有效,应用程序必须避免在逻辑上相关的缓冲区写入。否则,这些小写入会导致TCP
将这些多个缓冲区作为单个数据包发送,从而导致整体性能不佳。
如果应用程序有几个与逻辑相关的缓冲,且必须作为一个数据包发送,请应用以下临时解决方案之一以避免性能不佳:
-
在内存中构建连续数据包,然后在配置了
TCP
_NODELAY -
创建 I/O 向量,并在配置了
TCP_NODELAY
的套接字中使用writev
命令将它传递给内核。 -
使用
TCP_CORK
选项。TCP_CORK
告知TCP
在发送任何数据包前等待应用删除 cork。此命令会导致它接收的缓冲区附加到现有缓冲区中。这允许应用程序在内核空间中构建数据包,在使用为层提供抽象的不同库时,可能需要该数据包。
当应用程序中的每个组件在内核中构建逻辑数据包时,该套接字应该被取消封锁,允许 TCP
立即发送累积逻辑数据包。
31.2. 启用 TCP_NODELAY
TCP_NODELAY
选项会在事件发生时向内核发送缓冲区写入,且无延迟。使用 setsockopt ()
函数启用 TCP_NODELAY
。
流程
将以下行添加到
TCP
应用的.c
文件中:int one = 1; setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
int one = 1; setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
Copy to Clipboard Copied! - 保存文件并退出编辑器。
应用以下临时解决方案之一以防止性能不佳。
-
在内存中构建连续数据包,然后在配置了
TCP
_NODELAY -
创建 I/O 向量,并在配置了
TCP_NODELAY
的套接字中使用writev
将其传递给内核。
-
在内存中构建连续数据包,然后在配置了
31.3. 启用 TCP_CORK
TCP_CORK
选项可防止 TCP
发送任何数据包,直到套接字"不corked"。
流程
将以下行添加到
TCP
应用的.c
文件中:int one = 1; setsockopt(descriptor, SOL_TCP, TCP_CORK, &one, sizeof(one));
int one = 1; setsockopt(descriptor, SOL_TCP, TCP_CORK, &one, sizeof(one));
Copy to Clipboard Copied! - 保存文件并退出编辑器。
在由应用中各种组件在内核中构建了逻辑数据包后,禁用
TCP_CORK
。int zero = 0; setsockopt(descriptor, SOL_TCP, TCP_CORK, &zero, sizeof(zero));
int zero = 0; setsockopt(descriptor, SOL_TCP, TCP_CORK, &zero, sizeof(zero));
Copy to Clipboard Copied! TCP
立即发送累积逻辑数据包,而不等待来自应用程序的任何进一步数据包。
第 32 章 使用 mutex 防止资源过度使用
相互排除(mutex)算法用于防止过度使用常见资源。
32.1. mutex 选项
相互排除(mutex)算法用于防止使用通用资源的进程。快速用户空间 mutex (futex)是一个工具,它允许用户空间线程在不需要上下文切换到内核空间的情况下声明 mutex,只要 mutex 尚未由另一个线程保留。
当您使用标准属性初始化 pthread_mutex_t
对象时,会创建私有、非递归、非繁忙和非优先级继承功能。这个对象不提供 pthreads
API 和 RHEL for Real Time 内核提供的任何 benfits。
要从 pthreads
API 和 RHEL for Real Time 内核中受益,请创建一个 pthread_mutexattr_t
对象。此对象存储为 futex 定义的属性。
术语 futex
和 mutex
用于描述 POSIX 线程(pthread
) mutex 构造。
32.2. 创建 mutex 属性对象
要为 mutex
定义任何其他功能,请创建一个 pthread_mutexattr_t
对象。此对象存储 futex 的定义属性。这是您必须始终执行的基本安全流程。
流程
使用以下任一创建 mutex 属性对象:
-
pthread_mutex_t(my_mutex);
-
pthread_mutexattr_t(&my_mutex_attr);
-
pthread_mutexattr_init(&my_mutex_attr);
-
有关高级 mutex 属性的更多信息,请参阅高级 mutex 属性。
32.3. 使用标准属性创建 mutex
当您使用标准属性初始化 pthread_mutex_t
对象时,会创建私有、非递归、非繁忙和非优先级继承功能。
流程
使用以下命令之一在
pthreads
下创建一个 mutex 对象:-
pthread_mutex_t(my_mutex);
pthread_mutex_init(&my_mutex, &my_mutex_attr);
其中
&my_mutex_attr;
是一个 mutex 属性对象。
-
32.4. 高级 mutex 属性
以下高级 mutex 属性可以存储在 mutex 属性对象中:
mutex 属性
- 共享和私有 mutexes
共享 mutexes 可以在进程之间使用,但它们可能会带来更多的开销。
pthread_mutexattr_setpshared(&my_mutex_attr, PTHREAD_PROCESS_SHARED);
- 实时优先级继承
您可以使用优先级继承来避免对优先级进行优先级的问题。
pthread_mutexattr_setprotocol(&my_mutex_attr, PTHREAD_PRIO_INHERIT);
- 强大的互斥器
当 pthread dies 时,会发布 pthread 下的强大的 mutexes。但是,这会产生较高的开销。此字符串中的 _NP 表示这个选项是非 POSIX 或不可移植。
pthread_mutexattr_setrobust_np(&my_mutex_attr, PTHREAD_MUTEX_ROBUST_NP);
- mutex 初始化
共享 mutexes 可以在进程之间使用,但它们可能会带来更多的开销。
pthread_mutex_init(&my_mutex_attr, &my_mutex);
32.5. 清理 mutex 属性对象
在使用 mutex 属性对象创建 mutex 后,您可以保留属性对象来初始化同一类型的 mutexes,或者您可以清理它。mutex 在这两种情况下都不会受到影响。
流程
使用
pthread_mutexattr_destroy ()
函数清理属性对象:pthread_mutexattr_destroy(&my_mutex_attr);
pthread_mutexattr_destroy(&my_mutex_attr);
Copy to Clipboard Copied! mutex 现在作为常规的 pthread_mutex 运行,并可正常锁定、解锁和销毁。
第 33 章 分析应用程序性能
perf
是一个性能分析工具。它提供简单的命令行界面,并提取 Linux 性能测量的 CPU 硬件差异。perf
基于内核导出的 perf_events
接口。
perf
的一个优点是它既是内核和构架中立。可以在不需要特定系统配置的情况下检查分析数据。
先决条件
-
perf
软件包必须安装在系统上。 - 有管理员特权。
33.1. 收集系统范围统计信息
perf record
命令用于收集系统范围的统计信息。它可用于所有处理器。
流程
收集系统范围的性能统计信息。
perf record -a
# perf record -a ^C[ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.725 MB perf.data (~31655 samples) ]
Copy to Clipboard Copied! 在本例中,所有 CPU 使用
-a
选项表示,进程在几秒钟后被终止。结果显示它收集 0.725 MB 的数据并将其保存到新创建的perf.data
文件中。
验证
确保已创建结果文件。
ls
# ls perf.data
Copy to Clipboard Copied!
33.2. 归档性能分析结果
您可以使用
命令分析其他系统上 perf 的结果。如果出现以下情况,可能不需要这样做:
perf
archive
-
分析系统中已存在动态共享对象(DSO),如二进制文件和库,如
~/.debug/
缓存。 - 这两个系统都有相同的二进制文件集合。
流程
从
perf
命令创建结果的存档。perf archive
# perf archive
Copy to Clipboard Copied! 从归档创建 tarball。
tar cvf perf.data.tar.bz2 -C ~/.debug
# tar cvf perf.data.tar.bz2 -C ~/.debug
Copy to Clipboard Copied!
33.3. 分析性能分析结果
现在,可以使用 perf report
命令直接调查 perf record
功能中的数据。
流程
直接从
perf.data
文件或从归档的 tarball 分析结果。perf report
# perf report
Copy to Clipboard Copied! 报告的输出按照应用程序的最大 CPU 使用量百分比进行排序。它显示了示例是否在进程的内核或用户空间中发生。
报告显示有关从中获取示例的模块信息:
-
未放入内核模块的内核示例使用表示法
[kernel.kallsyms]
进行标记。 -
在内核模块中发生的内核示例被标记为
[module]
,[ext4]
。 对于用户空间中的进程,结果可能会显示与进程关联的共享库。
报告指示进程是否也发生在内核或用户空间中。
-
结果
[.]
表示用户空间。 -
结果
[k]
表示内核空间。
可以审核更精细的细节,包括适合经验丰富的
perf
开发人员的数据。-
未放入内核模块的内核示例使用表示法
33.4. 列出预定义的事件
有一系列可用选项来获取硬件追踪点活动。
流程
列出预定义的硬件和软件事件:
perf list
# perf list List of pre-defined events (to be used in -e): cpu-cycles OR cycles [Hardware event] stalled-cycles-frontend OR idle-cycles-frontend [Hardware event] stalled-cycles-backend OR idle-cycles-backend [Hardware event] instructions [Hardware event] cache-references [Hardware event] cache-misses [Hardware event] branch-instructions OR branches [Hardware event] branch-misses [Hardware event] bus-cycles [Hardware event] cpu-clock [Software event] task-clock [Software event] page-faults OR faults [Software event] minor-faults [Software event] major-faults [Software event] context-switches OR cs [Software event] cpu-migrations OR migrations [Software event] alignment-faults [Software event] emulation-faults [Software event] ...[output truncated]...
Copy to Clipboard Copied!
33.5. 获取指定事件的统计信息
您可以使用 perf stat
命令查看特定的事件。
流程
使用
perf stat
功能查看上下文切换数量:perf stat -e context-switches -a sleep 5
# perf stat -e context-switches -a sleep 5 Performance counter stats for 'sleep 5': 15,619 context-switches 5.002060064 seconds time elapsed
Copy to Clipboard Copied! 结果显示 5 秒,15619 上下文切换发生。
通过运行脚本来查看文件系统活动。下面显示了一个示例脚本:
for i in {1..100}; do touch /tmp/$i; sleep 1; done
# for i in {1..100}; do touch /tmp/$i; sleep 1; done
Copy to Clipboard Copied! 在另一个终端中运行
perf stat
命令:perf stat -e ext4:ext4_request_inode -a sleep 5
# perf stat -e ext4:ext4_request_inode -a sleep 5 Performance counter stats for 'sleep 5': 5 ext4:ext4_request_inode 5.002253620 seconds time elapsed
Copy to Clipboard Copied! 结果显示,脚本在 5 秒内需要创建 5 个文件,表示存在 5 个
索引节点
请求。
第 34 章 使用压力测试实时系统
stress-ng
工具测量系统的能力,以便在不可尝试的情况下保持良好的效率水平。stress-ng
工具是一种压力工作负载生成器,用于加载和压力所有内核接口。它包括广泛的压力机制,称为压力者。压力测试使得计算机工作困难和行程硬件问题(如热运行和操作系统漏洞)在系统过度工作时发生。
270 进行了不同的测试。这包括练习浮动点、整数、位操作、控制流和虚拟内存测试的 CPU 特定测试。
请谨慎使用 stress-ng
工具,因为某些测试可能会影响设计较差硬件上系统的热区域行点。这可能会影响系统性能,并导致过度出现系统延迟,这很难停止。
34.1. 测试 CPU 浮点单元和处理器数据缓存
浮点单元是处理器的功能部分,其执行浮点算术操作。浮点单元处理数学操作,使浮动数或十进制计算变得更加简单。
使用-- matrix-method
选项,您可以压力测试 CPU 浮动点操作和处理器数据缓存。
先决条件
- 在系统中具有 root 权限
流程
要测试一个 CPU 上的浮动点 60 秒,请使用 the-
matrix
选项:stress-ng --matrix 1 -t 1m
# stress-ng --matrix 1 -t 1m
Copy to Clipboard Copied! 要在多个 CPU 上运行多个压力(60 秒),请使用-
times or
-t
选项:stress-ng --matrix 0 -t 1m
# stress-ng --matrix 0 -t 1m stress-ng --matrix 0 -t 1m --times stress-ng: info: [16783] dispatching hogs: 4 matrix stress-ng: info: [16783] successful run completed in 60.00s (1 min, 0.00 secs) stress-ng: info: [16783] for a 60.00s run time: stress-ng: info: [16783] 240.00s available CPU time stress-ng: info: [16783] 205.21s user time ( 85.50%) stress-ng: info: [16783] 0.32s system time ( 0.13%) stress-ng: info: [16783] 205.53s total time ( 85.64%) stress-ng: info: [16783] load average: 3.20 1.25 1.40
Copy to Clipboard Copied! 具有 0 压力测试器的特殊模式,查询要运行的可用 CPU,无需指定 CPU 号。
总 CPU 时间为 4 x 60 秒(240 秒),其中 0.13% 在内核中为 85.50%,用户时间为 85.64%,
stress-ng
运行所有 CPU 的 85.64%。要使用 POSIX 消息队列测试进程间传递的消息,请使用 the
-mq
选项:stress-ng --mq 0 -t 30s --times --perf
# stress-ng --mq 0 -t 30s --times --perf
Copy to Clipboard Copied! mq
选项配置特定数量的进程,以使用 POSIX 消息队列强制上下文切换。这种压力测试旨在用于低数据缓存未命中。
34.2. 使用多个压力机制测试 CPU
stress-ng
工具运行多个压力测试。在默认模式中,它会并行运行指定的压力器机制。
先决条件
- 在系统中具有 root 权限
流程
运行多个 CPU 压力实例,如下所示:
stress-ng --cpu 2 --matrix 1 --mq 3 -t 5m
# stress-ng --cpu 2 --matrix 1 --mq 3 -t 5m
Copy to Clipboard Copied! 在示例中,
stress-ng
运行 CPU 压力器的两个实例,其中一个矩阵压力者和三个消息队列压力测试的实例,以测试五分钟。要并行运行所有压力测试,请使用--
all
选项:stress-ng --all 2
# stress-ng --all 2
Copy to Clipboard Copied! 在本例中,
stress-ng
会并行运行所有压力测试的两个实例。要在特定序列中运行每个不同的压力,请使用--
seq
选项。stress-ng --seq 4 -t 20
# stress-ng --seq 4 -t 20
Copy to Clipboard Copied! 在本例中,
stress-ng
会逐一运行一次对 20 分钟的所有压力,每个压力或与在线 CPU 数量匹配的实例数量。要从测试运行中排除特定的压力,请使用 the
-x
选项:stress-ng --seq 1 -x numa,matrix,hdd
# stress-ng --seq 1 -x numa,matrix,hdd
Copy to Clipboard Copied! 在本例中,
stress-ng
运行所有压力器,每个实例都不包括numa
,hdd
和key
ressors 机制。
34.3. 测量 CPU Heat 生成
为了测量 CPU 热代,指定的压力函数在短时间内生成高温度,以测试系统在最热生成之下的冷却可靠性和稳定性。使用-- matrix-size
选项,您可以在短时间内测量 Celsius 的 CPU 温度。
先决条件
- 您在系统上具有 root 权限。
流程
要在指定持续时间的高温度中测试 CPU 行为,请运行以下命令:
stress-ng --matrix 0 --matrix-size 64 --tz -t 60
# stress-ng --matrix 0 --matrix-size 64 --tz -t 60 stress-ng: info: [18351] dispatching hogs: 4 matrix stress-ng: info: [18351] successful run completed in 60.00s (1 min, 0.00 secs) stress-ng: info: [18351] matrix: stress-ng: info: [18351] x86_pkg_temp 88.00 °C stress-ng: info: [18351] acpitz 87.00 °C
Copy to Clipboard Copied! 在本例中,
stress-ng
将处理器软件包配置成rmal 区域,使其在 60 秒期间达到 88 位 Celsius。可选: 要在运行结束时打印报告,请使用--
tz
选项:stress-ng --cpu 0 --tz -t 60
# stress-ng --cpu 0 --tz -t 60 stress-ng: info: [18065] dispatching hogs: 4 cpu stress-ng: info: [18065] successful run completed in 60.07s (1 min, 0.07 secs) stress-ng: info: [18065] cpu: stress-ng: info: [18065] x86_pkg_temp 88.75 °C stress-ng: info: [18065] acpitz 88.38 °C
Copy to Clipboard Copied!
34.4. 使用 bogo 操作测量测试结果
stress-ng
工具可以通过测量每秒 bogo 操作来测量压力测试吞吐量。bogo 操作的大小取决于运行的压力。测试结果不是精确的,但提供了性能粗略估算。
您不能将此测量用作准确的基准指标。这些估计结果有助于了解不同内核版本或用于构建 stress-ng
的不同编译器版本中的系统性能变化。使用 --metrics-brief
选项显示可用 bogo 操作总数以及机器上的矩阵压力器性能。
先决条件
- 您在系统上具有 root 权限。
流程
要测量带有 bogo 操作的测试结果,请使用 with
-metrics-brief
选项:stress-ng --matrix 0 -t 60s --metrics-brief
# stress-ng --matrix 0 -t 60s --metrics-brief stress-ng: info: [17579] dispatching hogs: 4 matrix stress-ng: info: [17579] successful run completed in 60.01s (1 min, 0.01 secs) stress-ng: info: [17579] stressor bogo ops real time usr time sys time bogo ops/s bogo ops/s stress-ng: info: [17579] (secs) (secs) (secs) (real time) (usr+sys time) stress-ng: info: [17579] matrix 349322 60.00 203.23 0.19 5822.03 1717.25
Copy to Clipboard Copied! --metrics-brief
选项显示测试结果,以及由列表
压力者运行的总实时 bogo 操作(60 秒)。
34.5. 生成虚拟内存压力
在内存压力下,内核开始写出页面到交换。您可以使用 -page-in 选项强制非常数页面
交换回虚拟内存,对虚拟内存进行压力测试。这会导致虚拟机被大量练习。使用 --page-in
选项,您可以为 bigheap
、mmap
和虚拟机(vm
)压力启用此模式。page-in
选项,touch 分配不在核心的页面,强制它们进入页面。
先决条件
- 您在系统上具有 root 权限。
流程
要压力测试虚拟内存,请使用
--page-in
选项:stress-ng --vm 2 --vm-bytes 2G --mmap 2 --mmap-bytes 2G --page-in
# stress-ng --vm 2 --vm-bytes 2G --mmap 2 --mmap-bytes 2G --page-in
Copy to Clipboard Copied! 在本例中,
stress-ng
测试内存在有 4GB 内存的系统上(小于分配的缓冲区大小)、2 x 2GB 的vm
压力和 2 x 2GB 的mmap
压力(启用页面)。
34.6. 测试设备上的大型中断负载
以高频率运行计时器可生成大型中断负载。带有适当所选计时器频率的-- timer
压力可能会强制每秒有多个中断。
先决条件
- 您在系统上具有 root 权限。
流程
要生成中断负载,请使用 the
-timer
选项:stress-ng --timer 32 --timer-freq 1000000
# stress-ng --timer 32 --timer-freq 1000000
Copy to Clipboard Copied! 在本例中,
stress-ng
测试 1MHz 的 32 个实例。
34.7. 在程序中生成主要页面错误
使用 stress-ng
时,您可以通过在内存中未加载的页面中生成主要页面错误来测试和分析页面错误率。在新内核版本中,用户faultfd
机制会通知错误查找进程虚拟内存布局中的页面错误。
先决条件
- 您在系统上具有 root 权限。
流程
要在早期内核版本中生成主要页面错误,请使用:
stress-ng --fault 0 --perf -t 1m
# stress-ng --fault 0 --perf -t 1m
Copy to Clipboard Copied! 要在新内核版本中生成主要页面错误,请使用:
stress-ng --userfaultfd 0 --perf -t 1m
# stress-ng --userfaultfd 0 --perf -t 1m
Copy to Clipboard Copied!
34.8. 查看 CPU 压力测试机制
CPU 压力测试包含执行 CPU 的方法。您可以使用哪个选项打印输出来查看所有方法。
如果您没有指定测试方法,默认情况下,压力会以轮循方式检查所有压力,以测试每个压力者的 CPU。
先决条件
- 您在系统上具有 root 权限。
流程
打印所有可用压力器机制,使用
哪个选项
:stress-ng --cpu-method which
# stress-ng --cpu-method which cpu-method must be one of: all ackermann bitops callfunc cdouble cfloat clongdouble correlate crc16 decimal32 decimal64 decimal128 dither djb2a double euler explog fft fibonacci float fnv1a gamma gcd gray hamming hanoi hyperbolic idct int128 int64 int32
Copy to Clipboard Copied! 使用--
cpu-method
选项指定特定的 CPU 压力方法:stress-ng --cpu 1 --cpu-method fft -t 1m
# stress-ng --cpu 1 --cpu-method fft -t 1m
Copy to Clipboard Copied!
34.9. 使用验证模式
当测试处于活跃状态时,验证模式会验证结果。它将检查测试运行中的内存内容,并报告任何意外故障。
由于在这个模式下运行的额外验证步骤,所有压力者都没有验证模式,并且启用将减少 bogo 操作统计信息。
先决条件
- 您在系统上具有 root 权限。
流程
要验证压力测试结果,请使用--
verify
选项:stress-ng --vm 1 --vm-bytes 2G --verify -v
# stress-ng --vm 1 --vm-bytes 2G --verify -v
Copy to Clipboard Copied! 在本例中,
stress-ng
使用配置了vm
压力模式,打印对虚拟映射的内存进行全面映射的内存检查的输出。它 sanity 检查内存的读取和写入结果。
第 35 章 创建并运行的容器
本节提供有关使用实时内核创建和运行容器的信息。
先决条件
-
安装
podman
和其他容器相关工具。 - 熟悉 RHEL 上 Linux 容器的管理和管理。
-
安装
kernel-rt
软件包和其他实时相关软件包。
35.1. 创建容器
您可以将所有以下选项用于实时内核和主 RHEL 内核。kernel-rt
软件包带来了潜在的确定性改进,并允许常见的故障排除。
先决条件
- 有管理员特权。
流程
以下流程描述了如何配置与实时内核相关的 Linux 容器。
创建您要用于容器的目录。例如:
mkdir cyclictest
# mkdir cyclictest
Copy to Clipboard Copied! 进入该目录:
cd cyclictest
# cd cyclictest
Copy to Clipboard Copied! 登录到提供容器 registry 服务的主机:
podman login registry.redhat.io
# podman login registry.redhat.io Username: my_customer_portal_login Password: *** Login Succeeded!
Copy to Clipboard Copied! -
创建
Containerfile
: 如果您要从自定义 Containerfile 构建并构建 Containerfile,并构建它。以下是带有 cyclisttest 的示例:如果没有创建自己的镜像,您还可以拉取 realtime-tests-container 镜像来运行 cyclictest :
podman build -t cyclictest .
# podman build -t cyclictest .
Copy to Clipboard Copied!
35.2. 运行容器
您可以使用 Containerfile 运行构建的容器。
流程
使用
podman run
命令运行容器:podman run --device=/dev/cpu_dma_latency --cap-add ipc_lock --cap-add sys_nice --cap-add sys_rawio --rm -ti cyclictest
# podman run --device=/dev/cpu_dma_latency --cap-add ipc_lock --cap-add sys_nice --cap-add sys_rawio --rm -ti cyclictest /dev/cpu_dma_latency set to 0us policy: fifo: loadavg: 0.08 0.10 0.09 2/947 15 T: 0 ( 8) P:95 I:1000 C: 3209 Min: 1 Act: 1 Avg: 1 Max: 14 T: 1 ( 9) P:95 I:1500 C: 2137 Min: 1 Act: 2 Avg: 1 Max: 23 T: 2 (10) P:95 I:2000 C: 1601 Min: 1 Act: 2 Avg: 2 Max: 7 T: 3 (11) P:95 I:2500 C: 1280 Min: 1 Act: 2 Avg: 2 Max: 72 T: 4 (12) P:95 I:3000 C: 1066 Min: 1 Act: 1 Avg: 1 Max: 7 T: 5 (13) P:95 I:3500 C: 913 Min: 1 Act: 2 Avg: 2 Max: 87 T: 6 (14) P:95 I:4000 C: 798 Min: 1 Act: 1 Avg: 2 Max: 7 T: 7 (15) P:95 I:4500 C: 709 Min: 1 Act: 2 Avg: 2 Max: 29
Copy to Clipboard Copied!
本例演示了带有所需实时选项的 podman run
命令。例如:
-
第一个 out (FIFO)调度程序策略通过
-cap-add=sys_nice
选项为容器中运行的工作负载提供。这个选项还允许在调整实时工作负载时设置线程的 CPU 关联性,另一个重要的配置维度。 --device=/dev/cpu_dma_latency
选项使主机设备在容器内可用(由 cyclictest 工作负载用来配置 CPU 空闲时间管理)。如果指定的设备不可用,则会出现类似以下消息的错误:WARN: stat /dev/cpu_dma_latency failed: No such file or directory
当出现类似这些错误消息时,请参考 podman-run (1)手册页。要获得在容器内运行的特定工作负载,其他
podman-run
选项可能会很有用。在某些情况下,您还需要添加--
device=/dev/cpu 选项来添加
该目录层次结构,映射每个 CPU 设备文件,如/dev/cpupassphrase/msr
。
第 36 章 显示进程的优先级
您可以使用 sched_getattr
属性显示进程优先级的信息,以及有关进程的调度策略的信息。
先决条件
- 有管理员特权。
36.1. chrt 工具
chrt
工具检查并调整调度程序策略和优先级。它可以启动具有所需属性的新进程,或更改正在运行的进程的属性。
36.2. 使用 chrt 工具显示进程优先级
您可以显示指定进程的当前调度策略和调度优先级。
流程
使用 a
-p
选项运行chrt
工具,并指定正在运行的进程。chrt -p 468 chrt -p 476
# chrt -p 468 pid 468's current scheduling policy: SCHED_FIFO pid 468's current scheduling priority: 85 # chrt -p 476 pid 476's current scheduling policy: SCHED_OTHER pid 476's current scheduling priority: 0
Copy to Clipboard Copied!
36.3. 使用 sched_getscheduler ()显示进程优先级
实时进程使用一组函数来控制策略和优先级。您可以使用 sched_getscheduler ()
函数来显示指定进程的调度程序策略。
流程
创建
get_sched.c
源文件,并在文本编辑器中打开该文件。{EDITOR} get_sched.c
$ {EDITOR} get_sched.c
Copy to Clipboard Copied! 将以下几行添加到 文件中。
#include <sched.h> #include <unistd.h> #include <stdio.h> int main() { int policy; pid_t pid = getpid(); policy = sched_getscheduler(pid); printf("Policy for pid %ld is %i.\n", (long) pid, policy); return 0; }
#include <sched.h> #include <unistd.h> #include <stdio.h> int main() { int policy; pid_t pid = getpid(); policy = sched_getscheduler(pid); printf("Policy for pid %ld is %i.\n", (long) pid, policy); return 0; }
Copy to Clipboard Copied! policy
变量保存指定进程的调度程序策略。编译程序。
gcc get_sched.c -o get_sched
$ gcc get_sched.c -o get_sched
Copy to Clipboard Copied! 使用不同策略运行程序。
chrt -o 0 ./get_sched chrt -r 10 ./get_sched chrt -f 10 ./get_sched
$ chrt -o 0 ./get_sched Policy for pid 27240 is 0. $ chrt -r 10 ./get_sched Policy for pid 27243 is 2. $ chrt -f 10 ./get_sched Policy for pid 27245 is 1.
Copy to Clipboard Copied!
36.4. 显示调度程序策略的有效范围
您可以使用 sched_get_priority_min ()
和 sched_get_priority_max ()
函数来检查给定调度程序策略的有效优先级范围。
流程
创建
sched_get.c
源文件,并在文本编辑器中打开该文件。{EDITOR} sched_get.c
$ {EDITOR} sched_get.c
Copy to Clipboard Copied! 在文件中输入以下内容:
#include <stdio.h> #include <unistd.h> #include <sched.h> int main() { printf("Valid priority range for SCHED_OTHER: %d - %d\n", sched_get_priority_min(SCHED_OTHER), sched_get_priority_max(SCHED_OTHER)); printf("Valid priority range for SCHED_FIFO: %d - %d\n", sched_get_priority_min(SCHED_FIFO), sched_get_priority_max(SCHED_FIFO)); printf("Valid priority range for SCHED_RR: %d - %d\n", sched_get_priority_min(SCHED_RR), sched_get_priority_max(SCHED_RR)); return 0; }
#include <stdio.h> #include <unistd.h> #include <sched.h> int main() { printf("Valid priority range for SCHED_OTHER: %d - %d\n", sched_get_priority_min(SCHED_OTHER), sched_get_priority_max(SCHED_OTHER)); printf("Valid priority range for SCHED_FIFO: %d - %d\n", sched_get_priority_min(SCHED_FIFO), sched_get_priority_max(SCHED_FIFO)); printf("Valid priority range for SCHED_RR: %d - %d\n", sched_get_priority_min(SCHED_RR), sched_get_priority_max(SCHED_RR)); return 0; }
Copy to Clipboard Copied! 注意如果系统不知道指定的调度程序策略,则函数 return
-1
和errno
被设置为EINVAL
。注意SCHED_FIFO
和SCHED_RR
可以是1
到99
范围内的任意数字。但是,POSIX 无法保证遵守此范围,而可移植的程序应使用这些功能。- 保存文件并退出编辑器。
编译程序。
gcc sched_get.c -o msched_get
$ gcc sched_get.c -o msched_get
Copy to Clipboard Copied!
sched_get
程序现已就绪,可以从保存它的目录中运行。
36.5. 显示进程的时间片
SCHED_RR
(round-robin)策略与 SCHED_FIFO
(first-in、first-out)策略稍有不同。SCHED_RR
在循环轮转中分配具有相同优先级的并发进程。这样,每个进程都会被分配一个时间片。sched_rr_get_interval ()
函数报告分配给每个进程的时间片。
虽然 POSIX 要求这个功能只能用于配置为使用 SCHED_RR
调度程序策略运行的进程,但 sched_rr_get_interval ()
函数可以检索 Linux 上任何进程的时间片长度。
timeslice 信息作为 timespec
返回。这是自 00:00:00 GMT 基础时间 1 月 1 日以来的秒数和纳秒:
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
}
流程
创建
sched_timeslice.c
源文件,并在文本编辑器中打开该文件。{EDITOR} sched_timeslice.c
$ {EDITOR} sched_timeslice.c
Copy to Clipboard Copied! 将以下行添加到
sched_timeslice.c
文件中。#include <stdio.h> #include <sched.h> int main() { struct timespec ts; int ret; /* real apps must check return values */ ret = sched_rr_get_interval(0, &ts); printf("Timeslice: %lu.%lu\n", ts.tv_sec, ts.tv_nsec); return 0; }
#include <stdio.h> #include <sched.h> int main() { struct timespec ts; int ret; /* real apps must check return values */ ret = sched_rr_get_interval(0, &ts); printf("Timeslice: %lu.%lu\n", ts.tv_sec, ts.tv_nsec); return 0; }
Copy to Clipboard Copied! - 保存文件并退出编辑器。
编译程序。
gcc sched_timeslice.c -o sched_timeslice
$ gcc sched_timeslice.c -o sched_timeslice
Copy to Clipboard Copied! 使用不同策略和优先级运行程序。
chrt -o 0 ./sched_timeslice chrt -r 10 ./sched_timeslice chrt -f 10 ./sched_timeslice
$ chrt -o 0 ./sched_timeslice Timeslice: 0.38994072 $ chrt -r 10 ./sched_timeslice Timeslice: 0.99984800 $ chrt -f 10 ./sched_timeslice Timeslice: 0.0
Copy to Clipboard Copied!
36.6. 显示进程的调度策略和相关属性
sched_getattr ()
函数查询当前应用到指定进程的调度策略,由 PID 标识。如果 PID 等于零,则检索调用进程的策略。
size
参数应反映 sched_attr
结构的大小,如用户空间所知的。内核将 sched_attr::size
填充到其 sched_attr
结构的大小。
如果输入结构较小,内核会返回提供空间之外的值。因此,系统调用会失败,并显示 E2BIG
错误。其他 sched_attr
字段已填写,如 sched_attr 结构 中所述。
流程
创建
sched_timeslice.c
源文件,并在文本编辑器中打开该文件。{EDITOR} sched_timeslice.c
$ {EDITOR} sched_timeslice.c
Copy to Clipboard Copied! 将以下行添加到
sched_timeslice.c
文件中。#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <linux/unistd.h> #include <linux/kernel.h> #include <linux/types.h> #include <sys/syscall.h> #include <pthread.h> #define gettid() syscall(__NR_gettid) #define SCHED_DEADLINE 6 /* XXX use the proper syscall numbers */ #ifdef __x86_64__ #define __NR_sched_setattr 314 #define __NR_sched_getattr 315 #endif struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE (nsec) */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; }; int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) { return syscall(__NR_sched_getattr, pid, attr, size, flags); } int main (int argc, char **argv) { struct sched_attr attr; unsigned int flags = 0; int ret; ret = sched_getattr(0, &attr, sizeof(attr), flags); if (ret < 0) { perror("sched_getattr"); exit(-1); } printf("main thread pid=%ld\n", gettid()); printf("main thread policy=%ld\n", attr.sched_policy); printf("main thread nice=%ld\n", attr.sched_nice); printf("main thread priority=%ld\n", attr.sched_priority); printf("main thread runtime=%ld\n", attr.sched_runtime); printf("main thread deadline=%ld\n", attr.sched_deadline); printf("main thread period=%ld\n", attr.sched_period); return 0; }
#define _GNU_SOURCE #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <linux/unistd.h> #include <linux/kernel.h> #include <linux/types.h> #include <sys/syscall.h> #include <pthread.h> #define gettid() syscall(__NR_gettid) #define SCHED_DEADLINE 6 /* XXX use the proper syscall numbers */ #ifdef __x86_64__ #define __NR_sched_setattr 314 #define __NR_sched_getattr 315 #endif struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; /* SCHED_NORMAL, SCHED_BATCH */ __s32 sched_nice; /* SCHED_FIFO, SCHED_RR */ __u32 sched_priority; /* SCHED_DEADLINE (nsec) */ __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; }; int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) { return syscall(__NR_sched_getattr, pid, attr, size, flags); } int main (int argc, char **argv) { struct sched_attr attr; unsigned int flags = 0; int ret; ret = sched_getattr(0, &attr, sizeof(attr), flags); if (ret < 0) { perror("sched_getattr"); exit(-1); } printf("main thread pid=%ld\n", gettid()); printf("main thread policy=%ld\n", attr.sched_policy); printf("main thread nice=%ld\n", attr.sched_nice); printf("main thread priority=%ld\n", attr.sched_priority); printf("main thread runtime=%ld\n", attr.sched_runtime); printf("main thread deadline=%ld\n", attr.sched_deadline); printf("main thread period=%ld\n", attr.sched_period); return 0; }
Copy to Clipboard Copied! 编译
sched_timeslice.c
文件。gcc sched_timeslice.c -o sched_timeslice
$ gcc sched_timeslice.c -o sched_timeslice
Copy to Clipboard Copied! 检查
sched_timeslice
程序的输出。./sched_timeslice
$ ./sched_timeslice main thread pid=321716 main thread policy=6 main thread nice=0 main thread priority=0 main thread runtime=1000000 main thread deadline=9000000 main thread period=10000000
Copy to Clipboard Copied!
36.7. sched_attr 结构
sched_attr
结构包含或定义指定线程的调度策略及其关联的属性。sched_attr
结构的格式如下:
struct sched_attr { u32 size; u32 sched_policy; u64 sched_flags; s32 sched_nice; u32 sched_priority; /* SCHED_DEADLINE fields */ u64 sched_runtime; u64 sched_deadline; u64 sched_period; };
struct sched_attr {
u32 size;
u32 sched_policy;
u64 sched_flags;
s32 sched_nice;
u32 sched_priority;
/* SCHED_DEADLINE fields */
u64 sched_runtime;
u64 sched_deadline;
u64 sched_period;
};
sched_attr 数据结构
- size
线程大小(以字节为单位)。如果结构的大小小于内核结构,则假定其他字段为 0。
如果大小大于内核结构,内核会将所有其他字段验证为
0。
注意当
sched_attr
结构大于内核结构大小时,sched_setattr ()
函数会失败,并带有E2BIG
错误。- sched_policy
- 调度策略
- sched_flags
当进程使用 fork () 函数时,帮助控制调度行为。
调用过程称为父进程,新进程称为子进程。有效值:
-
0
:子进程从父进程继承调度策略。 -
SCHED_FLAG_RESET_ON_FORK: fork ()
:子进程不会从父进程继承调度策略。相反,它被设置为默认的调度策略(struct sched_attr){ .sched_policy = SCHED_OTHER, }
。
-
- sched_nice
-
指定在使用
SCHED_OTHER
或SCHED_BATCH
调度策略时设置的nice
值。nice
值是范围为-20
(高优先级)到+19
(低优先级)的数字。 - sched_priority
-
指定在调度
SCHED_FIFO
或SCHED_RR
时要设置的静态优先级。对于其他策略,将 priority 指定为0。
只能为截止时间调度指定 SCHED_DEADLINE
字段:
-
sched_runtime :指定截止时间调度的
runtime
参数。该值以纳秒表示。 -
SCHED_ DEADLINE : 指定
截止时间
调度的截止时间参数。该值以纳秒表示。 -
sched_period :指定截止时间调度的
period
参数。该值以纳秒表示。
第 37 章 查看抢占状态
使用 CPU 的进程可以自愿或非自愿地放弃它们使用的 CPU。
37.1. 抢占
进程可以自愿地生成 CPU,因为它已经完成,或者由于它正在等待事件,如磁盘中的数据、键按下或网络数据包。
进程也可以不自愿地获得 CPU。这称为抢占,并在优先级较高的进程希望使用 CPU 时发生。
抢占可能会对系统性能造成负面影响,持续抢占可能会导致状态称为 thrashing。当进程被持续抢占且没有运行任何进程完成时,会出现这个问题。
更改任务的优先级有助于减少非自愿抢占。
37.2. 检查进程的抢占状态
您可以检查指定进程的 voluntary 和 involuntary 抢占状态。状态存储在 /proc/PID/status
中。
先决条件
- 有管理员特权。
流程
显示
/proc/PID/status
的内容,其中PID
是进程的 ID。以下显示了 PID 为 1000 的进程的抢占状态。grep voluntary /proc/1000/status
# grep voluntary /proc/1000/status voluntary_ctxt_switches: 194529 nonvoluntary_ctxt_switches: 195338
Copy to Clipboard Copied!
第 38 章 使用 chrt 工具为进程设置优先级
您可以使用 chrt
工具为进程设置优先级。
先决条件
- 有管理员特权。
38.1. 使用 chrt 工具设置进程优先级
chrt
工具检查并调整调度程序策略和优先级。它可以启动具有所需属性的新进程,或更改正在运行的进程的属性。
流程
要设置进程的调度策略,请使用适当的命令选项和参数运行
chrt
命令。在以下示例中,受命令影响的进程 ID 为1000
,优先级(-p
)为50
。chrt -f -p 50 1000
# chrt -f -p 50 1000
Copy to Clipboard Copied! 要使用指定的调度策略和优先级启动应用程序,请根据需要添加应用程序名称和路径(如果需要)。
chrt -r -p 50 /bin/my-app
# chrt -r -p 50 /bin/my-app
Copy to Clipboard Copied! 有关
chrt
工具选项的更多信息,请参阅 chrt 工具选项。
38.2. chrt 工具选项
chrt
实用程序选项包括命令选项和参数,指定命令的进程和优先级。
策略选项
- -f
-
将调度程序策略设置为
SCHED_FIFO
。 - -o
-
将调度程序策略设置为
SCHED_OTHER
。 - -r
-
将调度程序策略设置为
SCHED_RR
(循环)。 - -d
-
将调度程序策略设置为
SCHED_DEADLINE
。 - -p n
将进程的优先级设置为 n。
当将进程设置为 SCHED_DEADLINE 时,您必须指定
运行时
、截止时间
和周期
参数。例如:
chrt -d --sched-runtime 5000000 --sched-deadline 10000000 --sched-period 16666666 0 video_processing_tool
# chrt -d --sched-runtime 5000000 --sched-deadline 10000000 --sched-period 16666666 0 video_processing_tool
Copy to Clipboard Copied! 其中
-
--sched-runtime 5000000
是以纳秒为单位的运行时间。 -
--sched-deadline 10000000
是以纳秒为单位的相对截止时间。 -
--sched-period 16666666
是以纳秒为单位的时间段。 -
0
是chrt
命令所需的未使用优先级的占位符。
-
第 39 章 为带有库调用的进程设置优先级
您可以使用 chrt
工具为进程设置优先级。
先决条件
- 有管理员特权。
39.1. 用于设置优先级的库调用
实时进程使用一组不同的库调用来控制策略和优先级。以下库调用用于设置非实时进程的优先级。
-
nice
-
setpriority
这些功能调整非实时进程的 nice 值。nice
值充当调度程序的建议,建议将 ready-to-run、非实时进程列表的顺序在处理器上运行。列表头处的进程在进一步关闭列表之前运行。
函数需要包含 sched.h
头文件。确保始终从功能检查返回代码。
39.2. 使用库调用设置进程优先级
可以使用 sched_setscheduler ()
函数设置调度程序策略和其他参数。目前,实时策略有一个参数 sched_priority
。这个参数用于调整进程的优先级。
sched_setscheduler ()
函数需要三个参数,格式为: sched_setscheduler (pid_t pid, int policy, const struct sched_param *sp);
。
sched_setscheduler (2)
手册页列出了 sched_setscheduler ()
的所有可能返回值,包括错误代码。
如果进程 ID 为零,sched_setscheduler ()
函数对调用进程执行操作。
以下代码摘录将当前进程的调度程序策略设置为 SCHED_FIFO
调度程序策略,并将优先级设置为 50
:
struct sched_param sp = { .sched_priority = 50 }; int ret; ret = sched_setscheduler(0, SCHED_FIFO, &sp); if (ret == -1) { perror("sched_setscheduler"); return 1; }
struct sched_param sp = { .sched_priority = 50 };
int ret;
ret = sched_setscheduler(0, SCHED_FIFO, &sp);
if (ret == -1) {
perror("sched_setscheduler");
return 1;
}
39.3. 使用库调用设置进程优先级参数
sched_setparam ()
函数用于设置特定进程的调度参数。然后,可以使用 sched_getparam ()
函数来验证这一点。
与仅返回调度策略的 sched_getscheduler ()
函数不同,sched_getparam ()
函数返回给定进程的所有调度参数。
流程
使用以下代码摘录来读取给定实时进程的优先级并递增它:
struct sched_param sp; int ret; ret = sched_getparam(0, &sp); sp.sched_priority += 2; ret = sched_setparam(0, &sp);
struct sched_param sp;
int ret;
ret = sched_getparam(0, &sp);
sp.sched_priority += 2;
ret = sched_setparam(0, &sp);
如果在真实应用程序中使用此代码,则需要从函数检查返回值并适当地处理任何错误。
在递增优先级时请小心。如本例中,持续将两个添加到调度程序优先级时,可能会导致无效的优先级。
39.4. 为进程设置调度策略和相关属性
sched_setattr ()
函数为 PID 中指定的实例 ID 设置调度策略及其关联的属性。当 pid=0 时,sched_setattr ()
会对调用线程的进程和属性执行操作。
流程
-
调用
sched_setattr ()
指定调用操作的进程 ID,以及以下实时调度策略之一:
实时调度策略
SCHED_FIFO
- 调度第一内和第一出策略。
SCHED_RR
- 调度循环策略。
SCHED_DEADLINE
- 调度截止时间调度策略。
Linux 还支持以下非实时调度策略:
非实时调度策略
SCHED_OTHER
- 调度标准循环时间共享策略。
SCHED_BATCH
- 调度进程的"批量"风格执行。
SCHED_IDLE
调度非常低的优先级的后台作业。
SCHED_IDLE
只能以静态优先级0
使用,而 nice 值对此策略没有影响。此策略旨在以非常低的优先级运行作业(低于使用
SCHED_OTHER
或SCHED_BATCH
策略的 +19 nice 值)。
第 40 章 在实时内核和解决方案中调度问题
实时内核中的调度有时可能会导致。通过使用提供的信息,您可以了解调度策略、调度程序节流和线程不足状态的问题,以及潜在的解决方案。
40.1. 实时内核的调度策略
实时调度策略共享一个主要特征:它们运行直到高优先级线程通过休眠或执行 I/O 中断线程或线程等待。
对于 SCHED_RR
,操作系统会中断一个正在运行的线程,以便相同 SCHED_RR
优先级的另一个线程可以运行。在这两种情况下,POSIX
规格不会进行置备,该规范定义了允许较低优先级线程获得任何 CPU 时间的策略。实时线程的这种特性意味着,可轻松编写应用程序,这会对给定 CPU 的 100% 进行单调。但是,这会导致操作系统出现问题。例如,操作系统负责管理系统范围和每个 CPU 资源,必须定期检查描述这些资源的数据结构,并使用它们执行内务操作。但是,如果内核被 SCHED_FIFO
线程 monopolized,则无法执行其内务任务。最终,整个系统变得不稳定,并可能导致崩溃。
在 RHEL for Real Time 内核中,中断处理程序作为具有 SCHED_FIFO
优先级的线程运行。默认优先级为 50。高于中断处理器线程的 SCHED_FIFO
或 SCHED_RR
策略的 cpu-hog 线程可能会阻止中断处理程序运行。这会导致程序等待这些中断信号的数据,并失败。
40.2. 实时内核中的调度程序节流
实时内核包含一个保护机制,用于启用分配给实时任务使用的带宽。保护机制称为实时调度程序节流。
实时节流机制的默认值定义了实时任务可以使用 95% 的 CPU 时间。剩余的 5% 将专用于非实时任务,例如在 SCHED_OTHER
和类似调度策略下运行的任务。务必要注意,如果单个实时任务占用 95% 的 CPU 时间插槽,则该 CPU 上的剩余实时任务将不会运行。只有非实时任务使用剩余的 CPU 时间。默认值可以有以下性能影响:
- 实时任务最多有 95% 的 CPU 时间,这可能会影响其性能。
- 实时任务不允许运行非实时任务来锁定系统。
实时调度程序节流由 /proc
文件中的以下参数控制:
/proc/sys/kernel/sched_rt_period_us
参数-
定义在 CPU
带宽的
100% 中的周期(微秒)。默认值为 1,000,000 HEKETIs,即 1 秒。必须仔细考虑对 period 值的更改,因为一个期限值非常高或低可能会导致问题。 /proc/sys/kernel/sched_rt_runtime_us
参数-
定义可用于所有实时任务的总带宽。默认值为 950,000 HEKETIs (0.95 s),这是 CPU 带宽的 95%。将值设为
-1
将配置实时任务,使其最多使用 100% 的 CPU 时间。只有当实时任务经过精心设计且没有明显的注意事项(如未绑定的轮询循环)时,这才有效。
40.3. 实时内核中的线程不足
当线程位于 CPU 运行队列中超过星级阈值且没有进行进度时,线程不足发生。线程不足的常见原因是运行固定优先级轮询应用程序,如 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,stopd 会监控运行主工作负载的 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
-