CRIUを開発する際、プロセス情報を取得するための現在のインターフェイスは理想的ではないことに気付きました。 さらに、ソケットについても同様の問題が解決されました。 これらの開発をプロセスに移そうとしましたが、非常に良い結果が得られました。これについては、この記事を最後まで読むことで学習できます。
現在のインターフェースの欠点
見出しを読んだ後、疑問が生じます:「そして、古いインターフェースは何を喜ばせなかったのですか?」 プロセス情報が
procfsファイルシステムを介して収集されるようになったことを多くの人が知って
います。 ここで、各プロセスは、数十個のファイルを含むディレクトリに対応しています。
$ ls /proc/self/ attr cwd loginuid numa_maps schedstat task autogroup environ map_files oom_adj sessionid timers auxv exe maps oom_score setgroups uid_map cgroup fd mem oom_score_adj smaps wchan clear_refs fdinfo mountinfo pagemap stack cmdline gid_map mounts personality stat comm io mountstats projid_map statm coredump_filter latency net root status cpuset limits ns sched syscall
各ファイルには多くの属性が含まれています。 最初の問題は、プロセスごとに少なくとも1つのファイルを読み取る必要があることです。 3つのシステムコールを行います。 数十万のプロセスでデータを収集する必要がある場合、強力なマシンであっても長時間かかる可能性があります。 忙しいマシンではpsまたはtopユーティリティの実行が遅いことを覚えている人もいるかもしれません。
2番目の問題は、プロセスのプロパティがファイルに分割される方法に関連しています。 現在のスプリットがあまり良くないことを示す良い例があります。 CRIUには、プロセスメモリのすべての領域のデータを取得するタスクがあります。 / proc / PID / mapsファイルを見ると、メモリ領域の復元に必要なフラグが含まれていないことがわかります。 幸いなことに、別のファイル-/ proc / PID / smapsがあります。このファイルには、必要な情報と、使用済みの物理メモリに関する統計(必要ありません)が含まれています。 簡単な実験では、最初のファイルの作成にかかる時間が1桁少ないことが示されています。
$ time cat /procsmaps > /dev/null real 0m0.253s user 0m0.004s sys 0m0.247s
おそらく、メモリ消費量の統計がすべての原因であると推測しているでしょう。それを収集するにはほとんどの時間がかかります。
3番目の問題は、ファイル形式で確認できます。 まず、単一の形式はありません。 第二に、いくつかのファイルの形式は原則的に拡張できません(このため、/ proc / PID / mapsにフラグフィールドを追加できません)。 第三に、人間が読みやすいテキスト形式の多くのファイル。 これは、1つのプロセスを見たいときに便利です。 ただし、タスクが数千のプロセスを分析することである場合、それらを目で見ずに、いくつかのコードを記述します。 異なる形式のファイルを並べ替えることは楽しい娯楽ではありません。 通常、バイナリ形式はプログラムコードでの処理に便利であり、その生成に必要なリソースは少なくなります。
ソケット診断ソケットインターフェイス
CRIUを始めたとき、ソケット情報の取得に問題がありました。 ほとんどのタイプのソケットでは、通常どおり、/ proc内のファイル(/ proc / net / unix、/ proc / net / netlinkなど)が使用され、かなり限られたパラメーターセットが含まれていました。 INETソケット用のネットリンクインターフェイスがあり、これは情報をバイナリ形式で簡単に拡張可能な形式で表していました。 このインターフェイスは、すべてのタイプのソケットに一般化できました。
次のように機能します。 最初に、パラメーターグループのセットとそれらが必要なソケットのセットを定義する要求が生成されます。 出力では、メッセージに分割された必要なデータを取得します。 1つのメッセージは1つのソケットを説明します。 すべてのパラメーターはグループに分割されますが、それらはメッセージのサイズに対してのみオーバーヘッドが発生するため、非常に小さくすることができます。 各グループは、タイプとサイズで説明されます。 同時に、既存のグループを拡張したり、新しいグループを追加したりできます。
新しいタスク診断プロセス属性取得インターフェイス
プロセスに関するデータを取得する際に問題が発生すると、すぐにソケットとの類推が始まり、プロセスに同じインターフェイスを使用するというアイデアが生まれました。
すべての属性はグループに分割する必要があります。 重要なルールが1つあります。グループ内のすべての属性を生成するために必要な時間に顕著な影響を与える属性はありません。 / proc / PID / smapsについて話したことを覚えていますか? 新しいインターフェースでは、これらの統計を別のグループに移動しました。
最初の段階では、すべての属性をカバーするようにタスクを設定しませんでした。 新しいインターフェイスがどれほど便利かを理解したかったのです。 したがって、CRIUのニーズに十分なインターフェイスを作成することにしました。 結果は、次の属性グループのセットです。
TASK_DIAG_BASE TASK_DIAG_CRED, TASK_DIAG_STAT, TASK_DIAG_VMA, TASK_DIAG_VMA_STAT, TASK_DIAG_PID = 64, TASK_DIAG_TGID,
実際、グループ化の現在のバージョンはここに表示されます。 特に、ここではTASK_DIAG_STATを参照します。これは、netlinkソケットに基づいて構築された、既存のtaskstatsとのインターフェースの統合の一部として2番目のバージョンに登場しました。 後者はnetlinkプロトコルを使用しており、この記事で扱う多くの既知の問題があります。
そして、情報が必要なプロセスのグループを設定する方法についてのいくつかの言葉。
#define TASK_DIAG_DUMP_ALL 0 #define TASK_DIAG_DUMP_ALL_THREAD 1 #define TASK_DIAG_DUMP_CHILDREN 2 #define TASK_DIAG_DUMP_THREAD 3 #define TASK_DIAG_DUMP_ONE 4
実装プロセスでは、いくつかの疑問が生じました。 インターフェイスは、一般ユーザー、つまり どこかにアクセス権を保存する必要がありました。 2番目の質問は、プロセス名前空間(pidns)へのリンクをどこから取得するかです。
2番目のものから始めましょう。 ソケットに基づいており、主にネットワークサブシステムで使用されるnetlinkインターフェイスを使用します。 ネットワーク名前空間へのリンクは、ソケットから取得されます。 この場合、リンクはプロセス名前空間に移動する必要があります。 少しのカーネルコードを読んだ後、各メッセージに送信者に関する情報(SCM_CREDENTIALS)が含まれていることがわかりました。 プロセス識別子が含まれているため、送信者からプロセス名前空間へのリンクを取得できます。 これは、次のようにネットワーク名前空間に反します。 ソケットは、作成された名前空間にバインドします。 情報を要求したプロセスからpidnsへのリンクを取ることはおそらく受け入れられます。さらに、必要な名前空間を設定する機会を得ます。 送信者に関する情報は、送信の段階で設定できます。
最初の問題ははるかに興味深いことが判明しましたが、長い間その詳細を理解できませんでした。 Linuxファイル記述子には1つの機能があります。 ファイル記述子を完全に機能させたまま、ファイルを開いて特権を下げることができます。 これはnetlinkソケットについては多少当てはまりますが、Andy Lutomirskiが私に指摘した問題があります。 それは、この特定のソケットが使用される理由を正確に指定することができないという事実にあります。 つまり、netlinkソケットを作成してからその特権を下げるアプリケーションがある場合、このアプリケーションはnetlinkソケットで利用可能な機能にソケットを使用できます。 つまり、特権の削減はnetlinkソケットに影響しません。 netlinkソケットに新しい機能を追加すると、それらを使用するアプリケーションの新しい可能性が開かれます。これは深刻なセキュリティ問題です。
インターフェースに関する他の提案がありました。 特に、新しいシステムコールを追加するというアイデアがありました。 しかし、私は彼女が本当に好きではありませんでした、なぜなら 1つのバッファにすべてを書き込むにはデータが多すぎる可能性があります。 ファイル記述子には、データをバッチで読み取ることが含まれます。
procfsファイルシステムにトランザクションファイルを作成する提案もありました。 この考え方は、netlinkソケットに対して行ったことと似ています。 ファイルを開き、リクエストを書き、答えを読んでください。 次のバージョンの作業バージョンのように、私たちがやめたのはこの考えでした。
パフォーマンスについて一言
最初のバージョンではあまり議論はされませんでしたが、プロセスプロパティを取得するための新しい高速なインターフェイスに興味を持つ別のグループを見つけるのに役立ちました。 ある晩、私は自分の仕事をPavel Odintsov(@pavelodintsov)と共有しました。彼は最近、perfに問題があり、プロセス属性の収集速度にも関係していると言いました。 これが、彼がインターフェースの開発に多大な貢献をしてくれたDavid Ahernと私たちを結びつけた方法です。 彼はまた、この仕事が私たちだけのものではないことを別の例で証明しました。
簡単な例でパフォーマンスの比較を開始できます。 すべてのプロセスについて、セッション番号、グループ、およびその他のパラメーターを/ proc / pid / statファイルから取得する必要があるとします。
正直な比較のために、各プロセスの/ proc / PID /ステータスを読み取る小さなプログラムを作成します。 以下では、psユーティリティよりも高速に動作することがわかります。
while ((de = readdir(d))) { if (de->d_name[0] < '0' || de->d_name[0] > '9') continue; snprintf(buf, sizeof(buf), "/proc/%s/stat", de->d_name); fd = open(buf, O_RDONLY); read(fd, buf, sizeof(buf)); close(fd); tasks++; }
task-diagのプログラムはより大容量です。 私のリポジトリのtools / testing / selftests / task_diag /ディレクトリにあります。
$ ps a -o pid,ppid,pgid,sid,comm | wc -l 50006 $ time ps a -o pid,ppid,pgid,sid,comm > /dev/null real 0m1.256s user 0m0.367s sys 0m0.871s $ time ./task_proc_all a tasks: 50085 real 0m0.279s user 0m0.013s sys 0m0.255s $ time ./task_diag_all a real 0m0.051s user 0m0.001s sys 0m0.049s
このような単純な例でも、task_diagが数倍高速であることがわかります。 psユーティリティは、プロセスごとにより多くのファイルを読み込むため、低速です。
両方のオプションでpref trace --summaryが表示するものを見てみましょう。
$ perf trace --summary ./task_proc_all a tasks: 50086 Summary of events: task_proc_all (72414), 300753 events, 100.0%, 0.000 msec syscall calls min avg max stddev (msec) (msec) (msec) (%) --------------- -------- --------- --------- --------- ------ read 50091 0.003 0.005 0.925 0.40% write 1 0.011 0.011 0.011 0.00% open 50092 0.003 0.004 0.992 0.49% close 50093 0.002 0.002 0.061 0.15% fstat 7 0.002 0.003 0.008 25.95% mmap 18 0.002 0.006 0.026 19.70% mprotect 10 0.006 0.010 0.020 13.28% munmap 2 0.012 0.020 0.028 40.18% brk 3 0.003 0.007 0.010 30.28% rt_sigaction 2 0.003 0.003 0.004 18.81% rt_sigprocmask 1 0.003 0.003 0.003 0.00% access 1 0.005 0.005 0.005 0.00% getdents 50 0.003 0.940 2.023 4.51% getrlimit 1 0.003 0.003 0.003 0.00% arch_prctl 1 0.002 0.002 0.002 0.00% set_tid_address 1 0.003 0.003 0.003 0.00% openat 1 0.022 0.022 0.022 0.00% set_robust_list 1 0.003 0.003 0.003 0.00%
$ perf trace --summary ./task_diag_all a Summary of events: task_diag_all (72481), 183 events, 94.8%, 0.000 msec syscall calls min avg max stddev (msec) (msec) (msec) (%) --------------- -------- --------- --------- --------- ------ read 31 0.003 1.471 6.364 14.43% write 1 0.003 0.003 0.003 0.00% open 7 0.005 0.008 0.020 26.21% close 6 0.002 0.002 0.003 3.96% fstat 6 0.002 0.002 0.003 4.67% mmap 17 0.002 0.006 0.030 25.38% mprotect 10 0.005 0.007 0.010 6.33% munmap 2 0.006 0.007 0.008 13.84% brk 3 0.003 0.004 0.004 9.08% rt_sigaction 2 0.002 0.002 0.002 9.57% rt_sigprocmask 1 0.002 0.002 0.002 0.00% access 1 0.006 0.006 0.006 0.00% getrlimit 1 0.002 0.002 0.002 0.00% arch_prctl 1 0.002 0.002 0.002 0.00% set_tid_address 1 0.002 0.002 0.002 0.00% set_robust_list 1 0.002 0.002 0.002 0.00%
task_diagの場合のシステムコールの数は大幅に削減されました。
perfユーティリティの結果(David Ahernからの手紙からの引用)。
> Using the fork test command: > 10,000 processes; 10k proc with 5 threads = 50,000 tasks > reading /proc: 11.3 sec > task_diag: 2.2 sec > > @7,440 tasks, reading /proc is at 0.77 sec and task_diag at 0.096 > > 128 instances of sepcjbb, 80,000+ tasks: > reading /proc: 32.1 sec > task_diag: 3.9 sec > > So overall much snappier startup times.
ここでは、生産性が1桁向上しています。
おわりに
このプロジェクトは開発中であり、まだ何度も変更することができます。 しかし、今では2つの実際のプロジェクトがあり、その例で生産性が大幅に向上していることがわかります。 何らかの形で、この作業が遅かれ早かれカーネルのメインブランチに分類されると確信しています。
参照資料
github.com/avagin/linux-task-diaglkml.org/lkml/2015/7/6/142lwn.net/Articles/633622www.slideshare.net/openvz/speeding-up-ps-and-top-57448025