Windows NTカーネルのアーキテクチャ
WindowsがUnix系と何が違うのかを、層構造とオブジェクト指向設計から一気に掴めます。Executive・Kernel・HALの分担、ハンドルとオブジェクトマネージャ、I/Oマネージャとドライバスタックを原理から整理します。
- 1.NTカーネルはHAL(ハードウェア抽象)・Kernel(スケジューリングと同期の最小機構)・Executive(オブジェクト管理やI/Oなど上位サービス)の層に分かれ、すべてカーネルモードで動くハイブリッド構造をとる。
- 2.ファイル・プロセス・スレッド・同期オブジェクト等がすべて統一的な「オブジェクト」として表現され、プロセスごとのハンドルテーブル経由で参照される。Unixのファイルディスクリプタより広い対象を1つの仕組みで扱う。
- 3.I/OはIRP(I/O Request Packet)を積層したドライバスタックへ流す非同期設計が基本で、Unixのvnode直結モデルとは設計思想が異なる。
NTカーネルは「層」でできている
Windows NT 系(現在の Windows 10/11 や Server もこの系譜)のカーネルは、責務ごとにきれいに層分けされた設計が特徴です。Linux のようなモノリシックでもなく純粋なマイクロカーネルでもない、ハイブリッドカーネルに分類されます(位置づけは マイクロカーネルとモノリシックカーネルの設計比較 を参照)。すべての層がカーネルモードの単一アドレス空間で動くため境界越えは関数呼び出しで速く、一方で内部はマイクロカーネル的に役割が分離されています。
カーネルモードの中身は、下から大きく3層です。
| 層 | 正体 | 担うもの |
|---|---|---|
| HAL | hal.dll | 割り込みコントローラ・タイマ・バスなどハードウェア差を吸収し上位から隠す |
| Kernel(Microkernel層) | ntoskrnl 内の Ke* 部分 | スレッドのスケジューリング、ディスパッチ、低レベル同期(スピンロック等)、割り込み配送 |
| Executive | ntoskrnl 内の上位部分 | オブジェクトマネージャ、プロセス/メモリ/I/O/セキュリティなど上位サービス群 |
ポイントは Kernel 層と Executive 層を分けている ことです。Kernel 層は「機構(mechanism)」だけを持ちます——スレッドをどう切り替えるか、どうロックを取るか、といった最小の道具立てです。その上の Executive が「ポリシー(policy)」と高レベルの抽象(オブジェクト、プロセス、I/O)を構築します。この機構とポリシーの分離は、マイクロカーネルの発想をモノリシックな実装の中に取り込んだものと言えます。HAL がさらに下にいるため、Kernel/Executive はチップセットの違いを意識せずに書けます。
NT では Win32 などの API は薄いサブシステム(かつては別プロセス、現在は多くがライブラリ + カーネル協調)として実装され、最終的に ntdll.dll を介してシステムコールへ降ります。アプリ → サブシステムDLL → ntdll → ntoskrnl(Executive)という流れで、Executive の Nt* 関数がシステムコールの実体です。境界の原理は システムコールとカーネル と共通です。
すべては「オブジェクト」:オブジェクトマネージャとハンドル
NT 設計の最大の個性が オブジェクトマネージャ です。プロセス、スレッド、ファイル、ミューテックス、セマフォ、イベント、レジストリキー、デバイス、トークン——これらカーネルが管理する資源がすべて統一的な「オブジェクト」として表現されます。各オブジェクトは共通のヘッダ(型情報・参照カウント・セキュリティ記述子・名前など)を持ち、\Device\、\BaseNamedObjects\ のような名前空間にツリー状に配置されます。
アプリがオブジェクトを使うときは、直接ポインタを触らず ハンドルを介します。ハンドルはプロセスごとのハンドルテーブルのインデックスで、テーブルのエントリがカーネル内オブジェクトへのポインタとアクセス権ビットを保持します。
ユーザー空間 カーネル空間
handle (整数) ──→ [プロセスのハンドルテーブル]
│ entry: { オブジェクト*, 許可されたアクセス権 }
↓
[オブジェクトヘッダ: 型/参照カウント/SD/名前]
↓
[オブジェクト本体: FILE_OBJECT, ETHREAD, ...]
この構造はUnixのファイルディスクリプタとよく似ていますが(ファイルディスクリプタとオープンファイルテーブルの構造 を参照)、対象が file に限られない点が決定的に異なります。Unix では「ファイル以外」の資源(プロセスやセマフォ等)はそれぞれ別 API・別 ID で扱いますが、NT では1つのハンドル機構ですべてを扱うため、WaitForSingleObject のような汎用APIで「スレッドの終了待ち」も「ミューテックスの取得」も同じ形で書けます。
オブジェクトには ハンドルカウント(いくつのハンドルが開いているか)と ポインタカウント(カーネル内コードからの参照を含む総参照数)の2系統があります。最後のハンドルが閉じてもカーネル内部がまだ参照していればオブジェクトは消えません。ポインタカウントが0になって初めて解放されます。これにより、ユーザーが CloseHandle した後もカーネルが安全に使い続けられます。
オブジェクトには**セキュリティ記述子(SD)**が結び付き、ハンドルを開く瞬間にアクセスチェックが走ります。要求したアクセス権が許可されると、その権限がハンドルエントリに刻まれ、以後の操作はその範囲に限定されます。「オープン時に一度チェックし、結果をハンドルに焼き付ける」というモデルは、Unix の open 時パーミッションチェックと発想が近い一方、ACL ベースで粒度が細かいのが特徴です。
I/OマネージャとIRP:ドライバを積み重ねる
I/O の中核は I/Oマネージャ と IRP(I/O Request Packet) です。アプリが ReadFile を呼ぶと、I/Oマネージャは要求を1つの IRP という構造体に詰め、対象デバイスのドライバスタックへ送り込みます。
NT のドライバは縦に積層されます。たとえばファイル読み取りなら、上から「ファイルシステムドライバ → ボリューム/パーティション → ディスククラスドライバ → ポート/ミニポート(実ハード)」のように複数層が並びます。IRP はこのスタックを上から下へ降りていき、各層がスタックロケーション(自分用の領域)を見て処理し、必要なら下位へ渡します。完了は逆向きに、下から上へ completion ルーチンを呼びながら戻ります。
ReadFile()
│ I/Oマネージャが IRP を生成
↓
[ファイルシステムドライバ] ← フィルタ(暗号化/AV等)も間に挟める
↓ IoCallDriver
[ボリューム / ディスククラス]
↓
[ポート / ミニポートドライバ] → 実デバイスへ
↑ IoCompleteRequest(completion を上へ伝播)
この設計の利点はフィルタドライバを任意の層の間に差し込めることです。暗号化、ウイルススキャン、ボリュームシャドウコピーなどが、既存ドライバを書き換えずにスタックへ挿入する形で実現されます。さらに IRP は本質的に非同期で、IoCallDriver 後に STATUS_PENDING を返して呼び出し元を解放し、完了時に I/O完了ポート(IOCP)等で通知する、という流れが自然に組めます。
NT 独自の概念に IRQL(Interrupt Request Level) があります。CPU が現在どの割り込み優先度で動いているかを表し、PASSIVE_LEVEL(通常)から DISPATCH_LEVEL、各デバイス割り込みレベルまで段階があります。DISPATCH_LEVEL 以上ではページフォルトを起こせない(ページング可能メモリに触れない)、ブロックできない、といった制約が課されます。ドライバの completion ルーチンが高 IRQL で走るため、そこで何ができるかは IRQL が決めます。Linux の「割り込みコンテキストではスリープ不可」に対応する、より体系化された仕組みです。
Unixとの設計差:IPCと同期
NT と Unix 系は、IPC と同期の設計思想がはっきり分かれます。共通の入口は プロセス間通信(IPC) で押さえつつ、NT 固有の選択を整理します。
| 観点 | Unix系(Linux等) | Windows NT |
|---|---|---|
| 資源の抽象 | ファイル中心(everything is a file)。多くの資源を fd で表すが対象は限定的 | オブジェクト中心(everything is an object)。プロセス/同期/レジストリ等まで統一ハンドル |
| 主要IPC | パイプ、シグナル、System V/POSIX IPC、Unixドメインソケット | ALPC(Advanced Local Procedure Call)を基盤に、名前付きパイプ・共有セクション・LPC |
| 同期の最小機構 | futex(軽量時はユーザー空間で完結) | ディスパッチャオブジェクト(イベント/ミューテックス/セマフォ)+ クリティカルセクション |
| 待機の汎用性 | select/poll/epoll は主にfd対象。スレッド終了待ちは別API(wait系) | WaitForMultipleObjects が任意のディスパッチャオブジェクトを一律に待てる |
| プロセス生成 | fork + exec(複製してから置換) | CreateProcess(最初から新イメージを構築、forkに相当する複製はしない) |
とりわけ重要なのが待機の統一性です。NT の Kernel 層には「ディスパッチャオブジェクト」という、待機可能(waitable)状態を持つオブジェクト群があります。スレッド、プロセス、イベント、ミューテックス、セマフォ、タイマがこれにあたり、いずれも「シグナル状態/非シグナル状態」を持ちます。WaitForMultipleObjects は型を問わずこれらをまとめて待てるため、「ファイルの読み取り完了とスレッドの終了とタイマ満了を同時に待つ」が1呼び出しで書けます。Unix では epoll が扱うのは主に fd であり、スレッド終了は wait/pthread_join、と窓口が分かれます(軽量ロックの内部は futexの内部動作 が参考になります)。
Windows の CRITICAL_SECTION は、競合がなければユーザー空間のインターロック命令だけで完結し、争いが起きたときだけカーネルのイベントへ降りて待機します。これは Linux の futex が「競合なしはユーザー空間、競合時のみ syscall」とするのと同じ最適化思想です。一方プロセス間で共有できる名前付きミューテックスは、カーネルのディスパッチャオブジェクトとして実装され、必ずカーネルを経由します。
「NT はマイクロカーネル」は誤りで、正しくはハイブリッドです(HAL/Kernel/Executive はすべてカーネルモードで動く)。「ハンドル = ファイルディスクリプタ」も不正確で、ハンドルはファイルに限らずあらゆるオブジェクトを指せます。CreateProcess は fork ではなく、複製を経ずに新イメージを直接構築する点も頻出の差異です。IRP は「同期I/Oの構造体」ではなく、本質は非同期で積層スタックを流れる要求パケットです。
まとめ
Windows NT カーネルは、HAL(ハード差の吸収)・Kernel(スケジューリングと低レベル同期の機構)・Executive(オブジェクト管理やI/O等の上位サービス) という層構造を持つハイブリッドカーネルです。最大の特徴は オブジェクトマネージャで、プロセスからミューテックスまであらゆる資源を統一オブジェクトとして表し、プロセスごとのハンドルテーブル経由でアクセス権付きに参照させます。I/O は IRP を積層ドライバスタックへ非同期に流す設計で、フィルタドライバの挿入と非同期完了を自然に支えます。Unix との本質的な差は、ファイル中心 vs オブジェクト中心という抽象の置き方と、WaitForMultipleObjects に代表される待機の統一性にあります。入口の境界は システムコールとカーネル、ハンドルの対比は ファイルディスクリプタの構造 も合わせてどうぞ。
OS Article
Windows NTカーネルのアーキテクチャを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Windows
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
ファイル・プロセス・スレッド・同期オブジェクト等がすべて統一的な「オブジェクト」として表現され、プロセスごとのハンドルテーブル経由で参照される。Unixのファイルディスクリプタより広い対象を1つの仕組みで扱う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Windows / NTカーネル」に近いか確認する。
- 強みである「NTカーネルはHAL(ハードウェア抽象)・Kernel(スケジューリングと同期の最小機構)・Executive(オブジェクト管理やI/Oなど上位サービス)の層に分かれ、すべてカーネルモードで動くハイブリッド構造をとる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。