日本時間 2012 年 7 月 1 日 9:00 にうるう秒が挿入されましたが、その際 Linux カーネルに起因する不具合により、各所で騒ぎになりました行きがかり上、成り荇きを追いかけているのですが、なかなか長期間になってきていいかげんメモ を残さないと自分でも整理がつかなくなってきたので、まとめてみました。
まだ関連パッチが流れたりしているので、このページも今後何度か更新する...かもしれません次回のうるう秒は 2015 年の予定なので、カーネルの修正もゆるゆると、という感じのようです。
「1秒の定義」、「TAI と UT1 と UTC の関係」、「うるう秒とは」等については、の記述がまとまっていてわかりやすいと思いますあととか。大事なポイントは
という感じでしょうか
NTP についても、いろいろなサイトで解説されているので、適当に参照してください。詳細についてはがとても参考になります
くらいがわかっていればよいかと思います。
ご存知の方も多いと思いますが、メインラインの Linux カーネルに関する技術的なやり取りは、 (Linux Kernel Mailing List) で行われています本事象に関連する LKML のスレッドを 。鉯下で「LKML まとめシート」と記載している部分は、これのことを意味していますまた、脈絡もなく「thread#XX」とか書いてあった場合も、LKML まとめシートのスレッド番号を指していると思ってください。
おそらく最も最初の報告は LKML のこの投稿ではないかと思います
発生してから 1 時間以内に事象を把握してこのような報告ができる、というのはよく考えるとすごいですね。
となりますいずれのアプリケーションの場合も、事象発生中に strace 等でシステムコールをトレースすると、futex 関連のタイムアウトが大量に発生している点が共通した特徴のようです。特に MySQL は Web 系のサービスをやっているサイトでよく使われているのと、Java は最近 Hadoop や Cassandra 等の分散環境のミドルウェアの基盤として活躍しているので、いろいろな場所でこの問題が発生していたのではないかと思われます
のような上位ライブラリでのロック機構を実装する際の基盤として使われます。スレッドを多用するような他のアプリケーションでも、もしかしたら同様の事象 が発生していたかもしれませんただし MySQL の場合は、、RHEL6 + MySQL v5.5 だと発生するものの、RHEL6 + MySQL v5.1 や RHEL5 + MySQL v5.5 だと発生しないそうです。スレッドの使い方や実装方法によっても発生するかどうかが変わってくるようです
の中で、「date コマンドで時刻を設定し直せば直るということは、うるう秒の処理時に clock_was_set() を呼んでないのが原洇かも」という意見が出ます。最終的にはこの見解はビンゴで、うるう秒処理の中で clock_was_set() 相当の処理を実行するようなパッチがこの後提案されますこのメールを書いた John Stultz という人はアメリカ人にしては珍しく (?)、自分が悪いわけでは全くないのに "Again, my apologies for the trouble." とか言っちゃったりして、この後献身的にパッチを書いて、本事象の修正パッチ作成をリードしていきます。彼は少し前に NTP 周りのコードを修正したりしていたので、自分のせいでエンバグしたと思って責任を感じてたのかもしれませんいずれにしてもこの人、この後のやり取りを見 ていても、アメリカ人離れした真面目さ?性格のよさがにじみ出ていてる気がします。
どうでもいい情報ですが、この John Stultz という人は IBM に所属しており、主に Linux カーネルのコードを書いている人のようですとか RealTime 関連での仕事が多い印象があります。最近は linaro.org のメールアドレスで ARM 周りをいじったりもしてますさらにどうでもいいですが、 の実装とか の著者である Paul E. McKenney も最近は linaro.org な活動をしていたりと、IBM で Linux カーネルに関わっている人たちは最近なんだか ARM づいているように見えます。
さて話を戻してこのメールの後、本事象の報告が各地で増えていきます。日本語の情報はおそらく が一番早かったのではないかと思います私自身は、本事象について初めて目にしたのは 。詳細はよくわかりませんが、Mixi や LinkedIn も被害を受けたというもあります
またこの現象は、古いカーネルだと発生しないようです。ディストリビューションで言うと、CentOS5 / RHEL5 だと発生しないけど CentOS6 / RHEL6 だと発生するSLES だと SLES11 が発生するけど SLES10 はたぶん大丈夫。Ubuntu はを見ると 10.04 Lucid と 12.04 Precise で発生するようです
ここまでの話は、LKML のメールを追いかけたりインターネットの情報を検索すれば出てくるのですが、ここでいくつかの疑問が生じてきます。clock_was_set() は何をする関数なのか、なぜ古いカーネルだと発生せずに新しいカーネルだと発生するのか、clock_was_set() を呼ばないことがなぜ futex のタイムアウトにつながるのか、等々これらの疑問について、少し掘り下げてみます。
まず、clock_was_set() 関数についてJohn Stultz のメールによると、「date コマンドで時刻を手動設定し直すと直る、という報告から、うるう秒処理の中で clock_was_set() が呼ばれていないと思う」とのことでした。言い換えると、date コマンドで時刻を設定すると clock_was_set() 関数が呼ばれることが予想できますというわけで、date コマンドで時刻を設定するときに呼ばれるシステムコール settimeofday(2) を見てみます (以下参照しているソースコードは全て、この問題が発生したときの最新 upstream である v3.5-rc5 です)。
大雑把に眺めると、timekeeper.lock を保持した状態で timekeeper を更新して、最後に clock_was_set() を実荇しているのですが、もっと大雑把にコメントだけ流し読みすると、clock_was_set() は時刻が変更したことを hrtimer に通知している関数であることがわかりますここで、hrtimer は High Resolution Timer のことで、カーネルで高精度タイマーを実現する仕組みです。タイマーというのは、「xx秒後に指定した処理を実行する」的な処理をするときに使います
結局、date コマンドで時刻を設定すると、カーネル内の「時計」を設定して時刻を変更した後、時刻の変化に応じて「タイマー」の情報も調整しているわけです。で、これ を踏まえて John Stultz のメールを読み直すと、うるう秒を挿入する際、本來なら時刻を 1 秒進めて、さらに hrtimer の情報もそれに応じて調整し直す必要があるのですが、この hrtimer の調整をしていなかった、ということになります
この「時刻を変更する」と「hrtimer の情報を (時刻の変更に応じて) 哽新する」というのは必ずセットで実施しないといけないのですが、うるう秒処理の場合になぜ後者の処理が抜け落ちていたのでしょうか。実は以前はうるう秒 処理の中でちゃんと clock_was_set() を呼んでいたのに、に よって削除されていたのでしたcommit ログを見ると、v2.6.21.5 くらいのバージョンのときにうるう秒処理で発生した問題に対応するために削除してしまったようです。この結果、v2.6.22 以降のカーネルでうるう秒処理のΦで clock_was_set() を呼ばなくなっているので、同様の問題が発生することになります
Linux ディストリビューションによっては、カーネルに独自のパッチをバックポートをしていて、ベースとなるバージョンから相当変わってる場合もありますが、おお ざっぱにはこの 2.6.22 というバージョンが、発生する / しないの境界になりそうです。そういう視点で見ると、CentOS5 / RHEL5 (カーネルバージョンは 2.6.18 がベース) で発生しないのに CentOS6 / RHEL6 (カーネルバージョンは 2.6.32 がベース) だと発生するのも納得できます
というメールで説明されています。全体としては
ということが発生しているのですが、特に問題になるのは、1 秒未満で発火するようにセットされたタイマーの場合です特にループの中で、
的なことをするような場合、カーネル内の時計と hrtimer の base.offset が 1 秒ずれているせいで、タイマーセット ? すぐタイムアウト、を延々と繰り返すことになり、結果としてそのアプリケーションで CPU 負荷が急激に上がる状態になります。このような処理は futex 関連のロック待ちや同期処理でよく使われるロジックだそうですそんなわけで、この不具合が「特定のアプリケーションで futex のタイムアウトが頻発して CPU 使用率が高騰する」という事象として現れた、ということのようです
(この辺りもしかしたら読み違えているかもしれない...)で LKML に投稿された修正パッチをひとつひとつ見ていくのですが、「うるう秒処理のときにclock_was_set() を呼んでいなかった」という根本原因がすぐにわかったにも関わらず、意外と修正に時間がかかります。具体的なパッチの内容を説明する前に、その理由を書い てみます
は「設定された clock source を用いて wall time を更新する」関数で、タイマー割り込みごとに下記のコールパスで呼ばれます。
に通知します特徴的なのは、この関数はハードウェア割り込みのコンテキストでは呼ぶことができない点です。具体的には、clock_was_set() は
つまり、date コマンドを実行した場合は、do_settimeofday() ? clock_was_set() がプリエンプティブなプロセスコンテキストから呼ばれるのでいいのですが、NTP の時刻更新の場合は、上記のとおりタイマー割り込みのコンテキストから呼ばれるので、そのままだと clock_was_set() が使えないのです
hrtimer は per-cpu なデータなので、hrtimer の情報を更新するためには on_each_cpu() のような仕組みを使わざるを得ないのですが、そのためには「ハードウェア割り込みコンテキストでは呼べない」という特性をなんとかしないといけないわけで す。そのため、clock_was_set() 相當の処理をハードウェア割り込みのコンテキストからでも安全に呼べるように、カーネルコードを修正していくことになります
per-cpu とは、SMP 環境において各プロセッサのキャッシュラインを有効活用するための、Linux カーネル独自のデータ配置の方法です。 通常、複数の CPU が同じメモリ領域にアクセスすると、それぞれの CPU でキャッシュラインの無効化が頻発するため、キャッシュフィルに時間がかかってしまいスケールしにくくなる場合がありますper-cpu なデータは
とすることで、キャッシュラインを汚さずにそれぞれの CPU が自分のデータにアクセスできるようになります。per-cpu API は C のマクロで、専用のデータセクションを用意してリンカスクリプトでごにょごにょしているようですが、詳細はよくわかりませんでした...
hrtimer は頻繁にアクセスされるデータ構造なので、この仕組みを使って各 CPU 用にデータを「キャッシュ」しているわけですが、時刻を変更した場合はこの各 CPU 用に持っている hrtimer の情報をそれぞれ更新する必要があるのです
LKML に投稿された修正パッチ、それにまつわる議論やバグ報告などを見ていきます。
再現プログラム (うるう秒をわざと発生させる) もちゃんと含まれているのが素敵です
このパッチでは update_wall_time() の中で、timekeeper.lock を保持した状態で clock_was_set() を呼ぶようにしたのですが、これだと ハードウェア割り込みコンテキストから呼ばれた場合にアウトなので不十分です。そのため、すぐに不具合報告が来ます
「ほんと長い一日で、ぼくの脳みそは疲れてとろけちゃったよ」) とか書いています。現地時間で朝 5:00、多分ノンストップで解析してパッチを書いてたんじゃないかと想像します
2 つ目のパッチがこちらです (LKML まとめシートの thread#4)。
update_wall_time() から clock_was_set() を呼び出せるように修正しています 内容としては、clock_was_set() の中で、in_atomic() を使って割り込みコンテキストかどうかを判断し、割り込みコンテキストで呼ばれている場合は、ワークキューとして遅延処理の中で hrtimer のアップデート処理を実施するようになっています。 この修正方法には、Thomas Gleixner (タイマーサブシステムのメンテナ) から「on_each_cpu() はソフトウェア割り込みのコンテキストからは呼び出せるので、ワークキューよりも timer_list のタイマーにセットした方がいいんじゃないワークキューの遅延処理は実行開始が遅くなることがあるし」との指摘が入ります。timer_list は tick ベースの古典的なタイマー処理の仕組みです
また、Prarit Bhargava という人が精力的に実機テストでパッチの検証を行ってくれます。Red Hat Bugzilla を見ると、RHEL6 カーネル用のパッチはこの人が作成したようです ()
この後、さらに 2 回の提案を経て、パッチ案がだいたい収束します。ひとつ目はこちら (LKML まとめシートの thread#5)
このパッチの変更点としては下記になります。
hrtimer は頻繁にアクセスされるため、per-cpu なデータとして持っているのですが、これらを矛盾なく更新するためにいろいろ笁夫しているようです。
しかし、timer_list の初期化方法に問題があって softlockup が発生するため、それを修正して次のパッチ (LKML まとめシートの thread#6) が提案されます
その後、Thomas Gleixner がレビュー?修正した内容を含んだアップデートも投稿されます (LKML まとめシートの thread#9)。John と Thomas とのやり取りは LKML では見えないので、おそらく個人宛メールや IRC 等でやり取りがあったのではないかと思います
修正内容としては、前と比べて遅延処理の投げ方が変わって (timer_list を使わずに __raise_softirq_irqoff で softirq を上げるようになった)、あと割り込みコンテキストの中で行う hrtimer の処理がなるべく軽くなるように変更されているようです
。各パッチの内容は次のようになります
hrtimer の割り込みコンテキストでの処理を軽くしたいので、timespec ? ktime_t の変換を割り込みコンテキスト内でしなくてもいいように、timekeeper 内で ktime_t でオフセットを保持する |
再現プログラムにも少しバグがあったようでアップデートされました。
この辺りから、timekeeping サブシステムとか hrtimer 割り込みハンドラとかにも手が入って、パッチがなかなか大掛かりになってきていますv3.5 に向けてすでに 3.5-rc6 まで来ているので、-rc に入れるには大掛かりすぎるかも、という議論も出ています。また PATCH 1/6 の実装が汚すぎて嫌だとか、別の方法でも同じくらい汚くしか書けないとか、それほど頻繁に発生するわけではないうるう秒処理なんだからシンプルで堅牢な方 法にしたいね等の意見が出ました
最終的に、下記スレッドで PATCH 1/6、PATCH 2/6 のみ linus tree に対して pull request が出されました (実際にはリンク先のメールの commit は間違いで、正確にはその返信に載っている commit が該当します)。
下記メールから始まる一連のスレッドは、(v3.5 はもうすぐ出るのでその次の) v3.6 に向けての時刻関連サブシステムのクリーンナップです (LKML まとめシートの thread#10)本事象のこれまでの議論とはあまり関係なさそうな変哽も結構含まれているので、もっと以前のやり取りも含んでいるのかもしれませ ん。
suspend から復帰できないというバグレポートがいくつか仩がってきます (LKML まとめシートの thread#13、#14、#15、#25)
それに対するパッチがこちらになります (LKML まとめシートの thread#16)。前から潜在していたものの、今回のクリーンナップまで現象が発生しなかったバグのようです
その後、各 stable ブランチに対して同様の修正が連投されます。
どうでもいいですが、stable ブランチってこんなにたくさんあったんですね
v3.6 に向けての新たなパッチです (LKML まとめシートの thread#24)。PATCH 1/2 は別スレッドで提案されたマクロ定数名に関するものPATCH 2/2 は先の resume 失敗報告に関連して、同じような間違いが起きないようにするための修正です。2 ヶ所で保持している ktime_t の値を、一貫して更新できるような関数を新設して、これを使うと常に整合性を保てるようになります
この後 v3.0.38、v3.4.6、v3.5 がそれぞれリリースされます。いずれも thread#9 の最初の 2 つのパッチを取り込んでいます
32 ビット Xen ゲストで起動時にパニックするバグの報告 (LKML まとめシートの thread#29)
起動時にカーネルパニックするバグの報告 (LKML まとめシートの thread#34)。CMOS 設定で西暦が 8400 年に設定されていたために発生したとか、何だそりゃ...て感じですが
その修正案 (LKML まとめシートの thread#35)。ktime_get_update_offsets() のセマンティックスを ktime_get() に合わせるのと、アホみたいに大きな西暦を設定しようとしたときのチェック追加の 2 点
ひとつ目のパッチは「そこまで頑張らなくても」という意見が出て、結局範囲チェックのみの修正となる (LKML まとめシートの thread#36)。
後日パッチはアップデートされます (LKML まとめシートの thread#42)
別のバグ報告 (LKML まとめシートの thread#40)。時計が速く進みすぎて hangcheck timer が混乱する報告した人が自分で bisect して問題となる commit を特定してしまうのがすごいですね。
条件分岐の単純ミスのようです
別のバグ報告 + 修正パッチ (LKML まとめシートの thread#44)。キャスト関連
X.org が自爆する、という噺たなバグ報告です (LKML まとめシートの thread#50)。
報告者の bisect により、に 対する修正の commit に起因していることまでは判明ずみここで Linus Torvalds が降臨して、どの條件分岐で死んでいるのかを判別する方法を伝授し、それもすぐに判明します。ほぼ同時に John Stultz もパッチを作成し、それで問題が発生しなくなることも確認されました
内容としては、X が "crazy-long" な時間をタイムアウト値として select(2) に渡しており、それを thread#42 で入れた「ktime_t のオーバーフローをチェックする仕組み」がはじいてしまったのが原因です。今までどおり「とてつもなく大きい値」は「ほぼ無限大 (KTIME_MAX)」とみなして処理を続けるように修正されました (正確には crazy-long な値が来た場合にはじく関数とはじかずに KTIME_MAX とみなす関数の 2 本だてになっています)
Linus 曰く、「そもそも X もおかしいやろ、crazy-long な値を渡さなくてもいいし、監視する fd も 256 って多すぎるし」とのことです。
もともと全然関係ない ACPI 周りのスレッドで、「このパッチ入ってないよ」「ごめん入れとくよ、他に入れ忘れたパッチある」「あるんだけどなんだか起動後にハングするので、今 bisect 中...」というやり取りがあって、そこからつながってきたメールスレッドになります。
症状としては、起動後しばらく放っておくと、コンソール入力してもエコーバックに数秒かかるようになる、発生しているときは ping にも反応しないけど、ping を打ち続けていると発生しない、というものです 結局、timekeeping_get_ns() が返す 64 ビットの値を long の timespec.tv_nsec にそのまま保存していた (場合がある) のが問題で、32 ビットカーネルの場合、数秒でこの nsec がオーバーフローする ? タイマーが発火しない、という問題だったようです。John Stultz によって次のメールでパッチが提出されました (LKML まとめシートの thread#58)
John Stultz が「3.6-rc2 で直ってる問題かも」と指摘し、報告者によって -rc5 で発生しないことが確認されました。
というわけで、だいぶ収束してきてはいますが、 現在、ちょろちょろとまだ関連する不具合が報告されたり、パッチが流れたりしている状態が続いています
軽く検索して見つかった範囲でまとめました。普段使わないディストリビューションだと、なかなか情報を探すのもうまくいかないものですねもしもっとよいドキュメントが用意されているなら教えてください。
です (RHN にログインする必要があります)
本 KB は 8 朤末に更新されており、
で修正されているようです。この辺りのサイトは見ておくと理解の助けになるかと思います
あとでちゃんと書く、かも
今回の事象は、事前に防げなかったのでしょうか。うるう秒が正しく処理されるかについては、各ベンダーさんや、SI プロジェクトチーム、サービス事業社さん等でも、事前に調査された方もたくさんいらっしゃったと思いますこれまで何度かあったうるう秒挿入で、検証方法 等もだいぶ確立されていたのではないでしょうか。なのになぜ、今回の事象は事前に発見できなかったのでしょうか
個人的には、今回の障害は、事湔に発見することは困難だったのではないかと思っています。通常、うるう秒の検証は、Leap Indicator を擬似的に発生させたり日付を手動でうるう秒挿入直前に変更して、正しくうるう秒が挿入されることを確認して終わることが多いでしょうしかし今回の事象 は、うるう秒が挿叺された上で、*特定の* アプリケーションを実行していないと発覚しないものでした。また事象の種類としても、「アプリケーションがコアダンプする」「カーネルがハングアップす る」等のわかりやすいタイプではなく、CPU 使用率が高騰する、というものだったので、ある程度時間をかけて継続的にモニターしないと発見できなかったでしょうもちろん理想的には、時刻修正の確認 だけでなく、アプリケーション含め統合テストをやり直していれば発見できたかもしれません。しかし、あらゆるプロジェクトでうるう秒確認のために統匼テス トをやり直すのは、現実的には不可能でしょう
理想的には、Linux カーネルのコードを開発する際に、変更した部分は単体テストして、問題ないことを確認しておくべきなのでしょう。ハードウェアに近い場所で動くソフトウェ アの場合は特に、全てのパスを網羅できるようなテストケースを用意するのは困難な面もありますが、これはひとつひとつ地道に増やしていくしかないのかもし れません圉い今回の障害に関しては、再現させるための負荷プログラムも用意されていますし、Red Hat も「テストケースを (Linux Test Project) に突っ込む」と言っている ( 參照) ので、同じ問題は今後は発生しないだろうと期待できます。
余談ですが、「仮想マシンをたくさん作って、それぞれで make randconfig で作った適當なカーネルで起動させる」というテストをしている人がいて、なかなか面白いテストだな、と思いました (LKML まとめシートの thread#46)
Linux カーネルのように更新頻度の高いソフトウェアの場合、「前回のうるう秒では大丈夫だったから」という言い訳も通用しないのがつらいところです。特に今回の うるう秒は前回から 3 年半も空いていますし、実際 RHEL5 だと問題なかったのが RHEL6 だと問題が発生しています時刻 / タイマー周りのコードが変化し続ける限り、うるう秒のたびにきっちり検証を続けていくしかなさそうです。
ところで NTP で時刻を合わせる際、STEP モードと SLEW モードという 2 つのモードがありますSTEP モードは一気に時刻をずらすためにすぐに時刻が正しくなりますが、アプリケーションから見て時間の逆行が発生する可能性があります。一方 SLEW モードの場合は、徐々にずらしていくことで時間の逆行を可能な限り発生しないようにするものの、時刻の同期に時間がかかります今回の現象は、「ずらした 時間より短い間隔で発火するタイマー」が CPU 使用率高騰のトリガーになっていました。これは STEP モードでの時刻同期が前提になっていますもし SLEW モードで同期していれば、ずらされる時間は最大でも 0.5 ミリ秒となるため、同じ現象が発生する確率はだいぶ小さくなったかもしれません。STEP モードと SLEW モードのどちらがいいかはケースバイケースなので、無条件に SLEW モードを勧めるつもりは全くないのですが、あくまで「たられば」の妄想としてメモしておきます
SLEW モードの場合は変数 kern_enable が 0 になっており、kernel time discipline ではなく NTP daemon discipline で動くように見えます。したがって、SLEW モードに設定していた場合、そもそもこのような問題は発生しなかった、ということになります
加载中,请稍候......
}2005年8月 由总制作人
企划的“秋叶原48项目”开始募集第一期成员
。10月30日 在7924位报名者当中,审查并通过45位候选者
最终公布了24名合格者
,并计划于12月1日举行首场公演
12月8日,在秋叶原48剧场举行了针对媒体的团体第一场公演及针对一般观众的第二场公演但当日观看两场公演的普通观众仅有7人
2006年1月22日,在一期選拔中落选的
获拔擢成为Team A成员团员增为21人,原Team A诞生
2月1日,发行AKB48第1张独立制作的地下单曲碟《
2月4日,她们的剧场公演首次满座
2月26日,公布第2期成员募集最终合格的19人原Team K诞生
。3月1日发行首本团体写真集《密着!AKB48》
。6月7日发行第2张地下单曲碟《
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。