Date Tue 24 December 2013 Tags Linux / Bug

Linux の連続稼働時間が 208.5 日を過ぎた段階で突如 Kernel Panic を引き起こすという過激な挙動で2011年の年の瀬に話題となった "旧208.5日問題" ですが、あれから二年が経った今、Linux Kernel 内の bug と Intel Xeon CPU の bug の合わせ技により再度類似の不具合が発生することが分かっています。

旧 208.5 日問題の発生原理に関しては以下の blog が参考になります。
okkyの銀河制圧奇譚 : sched_clock() overflow after 208.5 days in Linux Kernel

追記(2014/1/4)


新208.5日問題の簡易チェックツールを作成しました。よろしければお使い下さい。

tsc_checker - 新208.5日問題簡易チェックツール

また、Linux Kernel における時間管理に関しては、以下の本の6章「時間管理」に詳細が載っていますので、原理・理論からおさえたい方は一読を推奨します。

影響範囲


旧 208.5 日問題の対策パッチがあたっている Linux Kernel であっても、本 208.5 日問題は異なるロジックにより発生するため、影響を受けます。

  • Red Hat Enterprise Linux 6.2, 6.3, and 6.4 (kernel 2.6.32-220, 2.6.32-279, and 2.6.32-358 series)

不具合発生条件


  1. Intel Xeon E5 シリーズのプロセッサーを利用している
  2. 上記影響範囲に記述した Linux Kernel のうち、修正パッチが当たっていない(これらのバージョンのうち現在稼働中の RHEL 及び RHEL 系サーバはほとんどが該当すると思います。)
  3. 最後に cold reboot を行ってから 208.5 日以上の時間が経過している状態で、再起動を行う

結論


  1. 不具合対象の環境は一時的な運用の見直しが必要です。サーバ再起動は cold reboot にしましょう
  2. 根本解決には、対策パッチをあてるか、kernel code を修正してリビルドしたものを組み込みましょう

※ RHEL では kernel-2.6.32-220.45.1.el6 (6.2), kernel-2.6.32-279.37.2.el6 (6.3), kernel-2.6.32-358.23.2.el6 (6.4), kernel-2.6.32-431.el6 (6.5) で修正されているようです。

旧 208.5 日問題のおさらい


旧 208.5 日問題は以下のインライン関数が原因となり発生します。CPU 内の TSC (Time Stamp Counter) の値を Linux が nano sec order の clock として利用するためのロジックです。CYCNS_SCALE_FACTOR は整数の 10 を表します。

linux-2.6.32-220(/arch/x86/include/asm/timer.h)

static inline unsigned long long __cycles_2_ns(unsigned long long cyc)
{
    int cpu = smp_processor_id();
    unsigned long long ns = per_cpu(cyc2ns_offset, cpu);
    ns += cyc * per_cpu(cyc2ns, cpu) >> CYC2NS_SCALE_FACTOR;
    return ns;
}

ご覧の通り、TSC の値を TSC * SC >> 10 することで 64 bit 変数に nano sec を代入しようとしているので、10 bit shift が行われる前の段階では pico sec order の演算を行うことになります。pico sec order の 1 秒を表現するためには、約 40 bit の空間が必要となります。変数が 64 bit であるため、 1 秒以上の表現のためには残り約 24 bit 程しかなく、208.5日問題における 208.5 日とは、この約 24 bit で表現できる日数の上限を表したものになっています。(この日数を超えると overflow が発生します。)

新 208.5 日問題の解説


Linux Kernel を起動する際、各種初期化処理を行う start_kernel()内 で x86_late_time_init() という関数が呼び出されます。

linux-2.6.32.358(/arch/x86/kernel/time.c)

static __init void x86_late_time_init(void)
{
    x86_init.timers.timer_init();
    tsc_init();
}

このうち、tsc_init() の中で、cyc2ns_offset の初期化を行います。この初期化処理は、旧 208.5 日問題と同一のロジックを経由します。Linux の起動時には、 CPU の仕様として TSC がリセットされるはずであり、Linux 起動プロセスの初期段階である本関数がコールされる時点で TSC が 208.5 日に到達しているはずがなく、優先度の低い問題として扱われていたのだと思います。

linux-2.6.32-358(/arch/x86/kernel/tsc.c)

static void set_cyc2ns_scale(unsigned long cpu_khz, int cpu)
{
    unsigned long long tsc_now, ns_now, *offset;
    unsigned long flags, *scale;

    local_irq_save(flags);
    sched_clock_idle_sleep_event();

    scale = &per_cpu(cyc2ns, cpu);
    offset = &per_cpu(cyc2ns_offset, cpu);

    rdtscll(tsc_now);
    ns_now = __cycles_2_ns(tsc_now);

    if (cpu_khz) {
        *scale = (NSEC_PER_MSEC << CYC2NS_SCALE_FACTOR)/cpu_khz;
        *offset = ns_now - (tsc_now * *scale >> CYC2NS_SCALE_FACTOR);
    }

    sched_clock_idle_wakeup_event(0);
    local_irq_restore(flags);
}

しかし、時を同じくして、Intel Xeon Processor E5 Family は以下の不具合を抱えていました。本来リセットされるはずの TSC の値が、 Warm Reset (通常の再起動)時に初期化されないという不具合です。サーバの電源断を経由しない限り、 E5 Family Processor の TSC の値は保持され続けます。

BT81. X X X No Fix TSC is Not Affected by Warm Reset
Intel® Xeon® Processor E5 Family

つまり、新 208.5 日問題は、最後に電源停止を行ってから208.5日程度経過した段階で、再起動をした際に前述の初期化処理において offset 計算が overflow することで顕在化します。

旧 208.5 日問題とは異なり、稼働中の OS では発生しませんが、再起動時に突然ハング状態になるため、対策パッチをあてるか、tsc.c & kernel.h を自前で修正してリビルドするか、運用プロセスを一時的に変更する必要があると思います。kexec時も同様の問題が起きるはずですので、ご注意ください。

記事に間違いや不明な点がありましたら、ご指摘下さい。

参考URL



Comments

comments powered by Disqus