目次
【Ubuntu/Linux】CPUコアのシールド分離
リアルタイム性が要求されるネットワーク通信を含むプログラムなど、さまざまなイベントに応答する際にレイテンシーを最小限に抑える必要がある。 これを行う為に、割り込み (IRQ) とさまざまな CPU 上のユーザープロセスを相互に分離する手法について備忘録を残す。
尚、シールドモデルの考え方など下記の資料が参考になる。
プロセスの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 <progfile>
実行コアの確認
$ 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 バランシングから除外する。
- /etc/default/irqbalance
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スクリプトを実行して自動設定する。
- irq_shield.pl
#!/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 ( <IN> ) { 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スクリプト。
- cpu_pslist.pl
#!/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; } } }