« リサイクルは善or悪? | Home | 積雪7.9cm也 »

February 3, 2008

gettimeofdayがマイクロ秒オーダー精度がある理由(未完)

PHOTO:Clock

 めずらしく技術的な話である。おそらくnoisefactoryの常連読者の皆様のほとんどが興味がないと思われる。気にしないように。

 naoya氏のblogエントリLinux のスリープ処理、タイマ処理の詳細を見るにインスパイアされて、Linuxのシステムコールgettimeofday(2)がマイクロ秒の精度がある理由を調べてみた。 ナゼこんなことに興味があるかというと、時刻を監理しているticsという変数は、(デフォルトでは)4ms毎に更新される。 にもかかわらず、gettimeofdayがその1000倍以上の精度を持っているのは非常に不思議だ。

ハード屋なのかソフト屋なのか学者なのかよくわからない人代表としては、ハード側から攻めて見る事にした。 そもそもCPUにはclockというピンがあり、CPUのペリフェラルにはタイマカウンタがある。 詳細Linuxカーネルの6章によると、tsc(Time Stamp Counter)というカウンタがあるという。おそらくここに到達するだろうということで lxrで追って見る事にした。この記事では、Linux 2.6.11 i386のソースを用いることにする。

詳細Linuxカーネルによるとtscを読み出すアセンブリ言語命令はrdtscであるとのことであるから、それで検索するとinclude/asm-i386/msr.hにドンピシャのコードがある

static inline unsigned long long native_read_tsc(void)
{
    unsigned long long val;
    asm volatile("rdtsc" : "=A" (val));
    return val;
}

naive_read_tscは、マクロでrdtscllというマクロから呼ばれ、以下のようにclocksource構造体のグローバル変数clocksource_tscの readメンバにポインタが代入されている。

static struct clocksource clocksource_tsc = {
    .name = "tsc",
    .rating = 300,
    .read = read_tsc,
    .mask = CLOCKSOURCE_MASK(64),
    .mult = 0, /* to be set */
    .shift = 22,
    .flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_MUST_VERIFY,
};

さて、ここで行き詰まってしまった。ではgettimeofdayから掘り進んで行こう。gettimeofdayはシステムコールなので、gettimeofdayが呼ばれると ソフトウェア割り込みのハンドラsys_gettimeofdayが呼ばれる。

asmlinkage long sys_gettimeofday(struct timeval __user *tv, struct timezone __user *tz)
{
  if (likely(tv != NULL)) {
  struct timeval ktv;
  do_gettimeofday(&ktv);
  if (copy_to_user(tv, &ktv, sizeof(ktv)))
    return -EFAULT;
  }
  if (unlikely(tz != NULL)) {
    if (copy_to_user(tz, &sys_tz, sizeof(sys_tz)))
      return -EFAULT;
  }
  return 0;
}

sys_gettimeofdayはdo_gettimeofdayで時刻を取得している。

/**
* do_gettimeofday - Returns the time of day in a timeval
* @tv: pointer to the timeval to be set
*
* NOTE: Users should be converted to using get_realtime_clock_ts()
*/
void do_gettimeofday(struct timeval *tv)
{
  struct timespec now;

  __get_realtime_clock_ts(&now);
  tv->tv_sec = now.tv_sec;
  tv->tv_usec = now.tv_nsec/1000;
}

do_gettimeofdayは__get_realtime_clock_tsから時刻を取得し、なんとナノ秒オーダの時刻を取得しておきながら、 ここでマイクロ秒に丸めている。

/**
* __get_realtime_clock_ts - Returns the time of day in a timespec
* @ts: pointer to the timespec to be set
*
* Returns the time of day in a timespec. Used by
* do_gettimeofday() and get_realtime_clock_ts().
*/
static inline void __get_realtime_clock_ts(struct timespec *ts)
{
    unsigned long seq;
    s64 nsecs;
    do {
        seq = read_seqbegin(&xtime_lock);

        *ts = xtime;
        nsecs = __get_nsec_offset();

    } while (read_seqretry(&xtime_lock, seq));

    timespec_add_ns(ts, nsecs);
}

do_gettimeofdayは、xtime_lockを取得しxtimeで時刻を取得している。xtimeは詳細Linuxカーネル6.2.1.3によると、xtime変数はtimespec構造体で みなさまおなじみ1970年元日の0:0:0(UTC)からの秒数とナノ秒精度の秒数を格納している。これは最後にタイマ割り込みが入った時刻である。 タイマ割り込みはミリ秒オーダの精度しかないので、nsecのケタは__get_nsec_offsetから取得し補完している。で、__get_nsec_offsetは

/**
* __get_nsec_offset - Returns nanoseconds since last call to periodic_hook
*
* private function, must hold xtime_lock lock when being
* called. Returns the number of nanoseconds since the
* last call to update_wall_time() (adjusted by NTP scaling)
*/
static inline s64 __get_nsec_offset(void)
{
  cycle_t cycle_now, cycle_delta;
  s64 ns_offset;

  /* read clocksource: */
  cycle_now = clocksource_read(clock);

  /* calculate the delta since the last update_wall_time: */
  cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;

  /* convert to nanoseconds: */
  ns_offset = cyc2ns(clock, cycle_delta);

  return ns_offset;
}

現在のクロックカウントを取得し、前回のタイマ割り込み時のクロックカウントとの差を計算して クロックカウントをナノ秒に変換している。現在のクロックカウントはclocksource_readで取得している。 引数のclockはclocksource型ポインタのグローバル変数である。おっとclocksource型ってのはさっきのclocksource_tscの型と同じだ。 わくわくしてきた気持ちを抑えて、clocksource_readを見てみる。

/**
* clocksource_read: - Access the clocksource's current cycle value
* @cs: pointer to clocksource being read
*
* Uses the clocksource to return the current cycle_t value
*/
static inline cycle_t clocksource_read(struct clocksource *cs)
{
  return cs->read();
}

 すばらしい。グローバル変数clockが、clocksource_tscのアドレスが代入されていれば、native_read_tsc()が呼ばれ tscからクロックカウントを取得する。では、kernelの初期化ルーチンstart_kernelを見てみると、time_init()を呼んでおり time_init()がtsc_init()を呼ぶ。tsc_init()にはclocksource_register(&clocksource_tsc)という行がある。どうやら clocksourceのlinked-listに入れているようだ。

 ここまで追ってきて、また行き詰まってしまった。詳細Linuxカーネルによると1usec以上の精度を持ったタイマはtscだけではないようだ。 HPET(High Precision Event Timer)やACPI PMT(ACPI Power Management Timer)等があり、HPETは10MHz以上の周波数を持ち ACPI PMTは3.58MHzの周波数を持つ。これらの周期はもちろん1マイクロ秒以下である。本はちゃんと読まないといけないなぁ。 これらのタイマがどのように選ばれるのか、なんとなくわかったので、もう少し調べてまた書く事にしよう。 この記事を書くために調べた過程で、tscからナノ秒に変換するところとか、他に面白そうなことがありそうだ。 これらも後日にする。

実を言うとここまで真面目にLinux kernel sourceを読んだのは初めてだ。今回は見事にkernelの森に踏み込んで迷子に なってしまった。だが非常に楽しい。どうやら私の心はkernelの森の魅力に捕われてしまったようだ。

No TrackBacks

TrackBack URL: http://www.argv.org/~chome/blog/mt-tb.cgi/132

About this Entry

This page contains a single entry by chomy published on February 3, 2008 3:25 AM.

リサイクルは善or悪? was the previous entry in this blog.

積雪7.9cm也 is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.