systemdとcgroup

systemdではcgroupを活用しているようだ。サービスごとにcgroupでアイソレーションされているので、関係するプロセスを一網打尽で殺せるし、サービスごとの資源利用量を観測できる。確かにpidをファイルに記録しておいて、殺すってのはダサいよね。流れとしてはユニットファイルでExecStopで指定されているコマンドを実行し、それでも当該グループのプロセスが残っている場合は、SIGTERM、SIGKILLの順でシグナルを投げるそうな。詳細はレッドハット中井さんの「Linux女子部 systemd徹底入門」とか。

次はsystemd-cgtopの実行例。

Path                                                         Tasks   %CPU   Memory  Input/s Output/s

/                                                               86    0.3   197.3M        -        -
/system.slice/NetworkManager.service                             2      -        -        -        -
/system.slice/auditd.service                                     1      -        -        -        -
/system.slice/avahi-daemon.service                               2      -        -        -        -
/system.slice/crond.service                                      1      -        -        -        -
/system.slice/dbus.service                                       1      -        -        -        -
/system.slice/firewalld.service                                  1      -        -        -        -
/system.slice/iprdump.service                                    1      -        -        -        -
/system.slice/iprinit.service                                    1      -        -        -        -
/system.slice/iprupdate.service                                  1      -        -        -        -
/system.slice/lvm2-lvmetad.service                               1      -        -        -        -
/system.slice/polkit.service                                     1      -        -        -        -
/system.slice/postfix.service                                    3      -        -        -        -
/system.slice/rsyslog.service                                    1      -        -        -        -
/system.slice/sshd.service                                       1      -        -        -        -
/system.slice/system-getty.slice/getty@tty1.service              1      -        -        -        -
/system.slice/systemd-journald.service                           1      -        -        -        -
/system.slice/systemd-logind.service                             1      -        -        -        -
/system.slice/systemd-udevd.service                              1      -        -        -        -
/system.slice/tuned.service                                      1      -        -        -        -
/system.slice/vboxadd-service.service                            1      -        -        -        -
/user.slice/user-1000.slice/session-4.scope                      4      -        -        -        -

大きく分けるとsystem.sliceとuser.sliceってのがある。/sys/fs/cgroup/systemdがルートね。
systemd-cglsでcgroupの使われ方を見ることができる。

[vagrant@localhost ~]$ systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 23
├─user.slice
│ └─user-1000.slice
│   └─session-4.scope
│     ├─2407 sshd: vagrant [priv]
│     ├─2410 sshd: vagrant@pts/0 
│     ├─2411 -bash
│     ├─2471 systemd-cgls
│     └─2472 systemd-cgls
└─system.slice
  ├─polkit.service
  │ └─747 /usr/lib/polkit-1/polkitd --no-debug
  ├─auditd.service
  │ └─501 /sbin/auditd -n
  ├─systemd-udevd.service
  │ └─455 /usr/lib/systemd/systemd-udevd
  ├─lvm2-lvmetad.service
  │ └─440 /usr/sbin/lvmetad -f
  ├─systemd-journald.service
  │ └─426 /usr/lib/systemd/systemd-journald
  ├─dbus.service
  │ └─543 /bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
  ├─systemd-logind.service
  │ └─542 /usr/lib/systemd/systemd-logind
  ├─sshd.service
  │ └─1109 /usr/sbin/sshd -D
  :

冒頭で触れた挙動を確認するため、ちょっとソースコードを眺める。systemd本体はsrc/core以下なのかな。サービスユニットの上げ下げはsrc/core/service.cとかsrc/core/unit.cあたりかな。確かにSysVinitでシェルスクリプトで実装されていた分が全部Cで書かれているわけね。たしかにこれはビッグチェンジだなぁ。

service_stop()がsystemctl stopで呼ばれる関数。ユニットごとに状態遷移マシンになっていて、service_enter_stop()でExecStop、service_enter_stop_post()でExecStopPostに登録されたコマンドを順に実行する。コマンドの実行に失敗した場合は、service_enter_signal()を経由してunit_kill_context()でプロセスにSIGTERMを投げる。それでも死なないプロセスはwatchdogで回収され、同様にunit_kill_context()でSIGKILLを投げる。

unit_kill_context()が参照するKillContextにはkill_modeというメンバ変数があり、ユニットはKILL_CONTROL_GROUPに初期化されている(SysVinit互換のケースはKILL_PROCESSになっている)。unit_kill_context()ではcg_kill_recursive()を呼んで、cgroupのプロセスにシグナルを投げる。
cg_*関数の定義はsrc/shared/cgroup-util.c。cgroupの実装がファイルシステムというのがよくわかる。