RHEL6のtickless kernel

CentOS5 をVMWare Fusionで動かしていると、VMが常時10-20%程度のCPU負荷を与えているのが気になっていたが、CentOS6ではその現象が見られない。アイドル時の割り込みがほとんど発生しなくなった事による改善と思われる。
CentOS6.0 (カーネル:2.6.32, x86-64) について調べた。

tickless (NOHZ)時の動作概要図


わかった事(ticklessとは関係無し)

  • /etc/sysconfig/clockの UTC の設定はCMOS RTCの値をOSがどう解釈するかを指定する。
    • UTC=true: CMOSに保持しているRTCをUTC(ほぼグリニッジ標準時と考えて問題ない)として扱う。ZONE="Asia/Tokyo" であれば、UTC+09:00でJSTを求める。
    • UTC=false: CMOSに保持しているRTCをローカルタイム(日本時間がそのまま保存されている)として扱う。加減算なしでそのままJSTとして扱う。
    • WindowsはローカルタイムでRTCを保存するため、Linuxとのデュアルブート環境では注意が必要。
    • Linux単独の場合にはUTC=trueとする。仮想環境でもUTC=trueとする。

わかった事

  • CentOS5(2.6.18カーネル)と比べると時刻関係のソースが激変している。
  • OSのシステム時刻は xtime に保持されている。
  • システム起動時にのみCMOSに保存しているRTC値を読み取り、xtime を初期化する。また、シャットダウン時に逆に書き込みを行う。
    • 起動時の読み取りはカーネル内で行う(UTC前提)。ローカルタイムの場合はsysinitで是正。
      • カーネルの起動時(init/main.c) に timekeeping_init() を呼び出し、そのなかで read_persistent_clock() (アーキテクチャごとの実装) を呼び出し xtime を初期化する。ただし、このタイミングでは /etc/sysconfig/clock を読めないのでRTCはUTC扱いになる。
    • CentOS5まで、CentOS6は下記非該当 (2011/11/30 修正)
      • /etc/rc.d/rc.sysinit 内でも hwclock --hctosys を実行しており、この読み込みはUTC/ローカルタイムを /etc/sysconfig/clock から取得して考慮する。
      • ローカルタイムでRTCを記録した状態で、rc.sysinit内のhwclockをコメントアウトしたら、ローカルタイムがUTCとして読み込まれ、dateなどの取得上は時計が9時間進んでしまった。
      • つまりRTCの時刻をローカルタイムにしていると、カーネル起動時から rc.sysinit までの間の時刻はずれることになる。しかも日本のようにローカルタイムがUTCより進む環境だとシステム時間が rc.sysinit 実行時に戻されることになる。これはRTCをUTCで保持するべき強い動機になりそう。
    • シャットダウン時の保存は /etc/rc.d/rc0.d/S01halt 内で /sbin/hwclock --systohc を使用して行う。この書き込みはUTC/ローカルタイムを /etc/sysconfig/clock から取得して考慮する。
      • シャットダウン時はカーネルは特に書き込みをしていない。dateでシステム時刻を変更後、S01halt 内の hwclock をコメントアウトしたまま再起動したら、dateによる変更は綺麗に消えてしまった。
      • カーネルが自主的にシステム時刻を定期的に(シャットダウン時もだが)ハードウェア時刻に反映することはない。※カーネルのコンフィグ "CONFIG_GENERIC_CMOS_UPDATE" が定義された場合は定期的に反映するが、RHELではデフォルトは未定義。11分間隔は短いとの議論(Technology Reviews & Software Reviews From Fixunix)があるためと思われる。(2011/11/30 修正。ただし反映は確認できていない)
      • 1日に1度ぐらいの頻度ではhwclockで書込んだ方がよいだろう。
    • RTCは秒精度。ミリ秒以下は保持できず。
  • 時刻処理に関係するハードウェアは『clock source』と『timer』の2種類。
    • timer は『定期的に割り込みを発生させる(periodic)』または『指定した時間経過後に1度割り込みを発生させる(oneshot)』ために使用する。最も古い仕組みはPITだが、oneshotを使用するにはオーバーヘッドが大きくかつ精度が低いということで、periodicに使われてきた。
    • clock source は『xtimeの更新に使用する時刻のソース』である。update_wall_time() がコールされた際に、この時刻のソースを使用してxtimeを更新する。この際前回の呼び出しからの経過時間を考慮して更新できる。
  • 時間処理に関係するハードウェアでも『clock source』としてのみ使えるの、『timer』としてのみ使えるもの、どちらとしても使えるものがある。
    • 使用できる『clock source』は /sys/devices/system/clocksource/clocksource0/available_clocksource で確認できる。
    • 使用できる『timer』は /proc/timer_list で確認できる。
    • カーネルが対応している『clock source』は "clocksource_register関数" の呼び出しを grep して見つけた。
    • カーネルが対応している『timer』は "clockevents_register_device関数" の呼び出しを grep して見つけた。
    • jiffiesの値を『clock source』として使用する "jiffies" が存在する。lost tick したこと自体に気がつくことはないため xtime 更新時の補正は無いことになる。外部から直接修正するためにわざと補正させないとか?
  • CentOS6.0@VMWare Fusion3の『timer』はLocal APIC、『clock source』はTSC
    • TSCはクロック数がspeedstepやACPIによる電力管理で変更されると期待した動作にならない。周波数変更については Constant TSC、電力管理(ACPI)による変更については Invariant TSC をサポートしていれば、それらによらず TSC は一定間隔でインクリメントされる。cpuinfoを覗いてみると、constant_tsc が確認できる。Invariant については Core2Duo では未導入のため確認できず。ソース上からだと『nonstop_tsc』とかそんな感じの出力になるはず。また、Invariant TSC は Constant TSC であることも暗に保証しているコーディングになっている(arch/x86/kernel/cpu/intel.c)。『reliable_tsc』は arch/x86/kernel/cpu/vmware.c が設定している。

# cat /proc/cpuinfo

processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz
stepping : 10
cpu MHz : 2389.757
cache size : 3072 KB
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss syscall nx lm constant_tsc up arch_perfmon pebs bts rep_good xtopology tsc_reliable aperfmperf unfair_spinlock pni ssse3 cx16 sse4_1 hypervisor lahf_lm
bogomips : 4779.51
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management:

  • CentOS5.5@VMWare Fusion3の『timer』は不明(PITと思われるが確認ができない。IRQ0の割り込みは増加しているのでPITの割り込みがあるのは確認できたが。)、『clock source』は jiffies (jiffiesの変化(=tick数)で時刻を管理するのでtickロスがそのまま時刻遅れになる)
  • 『clocksource』をOSに選択させずに指定するにはカーネルオプション『clocksource』を設定する。
    • 指定可能な値(未確認): pit, tsc, hpet, acpi_pm
  • ACPIの状態
    • T-state(Tx): Throttling-state のこと。CPUが熱暴走しそうな場合などにタスクの有無にかかわらず『動作を抑圧』するためのもの。T0状態は何も抑圧しない状態で、CPUは必要時は最高性能を出せる。周波数は変更しない。
    • C-state(Cx): Processor Power-state のこと(なんでC?)。C0状態(プロセッサは処理中・処理可能な状態)、C1/C2状態(消費電力を落としており、プロセッサは処理可能な状態ではない(がすぐ処理可能な状態に戻せる)。C2はC1よりさらに消費電力を落としており処理可能な状態に戻るまでの時間もより長くかかる)、C3状態(他のプロセッサ・コアとのキャッシュ同期も行わない状態。復旧時はキャッシュ整合性をOSレベルで回復させる)。周波数の"調整"ではなく、クロックの"停止"を行う。
    • P-state(Px): Performance-state のこと。C-stateがC0である状態で、消費電力を調整するために負荷状況から適切な周波数に調整(変更)する。
  • システムコール settimeofday と clock_settime のバックエンドは timekeepingのdo_settimeofday で同じ
    • ntpdateをstraceしてみたらsettimeofdayではなくclock_settimeを呼んでいた。結局は timekeeping の do_settimeofday 呼び出しになる。

わかっていない点

  • 複数の『clock source』と『timer』候補の中からOSがどういう基準で使用するものを1つ選び出すのか。
  • ticklessカーネルも周期的な割り込みを使わないわけではなく、『次のタイマー割り込みが周期的な割り込みより未来に発生すればよい』状態になった場合に、周期的な割り込みを中断し、oneshotで次の割り込みを発生させる?(でもVMwareのTimekeepingには周期的な割り込みがなくなったともあるし・・・)

The NO_HZ option provides a further significant improvement. Because the guest operating system does not schedule any periodic timers, the virtual machine can never have a backlog greater than one timer interrupt, so apparent time does not fall far behind real time and catches up very quickly.

追加調査

  • NTPとの関係
    • 外部のソース(NTP)と同期できている場合かつカーネルのコンフィグ"CONFIG_GENERIC_CMOS_UPDATE"が定義された場合(RHELのデフォルトでは未定義)(2011/11/30 修正。ただし反映は確認できていない)は、定期的(約11分ごと(659秒ごと))にカーネルがxtimeをCMOS RTCに書き出している(/kernel/time/ntp.c の sync_cmos_clock (内のupdate_persistent_clock))
    • RHELのデフォルトのカーネルはNTPで同期していても定期的にRTCへの書き出しをおこなっていないことになる。
    • CentOSのntpdはntpユーザ・ntpグループ権限で動作している。これがどう考えてもroot権限が必要なシステム時刻変更をしているのか。