criu checkのソースを読んでみる

趣旨

criu checkのソース(cr-check.c)を読んで、checkpoing/restoreに必要な機能について調べる。

criu checkを実行してみる

環境はFedora 20(kernel 3.10-rc3) + criu git master HEAD。

$ sudo /usr/local/sbin/criu check       
(00.011674) Error (mem.c:67): Can't reset 3122's dirty memory tracker: Invalid argument
(00.012216) Error (kerndat.c:107): Dirty tracking support is OFF
(00.012360) Warn  (cr-check.c:516): Dirty tracking is OFF. Memory snapshot will not work.
Looks good.

--msを付けるとマージされていないカーネル機能のチェックをスキップする。

$ sudo /usr/local/sbin/criu check --ms
(00.001923) Warn  (sockets.c:616): Skipping netling diag check (not yet merged)
(00.003775) Warn  (cr-check.c:473): Skipping peeking siginfos check (not yet merged)
(00.003862) Warn  (cr-check.c:508): Skipping dirty tracking check (not yet merged)
Looks good.

とりあえず実装されている機能はすべてうまく動いていそう。

cr-check.cを読んでみる

check_tty()

ttyはよくわからん(´・ω・`)

check_map_files()
  • /proc/self/map_filesディレクトリの有無を調べている
  • /proc/self/map_filesはCR用に作られた → コミット
    • たぶん/proc/self/mapsでもできなくはないと思うけど、パースするの面倒そう
check_sock_diag()
  • collect_sockets()@sockets.cを呼んでるだけ
  • collect_socketsはPIDを引数に取り、netlinkを使って、当該プロセスが持つソケットの情報を取得する関数
    • socket(PF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
  • 取得するソケットのdomain: UNIX, IPv4 TCP/UDP/UDP-lite, IPv6 TCP/UDP/UDP-lite, packet, netlink
  • ここではPID=0つまりidleタスクを指定しているので、何も取得できない代わりに機能が実装されているかどうかだけチェックできる
check_ns_last_pid()
  • /proc/sys/kernel/ns_last_pidの有無を調べる
  • ns_last_pidは最後に生成されたプロセスのPIDを返す
    • bash -c 'echo $$; read pid < /proc/sys/kernel/ns_last_pid; echo $pid'ってやると確かめられる
  • またPIDを書き込んで次に生成されるプロセスのPIDを制御できる
    • プロセスをrestoreするときに使われる
    • echo 1111 > /proc/sys/kernel/ns_last_pid; bash -c 'echo $$; read pid < /proc/sys/kernel/ns_last_pid; echo $pid'
check_sock_peek_off()
  • SO_PEEK_OFFソケットオプションが実装されているか調べる
    • getsockopt(sk, SOL_SOCKET, SO_PEEK_OFF, &off, (socklen_t *)&sz);
  • SO_PEEK_OFFはMSG_PEEKオプションを指定したrecv*システムコールでpeek(盗み見る)するデータのオフセットを取り出したり、変更したりするためのもの
  • そもそもMSG_PEEKはソケットにあるデータの先頭からしかデータを盗み見ることはできなかった(必要なかったので
  • そこでMSG_PEEKにもオフセットが用意された(SO_PEEK_OFFが追加されたとき)
  • オフセットはMSG_PEEKでrecv*システムコールを呼ぶ度に盗み見たデータサイズ分増加する(ファイルポインタと同じ)
  • これにより、MSG_PEEKでrecv*を何度も呼び出せば、ソケットに溜まったデータをすべて盗み見る=dumpすることが可能になる
  • 疑問: この機能は必須なのだろうか?
check_kcmp()
  • kcmp(2)の有無を調べる
  • kcmpは2つのプロセスが資源を共有しているか調べる手段を提供する
  • kernel/kcmp.cを見てみると、資源を共有している=task構造体が持つ資源毎のオブジェクトのアドレスが一致する、という判定をしていて興味深い(単純な比較をしているわけではないけれども)
check_prctl()
  • prctl(2)の必要なオプションが実装されているか調べる
  • PR_GET_TID_ADDRESS
    • clear_tid_addressというpthread_joinに使われるアドレスを取得する
      • pthread_join以外に使われているのかは未調査
    • CLONE_CHILD_CLEARTIDオプション付きでclone(2)したプロセスは、プロセスが終了するときに、clear_tid_addressを0クリア+当該アドレスでfutex wakeされる
    • pthread_joinする=対象スレッドのclear_tid_addressでfutex waitする、ってことなんだろう(たぶん)
  • PR_SET_MM + PR_SET_MM_BRK
    • restoreするときにコード、データ、ヒープ、スタックのアドレスを指定してプロセスのメモリを復元するための機能
    • PR_SET_MM_BRKは最初にPR_SET_MMが実装されたときに用意されたオプション。PR_SET_MMが実装されているかどうか判定できる
  • PR_SET_MM + PR_SET_MM_EXE_FILE
    • mm_struct::exe_file(/proc/pid/exe)を書き換える
  • PR_SET_MM + PR_SET_MM_AUXV
check_fcntl()
  • F_GETOWNER_UIDSオプションの有無を調べる(未実装)
check_proc_stat()
check_fdinfo_eventfd()
  • eventfd(2)で作られるfdの/proc/pid/fdinfo/fdにeventfd-countのエントリが存在するか調べる
  • eventfd-countはeventfdしたときにカーネル内部に用意されるカウンターの値
    • カウンターについてはman 2 eventfdを参照
check_fdinfo_signalfd()
  • signalfd(2)で作られるfdの/proc/pid/fdinfo/fdにsigmaskのエントリが存在するか調べる
  • sigmaskはsignalfdを呼び出すときに指定する
check_fdinfo_eventpoll()
  • epoll_create(2)で作られるfdの/proc/pid/fdinfo/fdにtfdのエントリが存在するか調べる
  • epoll_ctl(2)で追加したfdのtfdが一致するか調べる
check_fdinfo_inotify()
  • inotify_init1(2)で作られるfdの/proc/pid/fdinfo/fdにinotify wdのエントリが存在するか調べる
  • inotify_add_watch(2)で追加したfdがinotify wdと一致するか調べる
  • 疑問: 上記パッチでfanotifyのエントリも追加されているけど、それに対するチェックがない
check_unaligned_vmsplice()
  • 局所変数char(1バイトデータ)をvmsplice(2)(SPLICE_F_GIFTオプション付き)できるか調べる
  • vmspliceはpipeのデータをrestoreするときに使われる
  • 古いカーネルでは、SPLICE_F_GIFTを付けたときはデータはページ単位でなければならなかったが、このパッチでその制限が取れているはず
  • 疑問: man vmspliceのSPLICE_F_GIFTにはデータはページ単位でなければならないと書いてあるけど、更新されていない?
check_so_gets()
  • getsockopt(2)のSO_GET_FILTERとSO_BINDTODEVICEオプションの有無を調べる
  • SO_GET_FILTER: SO_ATTACH_FILTERで設定したフィルタを取り出す
  • SO_BINDTODEVICE: SO_BINDTODEVICEで設定したデバイス名(インタフェース名)を取り出す
check_ipc()
  • /proc/sys/kernel/sem_next_idの有無を調べる
  • ns_last_pidと同じように、次に割り当てられるIPCオブジェクトIDを制御するためのもの
check_sigqueuinfo()
  • rt_sigqueueinfo(2)でsiginfo.si_code > 0の値siginfoを設定できるか調べる
  • rt_sigqueueinfoはsigaction(SA_SIGINFO)で受け取れるsiginfo_tを指定してシグナルをプロセスに飛ばす
  • si_code > 0なsiginfoはカーネル用で予約されていて、今まではrt_sigqueueinfoで他のユーザにシグナルを飛ばせられなかった
  • このパッチで自分自身にはsi_code > 0なsiginfoを飛ばすことができるようになった
check_ptrace_peeksiginfo()
  • ptraceの新たな機能(リクエスト)PTRACE_PEEKSIGINFOの有無を調べる
  • PTRACE_PEEKSIGINFOを使うとプロセスのシグナルを盗み見ることができる
    • 指定offsetから指定数だけ盗み見れる
  • 元々PTRACE_GETSIGINFOという機能はあったけど、これは最後のシグナルの情報しか取れなかった
check_mem_dirty_track()
  • kerndat_get_dirty_track()@kerndat.cを呼んで機能が有効になっているか調べる
  • ここで言ってるdirty trackとは、VMのライブマイグレーションマイグレーション中に更新されたメモリ(ページ)だけ再度転送するためにやってることのプロセス版で、soft dirty trackingのこと(まだマージされていない)
  • soft dirty tracking
    • 機能要件
      • 必要なときだけonにしてoffのときにはオーバヘッドがないようにする
      • ユーザレベルからdirty pageを取得できるようにする
      • ユーザレベルからdirty bitをクリアできるようにする(この要件があるのでハードウェアのdirty bitは使えない)
    • PTE内にハードウェアのdirty bitの他にソフトウェアで管理するsoft dirty bitを用意する
    • /proc/self/pagemap経由でsoft dirty bit読み出せるようにする
      • pagemapはV2Pのページマッピング情報を取り出すためのインタフェースで64bitのエントリ中に付加情報を入れることができる
    • echo 4 > /proc/pid/clear_refsで全ページのsoft dirty bitをクリアするとともに、ページをreadonlyにして書き込み時にページフォールトが起きるようにする
    • ページフォールトが起きた時にsoft dirty bitが立つようにする
    • これによりclear_refsに書き込んだ時点からのdirtyページを取得することができる
  • kerndat_get_dirty_trackは実際にsoft dirty bitが立つかどうか試している
    • /proc/pid/pagemapの前に/proc/pid/pagemap2を試している
  • 疑問: 結局pagemap2って追加されるの?

CRIU(crtools)関連のリンク

Androidアプリ作成時に参考にしたサイト

初めてAndroidアプリを作って、Android Marketで公開するまでに参照したサイトをまとめてみました。

SDKインストール(Mac向け)

ハードウェアスペックは?(HTC Desire)

Eclipseでの開発の基礎など

データを保存する方法にどんなものがあるのか?

Intentについて

Activityについて

ListViewとListActivityについて

Layoutや外観など

SQLiteを使いたい

長押しをハンドリングしたい

ダイアログ

その他

アプリ公開

systemdをいじったときのメモ

はじめに

How to boot up Fedora 15 with systemd inside Libvirt LXC · GitHubを書いたときに知ったsystemdについてのメモです。

systemdが起動していないときにファイルをいじる方法が主で、systemctlコマンドの使い方についてはあまり書かれていません。あしからず。

Systemdのバージョンは26(Fedora 15のもの)です。

systemdとは?

  • sysvinitやUpstartの仲間
  • Upstartと同じくsysvinit互換のrcスクリプトが使える
  • sysvinit/Upstartとの比較はここの表がわかりやすい
  • D-BUSとかPOSIX CapabilitiesとかcgroupsとかPlymouthとかInotifyとかPolicyKitとかいろいろな機能をバリバリ使ってる
    • 移植性?そんなの関係ねー!
  • いろいろな起動タスクや資源を定義して、それらの依存関係を記述することでブート順序を制御できる
  • 既存関係をみて可能ならば並行実行する
  • などなど

コマンドやファイル

  • /sbin/systemctl: 起動タスクの制御、状態の表示、依存関係のグラフ化などいろんなことができるコマンド
    • これ一つだけで大体できる
    • sysvinit互換のservice/chkconfigコマンドもある
  • /etc/systemd/system.conf: systemd自身の設定ファイル
  • /lib/systemd/system/: 起動タスクの定義ファイルがある場所
  • /etc/systemd/system/: システムローカルの起動タスクの定義ファイルがある場所
    • 起動タスクの挙動を変えたい時はこっちをいじる
  • /etc/systemd/system/default.target: sysvinitでいうところのrunlevelの設定。/lib/systemd/system/のファイルへのシンボリックリンク
    • デフォルトは/lib/systemd/system/graphical.target (runlevel=5)
    • runlevel=3にしたければ、ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target
  • man systemd: マニュアル
    • 他にもsystemd.*ってマニュアルがいっぱいあるので一通り目を通しておくと良い

起動タスク/資源の定義ファイル

起動タスクや資源にはいろいろ種類があるけど、ひっくるめてunitと呼んでいる。

  • service: コマンドを実行したり、デーモンを起動したりする
  • target: 自身では何も実行しない。unitのグループ化などに用いられる
  • mount, automount: マウントポイントを表わす定義ファイル。automountには対応するmountが必要
    • mountには何をどこにマウントするかを記述する。automountに記述するか、依存関係の結果によりブート時に自動的にマウントされる
    • automountはその名の通りブート時にマウントさせたいときに使う。依存関係にないが、自動的にマウントさせたいときに使う(たぶん)
  • socket: UNIXドメインソケットやFIFOIPアドレス&ポート番号などのIPC/通信資源を表わす
    • 接続要求があったら対応するserviceを起動する
    • serviceを永続的に起動しておく場合(D-BUSやsyslogデーモン)と、接続要求ごとに起動する場合(xinetdのようなもの)がある
  • path: ファイルやディレクトリ資源を監視して、状態に合わせてserviceを起動/有効化する
    • 指定ファイルに変化があったらserviceを起動するとか、指定ディレクトリが空でない場合(≒ファイルが存在する限り)対応するserviceを起動しておくとか

依存関係などの記述方法

Unitによって記述できる内容は違う。詳しくはman systemd.*参照(例えば、systemd.targetやsystemd.service)

サンプルとしてfedora-autorelabel.serviceを載せる。

[Unit]
Description=Relabel all filesystems, if necessary
DefaultDependencies=no
Requires=local-fs.target
Conflicts=shutdown.target
After=local-fs.target
Before=sysinit.target shutdown.target
ConditionSecurity=selinux
ConditionKernelCommandLine=|autorelabel
ConditionPathExists=|/.autorelabel

[Service]
ExecStart=/lib/systemd/fedora-autorelabel
Type=oneshot
TimeoutSec=0
RemainAfterExit=yes
StandardInput=tty
  • Requires: 当該unitが動いているときは指定したunitも起動しなければならない(動いていなければならない)
    • 順序関係はない。つまり、並行起動可能である(ただし、後述のBefore/Afterが指定されていない場合)
    • 後に起動したunitが何らかの理由で起動しなかった場合には、当該unitも起動しない(すでに起動していた場合は終了する)
  • Conflicts: 当該unitが動いているときは指定したunitは起動してはならない(動いていてはならない)
    • 使い方の一つは終了処理。shutdown.targetを入れておいて、シャットダウン時にshutdown.targetを強制起動させると、依存関係により当該unitを停止させようとする=終了処理が実行される
  • Wants: 当該unitが起動さえるとき、指定したunitの起動も試みる
  • Before: 当該unitは指定unitが起動する前に起動しなければならない
  • After: 当該unitは指定unitが起動した後に起動しなければならない
  • Condition*: 指定した条件を満たした場合のみ起動する

詳しくはman systemd.unitを参照。

知っておくと良いかもしれないこと

  • unitを無効にしたい場合は/etc/systemd/systemディレクトリ内に当該定義ファイル名の/dev/nullへのシンボリックリンクを作れば良い
    • 例: ln -sf /dev/null /etc/systemd/system/sysinit.service
    • 無効にすると当該unitをRequiresしているunitは当該unitを無視して起動する
    • もちろん、その結果、依存する処理が実行されず起動に失敗するかもしれないので取り扱いには注意
  • systemdは定義ファイルにないmountを行なう
    • /sysや/runなど
  • systemdは暗黙的に依存関係を追加している
    • basic.targetはsysvサービスをRequires/Afterしている
    • 詳しくはman systemd.special参照
  • socket unitを遅延起動するためにsystemdは当該資源をepollで監視してる
    • 当該資源にデータが届いたら、オンデマンドに当該serviceを起動する
  • 起動するgettyを減らしたい時は/etc/systemd/system/getty.target.wants/以下のシンボリックリンクを削除すれば良い
  • systemctl dotで依存関係のグラフ(Grapvizのdotコマンドで扱える形式)を生成することができる
  • 依存関係はsystemctl show -p Requires basic.targetなどで調べることができるけど、systemctl dot |grep basic.targetの方がわかりやすい場合がある

cgroupsとしばらく一緒に過ごしてみた

この記事はhttp://atnd.org/events/10701向けに書かれました。

はじめに

サーバ側ではかなり前からcgroupsを使っていましたが、クライアント側では使ったことがなかったので、頑張ってノートPCで使ってみることにしました。12月から使い始めて、1月半ぐらい使い続けてます。

サーバ側では独自スクリプトを書いて設定していましたが、今回は既存のツール(libcgroup)を使っています。とはいえ、libcgroupには資源利用状況を確認する機能がなかったので、独自スクリプト(cgtree)を作りました。もしよかったら使ってみてね!

今回cgroupsでやりたかったことは、

  • 資源を大量に消費するプロセスの制限
  • バックグラウンドプロセスの制限

などです。限られた資源を無駄に使うプロセスに鉄槌を。

注意

この記事で紹介する設定は、自分の環境に合わせてちまちまとチューニングしてきた結果ですので、万人向けではありません。私の設定は参考程度にして、自分の要件に合わせてチューニングしましょう。

定量的に評価してないので、著者の気のせいかもしれません。I _feel_ that the user experience is better than so far!

禁句:良いスペックのマシン買えば?メモリ増やせば?

基礎知識

cgroupsとは?

Linuxに実装された、プロセスをグループに分けて資源使用量を調節する機能です。個々の資源毎にサブシステムが用意されていて、個別にon/offできます。今回はcpu, cpuacct, memory, blkio, freezerサブシステムを使います。

何も設定していなければ、すべてのプロセスはどのグループにも属さないことになります。(rootグループに入っているとも言う。)

詳しくはRed Hatの平さんのプレゼン資料 (pdf)を読めばいいと思うよ!

libcgroupとは?

cgroupsの設定や操作を簡単に使うためのツールです。いろいろなデーモンやコマンドラインツールがありますが、今回主に使うのはcgconfigサービスとcgredサービス(cgrulesengdデーモン)です。cgconfigは/etc/cgconfig.confに設定されたとおりにグループ階層を作り(mountし)各種パラメタを設定します。cgredはfork/execなどを監視し、設定されたとおりにプロセスをグループに入れます(詳しい動作は後述)。

詳しくはRed Hatの平さんのプレゼン資料 (pdf)を読めばいいと思うよ!

cgtreeとは?

cgtreeは拙作のcgroupsグループ単位で資源利用状況を表示してくれるツールです。

[使用例]

$ cgtree -o memory
# Total=1434.7MB, Used(w/o buffer/cache)=699.8MB, SwapUsed=139.0MB
# Legend: # of procs (TotalUsed, RSS, SwapUsed)
: 103 procs (636.3MB, 343.3MB, 118.9MB)
  bgjob: 10 procs (42.3MB, 31.7MB, 31.7MB)
  noswap: 2 procs (29.9MB, 6.0MB, 1.1MB)
  browser: 1 procs (351.8MB, 201.5MB, 0.0MB)
    flash: 1 procs (13.1MB, 7.2MB, 0.0MB)

詳しい使い方はREADMEを読むかcgtree -hでオプションを確認してね!

環境

  • ハードウェア
    • Intel(R) Core(TM)2 CPU U7500 @ 1.06GHz
    • 1.5 GB RAM
  • ソフトウェア
    • Ubuntu 10.10 (Maverick)
    • linux-2.6.35-25-genericをカスタマイズしてblkioを有効にしたもの
    • libcgroup-0.37

要件

ここでは具体的な要件(私がやりたいこと)をまとめます。

  1. ブラウザが使う資源を制限したい
    • 他のプロセスのCPUやメモリを食い尽くすのを止めさせたい
    • Flashをおとなしくさせたい
      • Firefoxの場合Flashのせいで本体が遅くなることがある
  2. バックグラウンドプロセスが使う資源を制限したい
    • cron, anacron, dropbox, tracker(検索インデクス生成する要らない子), gwibber-service(マルチサービス対応SNSクライアントな要らない子)
    • カーネルコンパイル
      • 他の仕事を優先させたくて、コンパイルを一時停止させたいという要件もある
    • おまいらにくれてやる資源ねーから!
  3. Mozcの反応を良くしたい
    • しばらく使ってなくて、いざ使おうとすると反応が遅くなるのをどうにかしたい
    • スワップアウトさせない
  4. Autogroupの真似事もしてみたい
    • .bashrcとかに書くアレ (注:このページ妙に重いです)

具体的な設定

主に/etc/cgconfig.confと/etc/cgrules.confをいじっていますが、cgrules.confの方は見たまんまなのでcgconfig.confの方を説明していきます。

完成品

https://gist.github.com/802907 に置いておきますね。

ブラウザ編

作ったグループは2つ。

  1. browser
    • 他のプロセスからの隔離
  2. browser/flash
    • Flashのブラウザ本体への悪影響を減らす

使ったサブシステムも2つ。

  1. cpu
  2. memory

(cpuacctはcpuとペアで使うのでカウントしてないです。)

group browser {
  cpu {
    cpu.shares = 500;
  }
  cpuacct {
  }
  memory {
    memory.soft_limit_in_bytes = 400M;
    memory.limit_in_bytes = 500M;
  }
}
group browser/flash {
  cpu {
    cpu.shares = 200;
  }
  cpuacct {
  }
  memory {
    memory.soft_limit_in_bytes = 200M;
    memory.limit_in_bytes = 300M;
  }
}

[cpu]

browserグループのcpu.shareは500にしてあり、rootグループのプロセスの半分しかCPUが割り当たらないようにしています。最適な値かどうかわからないですが、今のところ問題なさそうです。

Firefox-4.0はfirefox-binとplugin-container(Flash)という2つのプロセスを生成します。同一グループに両プロセスを入れた場合、Firefox本体とFlashでCPU割当て比が1:1になり、FlashがCPUを大量に消費すると本体の動画がもっさりします。そこでbrowser/flash/をcpu.share=200に設定して比率を5:1になるようにしました。

ちなみにGoogle Chromeの場合は、本体、タブ、拡張機能がそれぞれ別プロセスになるため、Flashと同じグループに置いておいてもcpu割当て量は1:1になりません(N:1になる)。なのでbrowser/flashグループは必要ないと思います。

[memory]

本体を400MB、Flashに200MB割り当たるように設定しました。日々調整しながら決まった値なので、環境依存です。また変えるかも。

階層構造を作っていますが、use_hierarchy=1にしていないので、制限は独立しています(つまり400MBを本体とFlashで取り合うわけではない)。

soft_limit_in_bytesとlimit_in_bytesについては後述。

バックグラウンドプロセス編

作ったグループは2つ。

  1. bgjob
    • 他のプロセスからの隔離
  2. bgjob/restricted

使ったサブシステムは4つ。

  1. cpu
  2. memory
  3. blkio
  4. freezer
group bgjob {
    task {
      uid = root;
      gid = peo3;
    }
    admin {
      uid = root;
      gid = root;
    }
  cpu {
    cpu.shares = 10;
  }
  cpuacct {
  }
  blkio {
    blkio.weight = 100;
  }
  memory {
    memory.soft_limit_in_bytes = 100M;
    memory.limit_in_bytes = 200M;
    memory.swappiness = 100;
  }
}
group bgjob/restricted {
  perm {
    task {
      uid = root;
      gid = peo3;
    }
    admin {
      uid = root;
      gid = peo3;
    }
  }
  cpu {
  }
  cpuacct {
  }
  freezer {
  }
}

[bgjob]

とにかく割当て資源量を少なくしています。cpu.share=10(rootグループのプロセスの1/100), メモリは100MB, swappinessは100(最もスワップアウトされ易くなる), blkio.weight=100(最低値)に設定。

[bgjob/restricted]

カーネルコンパイル時にはプロセスを大量に生成します。そして個々のプロセスはbgjob内のプロセスと同等のCPU資源を消費するので、バックグラウンドとはいえあまり嬉しくなかったので作りました。CPU使用量のみ制限してます。

単にCPU使用量を減らすだけならnice値を上げれば良いのですが、コンパイルを中断したいときがあったので別グループにしてfreezerサブシステムを有効にしました。これにより、

cgset -r freezer.state=FROZEN bgjob/restricted

で中断

cgset -r freezer.state=THAWED bgjob/restricted

で再開できます。また

cgexec -g blkio:bgjob cgexec -g cpu,freezer:bgjob/restricted fakeroot make-kpkg --initrd --append-to-version=-blkio kernel-image kernel-headers

で当該グループ内でカーネルコンパイルを行なうことができます*1

ちなみにユーザがtasksやfreezer.stateを書きかえられるようにpermで設定しています。

Mozc編
group noswap {
  memory {
    memory.swappiness = 0;
  }
}

swappinessを0にしてスワップされにくいようにしてます。いちおう効果があるぽいです。

なんちゃってAutogroup編
group user {
  perm {
    task {
      uid = root;
      gid = peo3;
    }
    admin {
      uid = root;
      gid = peo3;
    }
  }
  cpu {
#   release_agent = /usr/local/sbin/cgroup_clean;
  }
  cpuacct {
  }
  blkio {
    blkio.weight = 500;
  }
}

ユーザがディレクトリを作れるようにperm.admin.gidをユーザのGIDに設定しています。

libcgroupはrelease_agentの設定に対応していないみたいです。なので/etc/init.d/cgconfigで設定するようにしました。

bgjob/restrictedでカーネルコンパイルするようにしたので、効果はあんまりないかも?

cgrules.conf

cgconfig.confで生成したグループにどのプロセスを入れるかを記述する設定ファイルです。cgrulesengdというデーモンが指定プロセスを指定グループに入れます(詳しい動作は後述)。

peo3:firefox-4.0-bin  cpu,memory       browser
peo3:google-chrome    cpu,memory       browser
peo3:firefox-bin      cpu,memory       browser
peo3:plugin-container cpu,memory       browser/flash
peo3:ibus-engine-mozc memory           noswap
peo3:update-manager   cpu,blkio,memory bgjob
peo3:software-center  cpu,blkio,memory bgjob
peo3:dropbox          cpu,blkio,memory bgjob
peo3:gwibber-service  cpu,blkio,memory bgjob
peo3:tracker-store    cpu,blkio,memory bgjob
peo3:tracker-extract  cpu,blkio,memory bgjob
peo3:tracker-miner-fs cpu,blkio,memory bgjob
lastfm:lastfmsubmitd  cpu,blkio,memory bgjob
#root:cron             cpu,blkio,memory bgjob
root:anacron          cpu,blkio,memory bgjob
root:aptd             cpu,blkio,memory bgjob

cronの項目をコメントアウトしていますが、それはcgrulesengdがcronだけグループに入れてくれないからです。原因はまだわかってないです。

ただノートPCは常時起動させていないので、ほとんどの場合、定期実行タスクはanacron経由で実行されるため困ってはいないですが。

Tipsと注意

libcgroupのdefaultグループ

何はともあれ/etc/default/cgconfigを編集してCREATE_DEFAULT=noにしましょう。

cgconfigはCREATE_DEFAULT=yesのときにdefaultグループにすべてのプロセスを入れます。ですが、入れてはまずいカーネルスレッドも入れちゃうため、動作がおかしくなるときがあります*2。私の環境ではレジュームできなくなりました。

安全のためdefaultグループは使わないようにするのが良いと思います。(実際、なくても困らないですし。)

service cgconfig restart

修正した設定を反映させようとcgconfig restart/reloadすると、一度全部グループを消しちゃうので、割当てられたプロセスが全部rootグループに戻ってしまいます。

なので設定を反映させたかたらcgset等で自力でやるか、いっそのことrebootさせるのが良いかも。

service cgred restart

前述の通りシェルプロセスはuser/????/グループに入っているため、シェルでservice restartするとcgrulesengdデーモンがそのグループに入ってしまいます。

代わりにrootグループに入れるためには

sudo cgexec -g cpu,memory,blkio:/ service cgred restart

ってやると良いです。

cgconfig.confのコメントアウト

どうも行頭に'#'を書かないとだめみたいです。

デバッグ

See /var/log/messages

もっとkwsk知りたい方

cgrulesengdの動作

[どのタイミングでプロセスをグループに入れている?]

cgrulesengdはデーモンで、起動しているときにだけ指定プロセスを指定グループにいれてくれます。

最初は定期的にプロセスリストをチェックしてるのかと思ったのですが、そうではないようです。

netlinkのNETLINK_CONNECTORファミリでカーネルprocess events connectorという機能を使うことで、プロセスの起動などのイベント発生時にcallbackしてもらってるようです。fork/exec/exit/setuid/setgidなどのイベントを契機にプロセスをグループに入れたりしているようです。

[cgrules.confに指定するプロセス名前]

どういうプロセス名前が指定可能なのか調べてみると、わりとまじめに名前抽出をしてることがわかりました。

cgrulesengdは/proc//{status,exe,cmdline}の3つのファイルを使ってプロセス名を割り出しています。基本的にはstatusのNameエントリを見れば名前が判るのですが、(何の制限か知らないですが)その名前は15文字以降が切り捨てられています。そこでexe(シンボリックリンク)が指すファイルを使って名前を補完しようとします。もし、exeが実行可能ファイルならファイル名をそのまま使うのですが、もしそれがシェルやスクリプト言語インタプリタだった場合はさらにcmdlineを参照します。コマンドライン引数の2番目をチェックして、statusの名前と部分一致した場合cmdlineから取り出した名前を使います。

そうすることで/usr/bin/python /usr/lib/system-service/system-service-dのようなプロセスから、system-service-dという名前を抽出できるようになり、cgrules.confでsystem-service-dと指定可能になるわけです。

memory.limit_in_bytesとmemory.soft_limit_in_bytes

※正確なところはカーネル付属文書のSoft limitsの項を読むかソースコードを参照してください。

ざっくり言うとlimit_in_bytes(以下hard)の方が強い制限でsoft_limit_in_bytes(以下soft)の方が弱い制限ということになります。hardで設定したメモリ量を必ず越えないようにカーネルが制限をかける一方で*3、softで設定したメモリ量は、空きメモリに余裕がある間は超えても良いが、メモリが足りない状況では回収(もしくはスワップアウト?)されるかもしれないですすよ、ということらしいです。

softの細かい動作はこの辺りを読めば解るかも? kswapd => balance_pgdat => mem_cgroup_soft_limit_reclaim

もしsoft関連のreclaimがここにしかないならば、グループ内のプロセスがsoft limitを超えるメモリを使おうとしても、そのタイミングでは制限をかけない、ってことになるんじゃないかと思います。

group_isolationは設定すべき?

Linux付属ドキュメントにgroup_isolationに関する項目というものがあります。どうも、isolationを厳しくするか否かの設定らしいです。

私の環境では/sys/block/sda/queue/iosched/group_isolationです。

ドキュメントを読むと、0の場合(デフォルト)はsequential workloadの場合しか公平性を保証しない。1の場合は加えてrandom I/Oの公平性も保証するが、スループットが落ちるらしいです。

その下の段落の説明はよくわからなかったです。sync-noidleとかcollective idlingってなんだろ?

あと、最後の節「What works」に

Currently only sync IO queues are support. All the buffered writes are still system wide and not per group.

とありますね。。。うーん実際のところ普段のworkloadのうちどのぐらいがこのsync I/Oに該当するんだろ?もしかして大して効果ないのかな?

この辺りはまた暇があったら調べてみます。

おわりに

といった感じで様々な設定を行なうことで、前述の目標

  1. ブラウザが使う資源を制限したい
  2. バックグラウンドプロセスが使う資源を制限したい
  3. Mozcの反応を良くしたい
  4. Autogroupの真似事もしてみたい

は達成できた気がします

個々の設定を決めるために、top, iotop, free, ps, vmstat, dstat, nice, ioniceそしてcgtreeといった資源使用量表示ツールを使って思ったとおり動いているか、自分で体感して不満はないかなどかなり試行錯誤をしています。快適な環境は一日にしてはならずですね。まぁメモリ増設すれ「それ以上いけない」

なにはともあれ、みんなもcgroupsを日常的に使ってみよう!

*1:カーネル2.6.35はblkioの階層機能がなく、blkioとcpu,freezerでグループ階層が違うため、個別にグループ割当てを行なってます。2.6.38からはこの制限がなくなります。

*2:関係ありそうなバグレポート https://lkml.org/lkml/2011/1/4/313

*3:やむを得ず超えることもあるかも?この辺はソース読まないとなんとも言えないです。