【Ubuntu/Linux】CPUコアのシールド分離
==============================
リアルタイム性が要求されるネットワーク通信を含むプログラムなど、さまざまなイベントに応答する際にレイテンシーを最小限に抑える必要がある。
これを行う為に、割り込み (IRQ) とさまざまな CPU 上のユーザープロセスを相互に分離する手法について備忘録を残す。
尚、シールドモデルの考え方など下記の資料が参考になる。
{{https://www.concurrent-rt.co.jp/external/TechSup/PDF/RedHawkLinux_UsersGuide8.2_Jpn.pdf}}
プロセスのCPUコア分離
-----------------
リアルタイム性が必要なユーザプログラム(プロセス)を実行するCPUコアを分離する。
### コア除外の設定
`/etc/default/grub`ファイルに以下設定をしコア`#2,#3`をスケジュールから除外。
`GRUB_CMDLINE_LINUX_DEFAULT="isolcpus=2,3"` (連番のCPU番号指定は`2-3`と書いてもよい)
$ sudo update-grub
$ sudo reboot
上記設定は、カーネルの起動引数に `isolcpus=2,3` を渡すもの。尚、カーネル起動コマンドに設定されているかの確認は下記。
$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-5.15.0-52-generic root=/dev/mapper/ubuntu--vg-ubuntu--lv ro isolcpus=2,3
尚、Ubuntu 22.04 Serverでは、何故か?上記では固定出来なかったので、下記コマンドで行った。
$ sudo ps -e -o pid= | xargs -n 1 taskset -p 0xFFFFFFF3
### コア固定の設定
実行するプログラムを、コア`#2,#3`に固定し実行。
$ sudo taskset -c 2-3
### 実行コアの確認
$ ps
PID TTY TIME CMD
1067 pts/0 00:00:00 bash
2466 pts/0 00:00:00 ps
$ taskset -pc 1067
プロセス ID 1067 の現在の親和性リスト: 0,1
割り込みのCPUコア固定
-----------------
上記でコア分離したプロセスが使用するNICカードの割り込みを、分離したコア側に割り当てる。
### IRQ バランスからの CPU の除外
`/etc/default/irqbalance` ファイルを編集して、コア`#2,#3`のCPUコアをIRQ バランシングから除外する。
IRQBALANCE_BANNED_CPUS=0000000c
`IRQBALANCE_BANNED_CPUS`の指定がなければ、前記の`isolcpus=`の指定を参照する模様。
### NICカードの IRQ への CPU アフィニティーの手動割り当て
`/proc/interrupts` ファイルを参照して、各デバイスが使用している IRQ を確認する。
$ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 17 0 0 0 IR-IO-APIC 2-edge timer
6: 0 0 0 1 IR-IO-APIC 6-edge ttyS2
8: 0 0 1 0 IR-IO-APIC 8-edge rtc0
9: 0 5 0 0 IR-IO-APIC 9-fasteoi acpi
...
該当するIRQ番号専用の`smp_affinity`ファイルに割り当てるCPUコアのbitマスク値を書き込む。このファイルは、`/proc/irq/(IRQ番号)/` ディレクトリにある。
例えば、IRQ 番号 130 の割り込みを CPU `#2,#3` でのみ実行するよう指示するには下記。
$ sudo echo c > /proc/irq/130/smp_affinity
尚、設定されたかどうかを、`smap_affinity_list`ファイルを表示して確認する。
$ cat /proc/irq/130/smp_affinity_list
### スクリプトによる IRQ への CPU アフィニティーの自動割り当て
`smp_affinity`の指定は、再起動した時には元に戻るので、下記のようなPerlスクリプトを実行して自動設定する。
#!/usr/bin/perl
#
# 指定NICインタフェースの割り込みをコア2,3に割り当てる。
#
use strict;
use warnings;
my $infile = "/proc/interrupts";
my $nicname = "enp3s0"; # 検索するNICインタフェース名
my $mode = 0; # 動作モード
if (@ARGV >= 1) {
$nicname = $ARGV[0];
}
if (@ARGV >= 2) {
if ($ARGV[1] eq "set") {
$mode = 1;
}
if ($ARGV[1] eq "debug") {
$mode = 2;
}
}
open(IN, "$infile") || die "can not open $infile\n";
while ( ) {
if ( $_ =~ /\b$nicname/ ) {
my @a = split();
$a[0] =~ s/:$//; # IRQ番号、先頭ワードの末尾':'文字を削除
if ($mode == 0) {
print;
}
else {
if ($mode == 1) { # set ?
system("echo c > /proc/irq/$a[0]/smp_affinity"); # コア2,3
}
print "echo c > /proc/irq/$a[0]/smp_affinity\n";
}
}
}
close(IN);
上記スクリプトを管理者権限で実行する。
$ sudo ./irq_shield.pl enp1s0 set
その他
-----
### CPUに負荷をかける
stressコマンドで、CPUのコア#2に演算負荷をかける。
$ taskset -c 2 stress -c 1
### CPU負荷状況の確認
下記コマンドでCPUの負荷状況を表示。
$ dstat -c -C 0,1,2,3
-----cpu0-usage----------cpu1-usage----------cpu2-usage----------cpu3-usage----
usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl
5 4 91 0 0: 7 2 90 0 0: 17 0 83 0 0: 0 0 100 0 0
11 6 83 0 0: 9 0 91 0 0: 99 1 0 0 0: 1 1 98 0 0
11 4 85 0 0: 8 0 92 0 0: 98 2 0 0 0: 0 1 99 0 0
9 4 86 0 0: 5 0 95 0 0: 99 1 0 0 0: 1 1 98 0 0
9 7 84 0 0: 8 1 91 0 0: 99 1 0 0 0: 0 2 98 0 0
14 5 81 0 0: 7 0 93 0 0:100 0 0 0 0: 0 1 99 0 0
12 5 83 0 0: 8 0 92 0 0: 99 1 0 0 0: 0 1 99 0 0
9 5 86 0 0: 4 1 95 0 0: 99 1 0 0 0: 0 2 98 0 0
14 6 80 0 0: 5 0 95 0 0: 98 2 0 0 0: 2 0 98 0 0
10 5 85 0 0: 6 0 94 0 0:100 0 0 0 0: 0 1 99 0 0
10 7 83 0 0: 4 0 96 0 0:100 0 0 0 0: 0 0 100 0 0
### CPU Affinityの一覧表示
全プロセスのCPU Affinityを一覧表示するPerlスクリプト。
#!/usr/bin/perl
#
# CPU affinityの一覧を表示する。
#
use strict;
use warnings;
my @result;
my $mode = 0;
if (@ARGV >= 1) {
if ($ARGV[0] eq "tree") {
$mode = 1;
}
}
if ($mode == 0) {
@result = `ps aux`;
}
else {
@result = `ps auxf`;
}
foreach my $line(@result)
{
my @a = split(/\s+/, $line); # 複数の空白や改行でで分割
if ($a[1] eq "PID") {
print "AFFIN " . $line;
}
else {
my $affi = `taskset -pc $a[1] 2>&1`; # エラー出力は標準へ
if ($? == 0) { # コマンド実行のリターンコード
my @b = split(/\s+/, $affi); # 複数の空白や改行で分割
my $str = sprintf("%-6s", $b[$#b]); # CPU affinity
print $str . $line;
}
}
}
参考
----
1. [[https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux_for_real_time/8/html/optimizing_rhel_8_for_real_time_for_low_latency_operation/assembly_binding-interrupts-and-processes_optimizing-rhel8-for-real-time-for-low-latency-operation#doc-wrapper|第12章 割り込みとユーザープロセスを分離してシステムレイテンシーを最小限に抑える]]
2. [[https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux_for_real_time/7/html/tuning_guide/interrupt_and_process_binding|2.4. 割り込みおよびプロセスバインディング]]
3. [[https://docs.oracle.com/cd/E26853_01/coh.371/b71688/tune_perftune.htm|6 パフォーマンス・チューニング]]
4. [[https://www.slideshare.net/syuu1228/interrupt-affinity|Interrupt Affinityについて]]
5. [[https://web.mit.edu/rhel-doc/4/RH-DOCS/rhel-rg-ja-4/s1-proc-directories.html|5.3. /proc/配下のディレクトリ]]
6. [[https://qiita.com/knakahara/items/825d2f052b07c0a2599e|irqbalance を NetBSD に移植してみる]]
7. [[https://rheb.hatenablog.com/entry/nfv_tuning|レッドハットが考えるNFV環境向けのチューニング]]
8. [[https://www.web-dev-qa-db-ja.com/ja/linux/isolcpusがアクティブ化されているかどうかを検出する方法/962607069/|isolcpusがアクティブ化されているかどうかを検出する方法]]
9. [[https://qiita.com/nakat-t/items/4542e84c4b72b78740e8|プロセス・スレッドを特定のCPUコアでのみ動作させる方法]]
10. [[https://www.rcannings.com/systemd-core-isolation/|SYSTEMD CORE ISOLATION]]
11. [[https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/6/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-tool_reference-irqbalance|3.4. irqbalance]]
12. [[https://qiita.com/gyaneman/items/f77c2633a5ac92f05302|プロセス・スレッドにCPUアフィニティを設定する方法]]
13. [[https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/assembly_configuring-cpu-affinity-and-numa-policies-using-systemd_managing-monitoring-and-updating-the-kernel|第28章 systemd を使用した CPU のアフィニティーおよび NUMA ポリシーの設定]]
14. [[https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-tuning_scheduling_policy-isolating_cpus|6.3.6.2. CPU の分離]]
15. [[https://blog.amedama.jp/entry/stress-command|stress コマンドを使ってマシンに負荷をかける]]
16. {{https://www.concurrent-rt.co.jp/external/TechSup/PDF/RedHawkLinux_UsersGuide8.2_Jpn.pdf}}