仮想化環境での時間管理入門

ここでは、各種時計ハードウェアから始まる時間に関連するデータを、OSがどのように取得し、保持し、変化を追跡し私たちの目にとどけるのか概要を簡単に見ていくことにします。

三つの層に分けて考えてみる

時間という概念は難しいですが、コンピュータの中での "時間" は、少なくとも次の三つの層に分けて考えることができると思います[1]。

  • layer3 : User Space Layer, Application Usage. POSIX Clocks
  • OSのユーザー/アプリケーションが実際に使用できる時間関連のデータのこと。
    普段OSを使用していて目にするデータは基本的にこちらになります。
  • layer2 : OS Layer (ex:struct timekeeper (xtime), jiffies)
  • OS内部での時間関連のデータの保持形式やデータ構造のこと。
  • layer1 : hardware (ハードウェアカウンター) (リアルタイムクロックデバイス)
  • 仮想基盤がどのように時間関連のデータを提供しているか。
    基盤に起因する時刻ずれなどで話題に上がる。

OSの中では、時間に関わるデータが上記の3つの層の中で、カタチを変えて存在します。
今回は一番下のレイヤーについて概要を簡単に紹介します。

[1]: ここでは簡単化のために、擬似的に layer という概念を導入しています。ただし、この概念は本記事 でのみ有効なものなので注意してください。他では通用しませんし、本来このような概念は存在していません。

ハードから伝わる時間関連のデータ

layer1 のハードウェアカウンター、リアルタイムクロックデバイスから時間が伝播していく様子を簡単に図示しておきます。

正確ではありませんが単純化すると以下のようになるのではないでしょうか。

clock-1-arch.png

起動時の時間

まず OS が起動したら、リアルタイムクロックデバイスから ”yyyy MM dd HH mm ss"のカタチの世界時(地球時間)の時刻情報をそのまま形式でデータとして取得します[2]。
取得したデータはナノ秒形式に変換して timekeeper 構造体に格納します。

clock-1-rtc.png

OSが世界時(地球時間)の時刻情報といえるデータをハードウェアから取得するのはこの初回の一回のみです。

あとは、OSがどのように時間を刻んで、この初回に取得した時刻をいかにして地球時間に即した時間として保持、追跡していくのか、timekeeping の問題になります。

[2]:実際にはデバイスによって異なるが、基本的に yyyy MM dd HH mm ss 形式のデータを使う。正確には、エミュレーションされたデバイスが年月日の各情報ごとにのそれぞれのレジスタを持つ。

時刻を保持、追跡する

大事なことは、時間を刻んでいくのに、世界時(地球時間)の時刻情報はいらないということです。
時間を刻んでいくためには、初回の時点とある時点の "時間差(経過時間)"さえわかればいいのです。

"時間差(経過時間)"がわかれば、初期時刻に対して、都度、その時に計測した "時間差(経過時間)" を加えていけば、時刻を取得できる算段になります。
加えて、この方法で、その他の時間関連のデータも追跡できます。

clock-1-interval.png

疑問 :”時間差(経過時間)”が重要なのは、わかりました。ではどうやってこの”時間差(経過時間)”を取得することができるでしょうか。

時間を刻む(経過時間を取得する)

答えは、一定の頻度でカウントアップするカウンターから"時間差(経過時間)"を取得することで可能にします。

clock-1-output.gif

例えば1秒に一回カウントアップするカウンターがあるとします。
OSが起動した時カウンターが20だったとして、次に読み込んだ時に、カウンターが100だったとしましょう。そうすると 80s たったことがわかります。
実際の話はこんなに単純なものではないですが、単純化すれば時間を刻むということはこういうことになります。

clock-1-clocksource.png

つまり、timekeeping に重要なものは、カウンターです。
図で言えば、数々のハードウェアカウンター PIT, HPET, ACPI_PMT, pvclock が大事であるということです。
なお、カウンターは clocksource として抽象化されています。

様々なデバイスたち

ここまでの話から PC アーキテクチャでは、時間管理に役立つ2つのハードウェアデバイスを提供していることがわかりました。
一つがリアルタイムクロックデバイスで、もう一つがハードウェアカウンタです。

実際の動作と実装はデバイスごとに異なりますが、一般的な目的とデザインはそれぞれほぼ同じです。
現代的な OS の中で、これらの時間の関わるハードウェアデバイスに期待される動作としては、以下のようなものが考えられます。

  • (意図しない所で)決して後退しない、停止しない
  • 値がジャンプしない
  • 高解像度の適切な周波数をもつこと
  • 読み込みが高速であること
  • ユーザースペースのコードで利用できること( vDSO )

エミュレーションされたレガシーなデバイスたち

さて、PC/AT 互換のハードウェアにはこの役割を担うものとして、古くから以下のようなハードウェアカウンタとリアルタイムクロックデバイスが提供されてきました。

  • ハードウェアカウンタ
  • PIT, HPET, ACPI PM (Timer)
  • リアルタイムクロック
  • RTC

しかし、どのデバイスも先ほど紹介した特性の内の、いくつかをみたしていません。
その上、これらは、最近のハイパーバイザではすべて仮想化されており、その仮想化コストの高さから、信頼できる時刻情報提供元としては使用することができないことも知られています。

VMで実用できるデバイスたち

上記の問題を緩和できる高速、高精度のデバイスもしくは仕組みもハイパーバイザには存在します。
一つが、TSC でもう一つが pvclock です。

  • TSC: CPU に内蔵されたクロックレベルの解像度を持った 64ビットカウンターです。
  • MSR(Model Specific Registers)の一つで、1命令で取得できるので高速です。
    ユーザー空間からアクセスできます。
    周波数の決定は OS/ハイパーバイザ の実装に依存します。
  • pvclock: 仮想化ゲストのために Xen, kvm で定義されているプロトコルで、カウンタとリアルタイムクロックの役割を果たすことができます。
  • TSC を拡張し、仮想化ゲストに適するカタチにしたタイマーデバイスの取扱に関する約束ごとです。
    このプロトコルでは、ホストとゲストの間で共有される単純な CPU ごとの構造体が基礎にあります。
    Xenの場合では pvclock = xen であり、KVM の場合では pvclock = kvm-clock として発見できます。

経過時間を計測してくれるカウンタの用途として、TSC と pvclock のどちらの方が適しているかは、ハードとハイパーバイザの実装に依存します。
例えば、ハードの実装によるTSCの機能の違いにも依るでしょうし、TSC 自体がそもそもハイパーバイザのエミュレーションによって実装されていることすらもあります。

ただ、利用できるハードウェア装置のうち、多くの場合に TSC が最も優れた情報源として各所で活用されます。

ここで一つ覚えておいて欲しいことは、完全に正確なハードウェア装置が存在しないことです。
そこにはわずかにずれが生じてくることがあります。
最初に紹介したように、カウンタで計算する経過時間が OS の時刻の正体でした。つまり、TSCのずれが時刻ズレの一つの原因となります。

これだけは押さえておきたい表

Xenを利用している場合/KVMを利用している場合のそれぞれにおいて、使用が推奨されるタイマーデバイスを下記に表として整理してみました。

ハードウェアカウンタ

カウンタ用途としてオススメできるデバイス、オススメできないデバイスを下記にまとめてあります。
注: 古めと新しめの区分は曖昧なもので調査が不十分です。Xenが流行り始めた時期を考慮してXenの場合だけ記載しています。

Xen+古めのハード

Xenと古めのハードの組み合わせ

種類read?write?MSR?vDSO?おすすめ度
pvclockread onlynotnotgood
tscread & writeyesyesshould not use
acpi pmread onlynotnotnot good
hpetread onlynotnotnot good
jiffiesread & writenotnotshould not use

大事なことは、TSCがマイグレーションなどでずれたりするかもしれないので使うべきではないという点

Xen+新しめのハード

Xenと新しめのハードの組み合わせ

種類read?write?MSR?vDSO?おすすめ度
pvclockread onlynotnotgood
tscread & writeyesyesvery good
acpi pmread onlynotnotnot good
hpetread onlynotnotnot good
jiffiesread & writenotnotshould not use

ハードウェア側の仮想化機能が充実している場合には TSC はむしろ使うべきだ。

KVM

KVMではどうかについてのまとめ。

種類read?write?MSR?vDSO?おすすめ度
pvclockread onlyyesyesvery good
tscread & writeyesyesvery good
acpi pmread onlynotnotnot good
hpetread onlynotnotnot good
jiffiesread & writenotnotshould not use

pvclockとtscを同列に扱っているが、pvclockの方はプロトコルに依存するオーバーヘッドを考慮すべきかもしれない。

仮想RTC

主に仮想マシンの再起動が注意すべき点になります。

HVread?write?rebootで値は保持されるか?stop/startで値は保持されるか?
Xenread & write保持しない保持しない
KVMread & write保持する保持しない

Xenでは必ずドメインの作り直しになり、値は保持されないのが注意点。

Linuxでの確認方法

Linuxでカウンタやリアルタイムクロックデバイスが認識されているかどうか確認する方法

clocksource の確認方法

ハードウェアカウンタは clocksource フレームワークによって抽象化されていたことを思い出してください。
現在の clocksource と、他にどの clocksource を使用できるかは sysfs より確認できます。


~]$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource 
xen
~]$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
xen tsc hpet acpi_pm 

RTC の確認方法

RTCは多くの場合 _dev_rtc キャラクタデバイスとして公開されています。


~]$ ls -l /dev/rtc*
lrwxrwxrwx 1 root root      4 May 24 07:09 /dev/rtc -> rtc0
crw------- 1 root root 253, 0 May 24 07:09 /dev/rtc0

timedatectlが便利!

他、時刻情報を確認するにはtimedatectlコマンドがとても便利です。活用しましょう。


~]$ timedatectl status
      Local time: 月 2019-10-21 08:39:34 EDT
  Universal time: 月 2019-10-21 12:39:34 UTC
        RTC time: 月 2019-10-21 12:39:34
       Time zone: America/New_York (EDT, -0400)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  日 2019-03-10 01:59:59 EST
                  日 2019-03-10 03:00:00 EDT
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  日 2019-11-03 01:59:59 EDT
                  日 2019-11-03 01:00:00 EST

導入まとめ

  1. リアルタイムクロックデバイスは基本的にOS起動時のみ関係します。
  2. 起動後の時刻ずれ、時刻のタイムゾーンの問題とは基本的に無関係です。
  3. リアルタイムクロックもエミュレーションされたデバイスです。その実装上、OS での時刻ズレと同様にズレるものであると認識してください。
  4. TSC も完全なものではありません。ハードウェアのデバイスとしても周波数の変化は起こり得ます。
  5. 仮想化環境の時刻はズレるもの。ズレても激怒しない。
  6. NTPと同期しよう!

間違いがあるかもしれないので鵜呑みにしないでください。間違いがある場合にはご指摘いただけますと幸いです。

参考文献

TAGS: #kernel/clock/1