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って追加されるの?