大原雄介の半導体業界こぼれ話
新命令FREDの導入で、消滅するx86のRing 1/2特権
2023年12月27日 06:12
なんか毎月Intelの話を書いてる気がするのだが、今月もIntelの話。ただちょっと(だいぶ?)先の話である。
つい先日、IntelがArchitecture Instruction Set Extensions and Future Features Programming Referenceを改定した。現時点での最新バージョンは319433-051である。
ちなみに前バージョンの319433-050は今年(2023年)9月にリリースされていた。これのリビジョン履歴を見ると、ついにCPUIDにFRED(Flexible Return and Event Delivery)対応のビットが追加されたことが明らかになった(図1)。
ではこのFRED関連命令とかはどこの世代で搭載されるのか? というのがこちら(図2)で、Panther Lake(図3)とClearwater Forest(図4)世代からとなる。要するに2024年後半あたりからFREDが利用できるようになるので、アプリケーション側で対応してね、というわけだ。
もっともこれ、本当に2024年か? というとちょっと怪しい気もする。新しい命令セットの詳細を公開して、そこから1年で対応プロセッサが出るというのはちょっと急すぎるからだ。図3にはPanther Lakeが2025年になる前に投入されるように描かれているが、実質2025年以降と考えた方が自然である。
そもそもFREDとは
まぁそんな対応CPUがいつ投入されるのかというのは今回の本題ではなく、本題はタイトルにも書いたFREDである。この仕様が最初にリリースされたのは2021年3月のことであり、その後煩雑にリビジョンを繰り返して今月には6.1が上がっている。
そもそもFREDは何か? というと、あまり一般プログラマーには関係ない部分であるが、「特権レベルの変更に伴うオーバーヘッドを削減するための新命令群」である。
x86で言えば、80286の時にRing 0~3という4種類の特権レベルが導入され、OSは一番特権レベルの高いRing 0で動作、アプリケーションは一番特権レベルの低いRing 3で動作するようになった、という話を聞いたことがある方は多いと思う。
この辺は最終的にOSの作り方にも依存するわけだが、基本的にCPUのハードウェアを直接操作できるのは、Ring 0で動いている時だけである。だから、たとえばキーボードのLEDを光らせたいと思ったら、Ring 3で動いているプログラムはOSが提供しているサービス(WindowsだとSetupAPI.libで提供されているAPIを組み合わせる格好になるだろう)を使ってキーボードデバイスを特定し、そのキーボードのLEDを点灯させるリクエストをOSに送る。このリクエストを受けたOSは、Ring 0で動作するデバイスドライバにそのリクエストを渡し、デバイスドライバが実際にキーボードがつながっているデバイス(昔ならPS/2コネクタだが、今ならUSBコントローラだろう)にリクエストを出す、という格好だ。
Arduinoと違い、直接Ring 3で動いているプログラムはハードウェアから触れないようになっているわけだ。
そもそも、なんでx86には4レベルの特権があるのか?
ちょっと脇道に逸れるが、そもそもなんで80286は4レベルの特権メカニズムを実装したのか? という話は公式には不明(Intelがそれを明確に説明したことはなかったと思う。パット・ゲルシンガー氏あたりに聞けば覚えているだろうか?)である。実際この4レベルの特権メカニズム、ほとんどのドキュメントが「普通はRing 0と3だけが使われる」くらいの説明に留まっている。
で、確証はないのだが、多分これDECが「VAX」で実装した保護メカニズムをそのまま持ち込んだのだと筆者は考えている。VAXは「VMS」というOSを組み合わせると、本当に4レベルの特権をフルに使うのだ(図5)。
VAXの場合、K(Kernel)/E(Executive)/S(Supervisor)/U(User)という名称で、これがRing 0~3にそのまま該当する格好だ。KはVMSのKernelが動作するモード、Eはその外側でRMS(Record Management Service:ファイルシステムを扱う)やI/Oなどの管理を行なうモード、SはDCLと呼ばれるコマンドシェルが動作するモード、Uがユーザープログラムとなる。
このVMSは、DECがVAX-11をリリースした1977年に合わせてリリースされており、いろいろな競合メーカーに影響を与えている。ちなみにVMSの元になったのは、PDP-11上で動いていた「RSX-11M」で、ここにも特権の概念はあるのだが、VMSほど複雑ではない。おそらくRSX-11MをVMSに拡張するにあたって、担当者(言うまでもなくデヴィッド・カトラー氏である)が新たに盛り込んだのだろう。
これも筆者の推察だが、80286を設計するにあたって、「本格的なOSに対応する」ことを目論んだものの、ではその本格的なOSには何が求められるのか? はまだ手探りだった時代だ(IBMのSystem/370上で動いていた「VS1」や「VS2」などは、根本的にアーキテクチャが違うのであまり参考にならなかっただろう)。VAX-11とVMSの組み合わせは、技術的なヒントを得るのにちょうど適切だっただろうと思う。
もっとも、この4レベルの特権メカニズムが本当に必要だったか? というとこれはちょっと怪しい。同じVAXで動くUNIX(元々のUNIX/32Vとかその後継、BSD系に加え、DEC自身Ultrixという名称でUNIXをポーティングした)は、「Kernel Mode」と「User Mode」しか使っていない。
またカトラー氏はMicrosoftに移籍後、「Windows NT」を開発したのもご存じの通り。Inside Windows NT(その後Windows Internalsに改称した。日本語版だとインサイドWindowsである)を、先ほどのIDSと見比べながら読み解くと、結構OSの中身がVMSとよく似ている(そりゃまぁ開発者が一緒なんだから当然だ)ことに驚くが、それでも4レベルの特権メカニズムを持ってくることはせず、Ring 0と3だけでWindows NTを実装している。やっぱり4レベルの特権メカニズムは過剰だったのだろう、と思わなくもない。
ただ、2000年台になって仮想化というかハイパーバイザが本格的に利用されるようになると、OSとハイパーバイザが同一特権レベルで動作するのは危険、という認識になり、なので、ハイパーバイザだけをRing 0で動かし、OS KernelはRing 1で動かすという技法が採用されるようになった。
ところが、OSのコードの中には、Ring 0で動作することを前提とした部分が少なくない。具体的には、CS(Code Segment)レジスタの扱い(CSの中には、どの特権レベルで動作しているかの情報も入るので、Ring 1で動かすと値が変わってしまう)とかPOPF命令(POP Flag命令:CPUのフラグレジスタを復元する。なぜかこれは特権命令ではないため、特権レベルで保護できない)などいくつかの課題があり、これを解決するためにバイナリ変換(問題のある命令だけをほかの命令に置き換える)をハイパーバイザで動かすという、割と力業の実装が行なわれていた。
もちろんこれは昔の話で、今は仮想化支援機能(IntelのVT-xとかAMDのAMD-V)があるので話はもっと楽になっているのだが。
だからこそ活きるFREDの価値
話を戻すと、そんなわけで、大昔のMS-DOSとかはともかく、ちゃんとしたOS(それがWindowsかLinuxかは問わない)をバリバリ使う限りにおいては、ものすごく煩雑にRingの切り替えが発生することになる。
1つのアイディアとしては、特定のコアをKernelに割り当てて、常時Ring 0に上げっぱなしという方法もなくはない(組み込み向けのある種の実装にそうした例は存在する)が、普通はそのコアがボトルネックになりがちなので一般的ではない。そこで、もう少し特権の変更に掛かるコストを下げることで、レスポンスの改善やオーバーヘッド削減に伴う性能向上を図ろう、というのがFREDの意図である。
そもそもx86で特権レベル(CPL:Current Privilege Level)を変更する場合
- 特権を増やす(CPLを減らす):SYSCALL/SYSENTER命令を使う、Call Gateを呼び出す(far CALL命令)、Interrupt & Trap Gateを呼び出す(これはIDT:Interrupt Descriptor Tableに登録しておくと、InterruptなりTrapが発生した際に自動的に変更される)
- 特権を減らす(CPLを増やす):SYSRET/SYSEXIT命令を使う、far RET命令を使う、IRET命令を使う
といった形で行なう。SYSCALLの戻りはSYSRET、SYSENTERの戻りはSYSEXIT、という具合に、対になる命令が決まっている格好だ。
まぁここまではいいのだが、このCPLの変更にはいろいろと制限がある。
まず、現在のCPLの値を参照するのが面倒である。32bitモードの場合、CPLはCSレジスタとSSレジスタに2bitのフィールドで格納されているのだが、CPLを変更するとCS/SSレジスタを書き換えてしまうので、結局ソフトウェアから直接参照できない。
64bitモードだとGSレジスタにやはりCPLが含まれており、しかも幸運にもCPLが変更されてもGSレジスタは書き換わらない……のだが、64bit OSがTLS(Thread Local Storage)の管理をこのGSを使ってしまっており、GSのベースアドレスがTLSのロケーションを指している。ということは、OSとユーザーでGSのセグメントが異なることになる(CPLが異なるから)。このためCPLを変更する際には、GSの値を退避/復元しておく必要があり、この目的でSWAPGS命令が用意されている。
さて、この面倒くささを解消すべく考慮されたのがFREDである。FREDを使うと
特権を増やす:IDTからInterrupt & Trap Gateを呼び出す場合、FREDは新プロセスを生成するが、この新プロセスは、IDTを含む既存のデータ構造を一切アクセスしない。これはSYSCALL/SYSENTER命令を利用した場合も同じである。
- 特権を減らす:新しくERETS及びERETU命令が提供される。これは機能的にはSYSRETやSYSEXITと同じだが、ERETSはCPLを変化させない(つまりRing 0で動く)スーパーバイザーに制御を戻す命令、ERETUはCPLが3の、ユーザープロセスに制御を戻す命令である
と簡単化される。またFREDを利用した場合、GSレジスタの変更はSWAPGSを使った場合と同じ仕組みで自動的に更新される(のでOS側でGSのハンドリングを考慮する必要がない)
ただこの仕組みを使うと、今後はGSを変更する方法がなくなってしまう。先にOSはTLSの格納にGSを使っていると説明したわけで、コンテキストスイッチングとかにこれまではSWAPGSを使ってGSの変更を行なっていた。ところがFREDを有効にすると、SWAPGSが使えなくなる(Invalid Exceptionが発生するようになる)。
そこでSWAPGSに代わってGSを変更するのがLKGS命令である。この命令を使うとIA32_KERNEL_GS_BASE MSRにベースアドレスをロードできるようになる。ただLKGS命令は別にFREDが有効でなくても利用可能となっている。
ちなみにFREDを有効にした場合、Far CALLの振る舞いも変化する。同様に、FREDが有効になった場合、遷移できるCPLは0か3のみで、もう今後は1/2への遷移は行なえないことになる。
この辺は影響が大きいためか、今のところFREDのターゲットとなるのは64bit OSであり、IA-32eモードでは利用できるが、旧来のプロテクトモードやリアルモードでは動作しない。もう旧来のプロテクトモードやリアルモードはメンテナンスモードに入っており、新規の命令対応とか新メカニズムのサポートなどは行なわれないから、これは妥当な制限だろう。
このFREDの導入で、どの程度ボトルネックが解消されるのか、については何の数字も示されていない。ただ、何しろ煩雑に使われる処理だから、ほんのわずかな改善であっても、トータルでは結構な差になるのかもしれない。
FREDの真の意味
ただFREDの本当の意図は、Ring 1/2の廃止にあるのではないかと筆者は考える。それこそVMSしか使った例がない(ちなみにOpenVMSをエミュレータを使ってx86に載せている例はあるが、こっちはエミュレータがRing 0~3をエミュレーションしてるだけで、エミュレータ本体はRing 0と3でのみ動いている)ようなRing 1/2をいつまでも残しておく技術的な意味はなく、ところがソフトウェア互換性を保つためにやむなく残していたというのが正直なところである。
なので、廃止の最初のステップとして、まずFREDを導入してRing 1/2を使えないように変更。すべての現行OSがFREDを使うようになり、かつもう古いOSがほとんど死に絶えたであろうタイミングでRing 1/2を廃止、というあたりがIntelの思い描いている図式に思える。
いや廃止ではなく、別の用途に割り当てるのかもしれないが。あまり深く考えずに(?)仕様を決めてしまうと、後で廃止するのにえらい苦労する、という好例なのかもしれない。