前回 は、4.14カーネルでcgroup v2に導入されたスレッドモードの概要を説明しました。今回は、実際にスレッド化サブツリーを作成して操作を行ってみましょう。
今回の実行例は、デフォルトではcgroupをマウントしないPlamo Linux 7.2環境で試しています。systemdを採用したディストリビューションの場合、カーネルの起動パラメータにcgroup_no_v1=all
と指定して、すべてのコントローラがcgroup v2から使えるようにするとcgroup v2の機能が試しやすいでしょう。
そして、今回の実行例はいずれもroot
ユーザで実行しています。
それでは早速、cgroup v2のツリー中でどのようにスレッド化サブツリーを作り、操作していくのかを見ていきましょう。
スレッド化サブツリーを作るには2つの方法があります。
cgroup.typeファイルへの書き込みによるスレッド化サブツリーの作成
まずは1つめの方法です。
シンプルにcgroup.type
ファイルにthreaded
という文字列を書き込むだけです。これまで説明してきたcgroupの操作方法をご存知であれば想像がつく方法かと思います。
この結果次のようになります。
threaded
という文字列を書き込んだcgroupのタイプは"threaded" になる
threaded
という文字列を書き込んだcgroupの親cgroup は"domain threaded" になる
threaded
という文字列を書き込んだcgroupの親cgroup配下の"threaded"以外のcgroupは"domain invalid" になる
つまり、threaded
と書き込んだcgroupの親cgroup 配下が、前回 説明したスレッド化サブツリーとなるわけです。前回説明したように、スレッド化サブツリー内はすべて"threaded"なcgroupでなければ使えませんので、"domain"だったcgroupは"domain invalid"に変化するという動きになっています。
できあがったスレッド化サブツリー内の"domain invalid"な状態のcgroupすべてにthreaded
という文字列を書き込んでいかないと、スレッド化サブツリーが完成しないのは面倒ですね。これは将来的にスレッドモードの機能を拡張して、動作を変える必要が出てきた場合に備えてこのようになっているようです。
この動きを確認してみましょう。
cgroup v2のroot配下にtd
というディレクトリを、td
配下にth01
、th02
というcgroupを2つ作成します。
# mount -t cgroup2 cgroup2 /sys/fs/cgroup/
# mkdir -p td/th{01,02}
# tree -d /sys/fs/cgroup/
/sys/fs/cgroup/
└── td
├── th01
└── th02
3 directories
作成した3つのcgroup内のプロセスとコントローラファイルの中身を確認します。
# cat td/cgroup.procs
# cat td/cgroup.controllers
# cat td/th0{1,2}/cgroup.procs
# cat td/th0{1,2}/cgroup.controllers
まだ作っただけでプロセスの登録もコントローラの登録も行っていませんので、すべて空の状態です。
それぞれのcgroup.type
ファイルも確認しておきましょう。
# find /sys/fs/cgroup -name "cgroup.type" -exec cat {} \;
domain
domain
domain
いずれのcgroupも初期状態ですのですべて"domain"となっています(図1 ) 。
図1 作成直後ですべてdomainの状態
この状態でth01
を"threaded"としてみましょう。
# echo "threaded" > /sys/fs/cgroup/td/th01/cgroup.type
# cat /sys/fs/cgroup/td/th01/cgroup.type
threaded
threaded
になりましたね。他のcgroupがどうなっているのかも見てみましょう。
# cat /sys/fs/cgroup/td/cgroup.type
domain threaded
# cat /sys/fs/cgroup/td/th02/cgroup.type
domain invalid
"threaded"であるth01
の親cgroupであるtd
はスレッド化サブツリーのrootである"domain threaded"に、兄弟cgroupであるth02
は"domain invalid"となりました。
つまり"threaded cgroup"の親は"domain threaded"に、スレッド化サブツリー内のdomain cgroupは"invalid"になります(図2 ) 。
図2 スレッド化サブツリーができあがった直後
このままではth02
は使えない状態のままです。th02
を機能させるためには"threaded"にする必要があります。
# echo "threaded" > /sys/fs/cgroup/td/th02/cgroup.type
# cat /sys/fs/cgroup/td/th02/cgroup.type
threaded
これでth02
がthreadedになり、使える状態になりました。プロセスやスレッドが追加できます(図3 ) 。
図3 domain invalidのcgroupをthreadedに変更(スレッド化サブツリーの完成)
スレッド化サブツリー内でのタスク操作
2つめの方法を見る前に、スレッド化サブツリー内へのプロセスやスレッドの追加について見ておきましょう。
マルチスレッドプロセスについて
プロセスやスレッドの追加について見る前に、マルチスレッドに関わるIDの用語がややこしいので、説明で使う用語の整理をしておきます。
まずはマルチスレッドプロセスを起動してみます。次のように2つのスレッドを持つプロセスです(pstree
コマンドではスレッドは中かっこ{}
で囲われます) 。
# pstree -p 907
threadtest(907)─┬─{threadtest}(908)
└─{threadtest}(909)
この後は、上の例を使って説明を進めます。
スレッドグループ:マルチスレッドプロセス内のプロセス、スレッドの集合(ここではID 907
、908
、909
のプロセスからなるグループ)
スレッドグループリーダ:スレッドグループ内でスレッドを生成する元となるプロセス(ここではID 907
のプロセス)
TGID:スレッドグループのID。スレッドグループリーダのPIDに等しい(ここでは907
)
PID:各スレッドのID(ここではそれぞれ907
、908
、909
)
プロセスやスレッドはそれぞれ、IDとしてカーネル内ではpid
というデータを持ちます。ここでは各スレッドのIDである907
、908
、909
です。スレッドIDとしてTIDと説明されることもありますが、カーネル内ではpid
という名前の変数ですので、ここでは各スレッドのIDとしてPIDという用語を使います。gettid(2)
というシステムコールを使うとこのpid
が返ります。
そして、907
、908
、909
からなるスレッドグループのID(TGID)が907
です。getpid(2)
というシステムコールでマルチスレッドプロセスのIDを取得すると、このTGIDが返ります。
PID、TID、TGIDとややこしいのですが、ここでは上記のようにカーネル内の名称をベースに説明を進めます。
スレッド化サブツリー内へのタスクの追加
用語の説明が済んだところで、スレッド化サブツリー内へのプロセスやスレッドの追加を見ておきましょう。
スレッド化サブツリーにマルチスレッドプロセスを登録させるには、まずはプロセスをcgroup.procs
に登録する必要があります。"threaded"や"domain threaded"のcgroupであっても、いきなりcgroup.threads
にプロセスやスレッドのIDは登録できません。
このスレッドグループリーダのPID(=TGID)である907
を図3 で作ったcgroup td
に登録してみます。
# echo 907 > /sys/fs/cgroup/td/cgroup.procs
# cat /sys/fs/cgroup/td/cgroup.procs
907
# cat /sys/fs/cgroup/td/cgroup.threads
907
908
909
スレッドグループリーダの907
をcgroup.procs
に登録すると、cgroup.procs
にはそのPIDが登録され、cgroup.threads
にはその子となるスレッドのID(PID)である908
、909
が登録されています。
cgroup.procs
への登録は"domain threaded'であるcgroupへの登録である必要はなく、いきなり"threaded"であるth01
やth02
への登録でも構いません。次の操作は"threaded"であるth01
へマルチスレッドプロセスを登録している例です。
# echo 907 > /sys/fs/cgroup/cgroup.procs
# echo 907 > /sys/fs/cgroup/td/th01/cgroup.procs
# cat /sys/fs/cgroup/td/th01/cgroup.threads
907
908
909
上の例で、cgroupに登録されたプロセスとスレッドのIDを確認するためにcgroup.threads
の内容を見ています。これは"threaded"なcgroupのcgroup.procs
ファイルは読み取りできないからです。cgroup.procs
を読もうとすると次のようにエラーになります。
"domain threaded"であるcgroupのcgroup.procs
は先の例で実行したように読み取れます。
# cat /sys/fs/cgroup/td/th01/cgroup.procs
cat: /sys/fs/cgroup/td/th01/cgroup.procs: Operation not supported
この例ではスレッドグループリーダのPIDを登録しました。代わりにスレッドのIDをcgroup.procs
に登録しても同じ結果となります。上の例のマルチスレッドプロセス内のスレッドのPIDである909
をcgroup.procs
に登録してみましょう。
# echo 909 > /sys/fs/cgroup/td/cgroup.procs
# cat /sys/fs/cgroup/td/cgroup.procs
907
# cat /sys/fs/cgroup/td/cgroup.threads
907
908
909
このように、マルチスレッドプロセス内のスレッドのPIDを登録しても、cgroup.procs
ではTGIDが表示され、cgroup.threads
にはスレッドグループ内のスレッドすべてが表示されていることがわかります。
先に「まずはプロセスをcgroup.procs
に登録する必要があります」と書きました。これを確認しておきましょう。
プロセスをcgroup.procs
ではなく、いきなりcgroup.threads
に登録を試みます。先の例に続けて実行する場合は、先と同様に一度プロセスをrootに戻して試してください。
# echo 907 > /sys/fs/cgroup/cgroup.procs
# echo 907 > /sys/fs/cgroup/td/cgroup.threads
bash: echo: write error: Operation not supported
# echo 907 > /sys/fs/cgroup/td/th01/cgroup.threads
bash: echo: write error: Operation not supported
このようにいきなりcgroup.threads
に登録しようとするとエラーとなります。
一度TGIDをスレッド化サブツリーに登録したあとは、スレッド化サブツリー内で自由に移動できることを確認してみましょう。
# echo 907 > /sys/fs/cgroup/td/cgroup.procs
# cat /sys/fs/cgroup/td/cgroup.procs
907
# cat /sys/fs/cgroup/td/cgroup.threads
907
908
909
# echo 908 > /sys/fs/cgroup/td/th01/cgroup.threads
# cat /sys/fs/cgroup/td/th01/cgroup.threads
908
# echo 909 > /sys/fs/cgroup/td/th02/cgroup.threads
# cat /sys/fs/cgroup/td/th02/cgroup.threads
909
ここでスレッドのPIDをcgroup.threads
でなくcgroup.procs
に登録すると、マルチスレッドプロセス全体が移動してしまうことに注意してください。
# echo 909 > /sys/fs/cgroup/td/th01/cgroup.procs
# cat /sys/fs/cgroup/td/th01/cgroup.threads
908
907
909
色々試して頭が混乱したかもしれませんね。ここまでに試したことをまとめておきましょう。
マルチスレッドプロセスをスレッド化サブツリーに移動する場合は、まずはスレッド化サブツリー内のcgroupのcgroup.procs
に登録する必要がある
登録するIDはスレッドグループ内のスレッドのPIDでも良い
"threaded"なcgroupのcgroup.procs
は読み取れない。登録されているIDを確認するにはcgroup.threads
を読む必要がある
domain threaded
なcgroupのcgroup.procs
は読み取れる
スレッドグループ内のスレッドグループリーダ以外のスレッドのPIDであってもcgroup.procs
への書き込みではマルチスレッドプロセス全体が移動する
プロセスが存在するcgroupへのスレッドコントローラの登録によるスレッド化サブツリーの作成
スレッド化サブツリーを作る方法はもう1つあります。
この方法では次の2つの手順が必要です。いずれも同じcgroupに対して操作を行います。
"domain"タイプであるcgroupでスレッドコントローラ有効にし、その子cgroupでもスレッドコントローラを有効にする(該当cgroupとその親cgroup両方のcgroup.subtree_controll
にスレッドコントローラが登録された状態)
プロセスをcgroupに登録する
この手順の順番はどちらが先でも問題ありません。この結果は次のようになります。
該当のcgroupは"domain threaded"なcgroupとなる
上記の"domain threaded"なcgroup配下の"domain" cgroupは"domain invalid"となる
cgroup v2の当初の仕様では、すでにプロセスが登録されているcgroupのサブツリーでコントローラを有効にできませんでした。しかし、スレッド化サブツリーでは自身と子孫のcgroupでコントローラを有効にでき、スレッド化サブツリーを作成できます。
試してみましょう。
まずは先ほどの例と同様のツリーを作成します。そしてtd
の親cgroup(ここの例ではroot cgroup)内のcgroup.subtree_control
ファイルにcpuコントローラを登録します。cgroupでコントローラを有効化するあたりのお話は連載の第38回 をご覧ください。
# mkdir -p td/th{01,02}
# tree -d .
.
└── td
├── th01
└── th02
3 directories
# echo "+cpu" > cgroup.subtree_control
# cat cgroup.subtree_control
cpu
# cat td/cgroup.controllers
cpu
そしてcgroup td
にプロセスを登録します。
# echo $$ > td/cgroup.procs
# cat td/cgroup.procs
912
938
# cat td/cgroup.type
domain
この時点ではまだcgroup td
のタイプは"domain"です(図4 ) 。
図4 スレッドコントローラを登録して末端ではないcgroupにプロセスを登録
ここでtd
のcgroup.subtree_control
ファイルにCPUコントローラを登録し、td
の子孫cgroupでCPUコントローラが使えるようにしてみます。
# echo "+cpu" > td/cgroup.subtree_control
# cat td/cgroup.subtree_control
cpu
# cat td/th{01,02}/cgroup.controllers
cpu
cpu
図5 cgroup.subtree_controlファイルにスレッドコントローラを登録
td
のcgroup.subtree_control
ファイルにCPUコントローラが登録され、その子cgroupであるth01
、th02
のcgroup.controllers
ファイルにはcpu
が登録されており、CPUコントローラが使える状態になっています。
ここでおもむろにtd
のcgroup.type
ファイルを確認して、td
のcgroupタイプを確認してみましょう。
# cat td/cgroup.type
domain threaded
プロセスが所属し、なおかつCPUコントローラが使える状態となったtd
はdomain threadedになっています。その子cgroupであるth01
、th02
のタイプを確認してみましょう。
# cat td/th{01,02}/cgroup.type
domain invalid
domain invalid
親となるtd
が"domain threaded"となったので、元々domainであった子cgroup th01
、th02
はいずれも"domain invalid"となっています。スレッド化サブツリーを完成させるには、この2つのcgroupをthreadedに変えましょう。
図6 tdがdomain threadedに変化
# echo "threaded" > td/th01/cgroup.type
# echo "threaded" > td/th02/cgroup.type
# cat td/th{01,02}/cgroup.type
threaded
threaded
th01
、th02
がthreadedに設定されました。これでスレッド化サブツリーの完成です。
図7 スレッド化サプツリーの完成
cgroup.type
ファイルへの書き込みでスレッド化サブツリーを作成した際の例と同様にスレッドを登録してみましょう。
# pstree -p 928
threadtest(928)─┬─{threadtest}(929)
└─{threadtest}(930)
# echo 928 > /sys/fs/cgroup/td/cgroup.procs
# echo 929 > /sys/fs/cgroup/td/th01/cgroup.threads
# echo 930 > /sys/fs/cgroup/td/th02/cgroup.threads
# cat /sys/fs/cgroup/td/cgroup.procs
893
928
939
# cat /sys/fs/cgroup/td/th01/cgroup.threads
929
# cat /sys/fs/cgroup/td/th02/cgroup.threads
930
親プロセスのPIDとそのプロセスに所属するスレッドが、同一のスレッド化サブツリーの別々のcgroupに登録できていることがわかります。
cgroup.threads
ファイルへのタスクの登録権限
第40回 で、cgroup v2を操作する際の権限についての説明をしました。
今回の実行例では、いずれもroot
権限で操作を行っていますので、特に気にする必要はありません。しかし、一般ユーザ権限でプロセスやスレッドの操作を行う場合は、第40回 での説明と同様に必要な権限を持っているかどうかを考える必要があります。
つまり、cgroup.threads
ファイルへの書き込みには第40回 で説明した、cgroupへのプロセス登録の際と同様の権限が必要です。
書き込む先のcgroup.threads
ファイルへの書き込み権
移動元と移動先のcgroupの共通の祖先にあるcgroup.procs
ファイルへの書き込み権
そして、前回 説明したとおり、同じプロセス内のスレッドは同じスレッド化サブツリーに所属する必要がありますので、
移動元と移動先のcgroupは同じスレッド化サブツリー内に所属している必要がある
という条件が加わります。
root cgroupの扱い
これまでの例では、root cgroupの下に"domain threaded"となるcgroupを作成し、その配下に"threaded"なcgroupを作りました。
では、root直下に"threaded"なcgroupを作成できるのでしょうか? 試してみましょう。
root直下にth01
というcgroupを作成し、"threaded"に変更します。
# mkdir /sys/fs/cgroup/th01
# cd /sys/fs/cgroup/
# echo threaded > th01/cgroup.type
# cat th01/cgroup.type
threaded
このth01
に、これまでの実行例と同様のマルチスレッドプロセスを登録してみます。
# pstree -p 902
threadtest(902)─┬─{threadtest}(903)
└─{threadtest}(904)
# echo 902 > th01/cgroup.procs
# cat th01/cgroup.threads
902
903
904
このように何の問題もなく登録できました。つまりroot直下に"threaded" cgroupを作成できるということです。
ここでスレッドを1つrootに移動してみましょう。
# echo 904 > /sys/fs/cgroup/cgroup.threads
# grep 904 cgroup.threads
904
# cat th01/cgroup.threads
902
903
移動したスレッドだけがrootに移動し、残りのタスクはth01
cgroupに残っています。
つまり、root直下に"threaded" cgroupを作ると、root cgroupが"domain threaded"と同じ働きをします。そして"domain threaded"なcgroupが存在する場合と同様に操作できます。
root cgroupは"domain"と"threaded"なcgroupの親になります。このようにroot cgroupを例外的に扱っているのは、"threaded"なcgroupをroot直下に作ることでなるべく階層を浅くして、cgroup階層を走査するコストを下げるためです。
図8 root直下のcgroupを"threaded"に変更する
もし"threaded"に変化したroot直下のcgroupが子cgroupを持っていた場合は、子cgroupは"domain invalid"に変化します。その子cgroupを使用するためには、そのcgroupを"threaded"に変更する必要があります。
# mkdir -p th02/th03
# echo threaded > th02/cgroup.type
# cat th02/cgroup.type
threaded
# cat th02/th03/cgroup.type
domain invalid
まとめ
今回はスレッド化サブツリーの操作について説明をしました。
スレッド化サブツリーを作成するには2つの方法がありました。
cgroup.type
ファイルにthreaded
という文字列を書き込む
プロセスが存在するcgroupへスレッドコントローラーを登録する
そして、スレッド化サブツリー内のプロセス、スレッドの操作方法や権限、root cgroupの例外的な扱いについても説明しました。
今回でcgroup v2のスレッドモードの説明は終わりです。前回と今回の説明で、スレッドモードがcgroup v2の仕様とうまく共存できるように作られていることが理解できたのではないでしょうか。