amd64、intel64、x86-64、IA-32e、x64 はすべて 64 ビット x86 アーキテクチャに分類されます。IA-32 は 32 ビット x86 アーキテクチャです。
64 ビット OS は x64 モード(ロングモード)を有効にします:OS をロードした後、最初に従来のページングアドレス保護モードに入ります。その後、PAE モードが有効になります。
1)互換サブモード:32 ビットまたは 16 ビットのアプリケーションを修正または再コンパイルすることなく実行できます。ただし、仮想 8086 モード、タスクスイッチング、およびスタックパラメータコピー機能は互換モードではサポートされていません。したがって、従来のアプリケーションを実行する際にはいくつかの制限があります。
2)64 ビットサブモード:64 ビットアプリケーションを専用に実行し、64 ビットの線形アドレス空間をサポートします。CPU 内の汎用レジスタは 64 ビットに拡張され、8 つの追加の汎用レジスタ(R8〜R15)が追加されます。さらに、処理効率を向上させるための新しい SIMD 拡張レジスタ(XMM8〜XMM15)もあります。64 ビットサブモードは、より効率的な命令セット、拡張されたメモリアドレッシング、新しい割り込み優先度制御メカニズムをサポートし、より強力な計算能力を提供します。
32 ビット OS は x86-32 モードを有効にします:
1)リアルモード:論理アドレスは物理アドレスに直接マッピングされ、ページングメカニズムはありません。20 ビットの外部アドレスバスは 1MB のメモリ空間にしかアクセスできません。BIOS は通常、このモードでハードウェアとデータ構造を初期化します。
2)プロテクトモード:プロテクトモードで導入されたメカニズムは、現代のオペレーティングシステム(Windows、Linux など)が実行される基盤です。このモードでは、CPU はメモリ保護、仮想メモリ、タスクスイッチングなどの機能を実現し、OS はこれらの機能を利用してハードウェアリソースを管理できます。システムはより大きな物理アドレス空間にアクセスでき、マルチタスクとメモリ保護をサポートします。
3)仮想 8086 モード:プロテクトモードで 16 ビットプログラムを実行でき、これらのプログラムはリアルモードに近い環境で実行できます。OS は仮想化技術を使用して、各 16 ビットプログラムを独立した仮想環境で実行し、プロテクトモードを終了することなく実行します。
実行モード | 実行サブモード | 何ビットの OS によって有効化されるか | アプリケーションは再コンパイルが必要か | デフォルトアドレス長 | デフォルトオペランド長 | 拡張レジスタ | 汎用レジスタビット幅 |
---|---|---|---|---|---|---|---|
x86-32 モード | 仮想 8086 モード | 16 ビット | 不要 | 16 | 16 | 使用しない | 16 |
リアルモード | 16 ビットまたは 32 ビット | 不要 | 16 | 16 | 使用しない | 16 | |
プロテクトモード | 16 ビットまたは 32 ビット | 不要 | 16 または 32 | 16 または 32 | 使用しない | 16 または 32 | |
x64 モード(ロングモード) | 64 ビットモード | 64 ビット | 必要 | 64 | 32 | 使用する | 64 |
互換モード | 64 ビット | 不要 | 16 または 32 | 16 または 32 | 使用しない | 16 または 32 |
以下の説明は、x86-32 プロテクトモードと x64 互換モードのクラシックアーキテクチャに基づいており、後続のアーキテクチャの改善はこれを基にしています。また、x64 64 ビットモードの特別な改善も列挙します。
CPU アーキテクチャ#
メモリアーキテクチャ#
アドレス空間#
物理アドレス空間#
メモリやその他のハードウェアデバイスなど、CPU が使用できるリソースは物理アドレス空間として統一されてアドレス指定されます。CPU は物理アドレスをインデックスとしてアクセスします。MMU は、CPU が渡した仮想アドレスを物理アドレスに変換した後、外部アドレスバスを介してメモリにアクセスします。外部アドレスバスのビット数は物理アドレス空間のサイズを決定し、外部アドレスバスのビット数は内部アドレスバスのビット数以上である必要があります。
メモリ管理ユニット(Memory Management Unit)には、ベースレジスタbase
があります:プロセス空間 0 のアドレスにマッピングされた物理アドレスを保存します。MMU は次の作業を行います:
physical address = virtual address + base;
プロセス空間はゼロから始まり、仮想アドレスはプロセス空間のオフセットに相当します。
線形アドレス空間#
線形アドレス空間のサイズは、CPU の内部アドレスバスのビット数によって決まります。内部アドレスバスは CPU の実行ユニットに接続されており、内部アドレスバスのビット数は通常 CPU のビット数と一致します。線形アドレス空間は、各プログラムが自分専用の仮想アドレス空間を持っていると考えられるものです。線形アドレス空間は、物理アドレス空間の一部または全体にマッピングされます。1 つのハードウェアプラットフォームには複数の線形アドレス空間が存在する可能性があります。
ページングメカニズムが有効でない場合、線形アドレスは物理アドレスと同じです。CPU がページングメカニズムを使用する場合、線形アドレスを物理アドレスに変換する必要があります。
論理アドレス / セグメントアドレス#
x86 の初期のセグメンテーションメカニズムの歴史的負担。論理アドレスはプログラムが直接使用するアドレスです。16 ビットのセグメントインデックス子 + 32 ビットまたは 64 ビットのオフセットで構成されます。以下の文のポインタ変数 p が保存しているのは、変数 a の論理アドレスのオフセットであり、リンカとローダーはそのオフセットが存在するセグメントインデックス子を割り当て、CPU はセグメントレジスタまたはインデックス子に基づいてマッピングテーブル(Descriptor table)からセグメントに対応するセグメントベースアドレス、セグメント境界、権限などを取得します。その後、セグメントベースアドレス + オフセットを生成して仮想アドレスを得ます。
int a = 100;
int *p = &a;
セグメンテーションメカニズム#
プロセスデータは論理的にコードセグメント、データセグメント、スタックセグメントなどに分割されます。プログラムはセレクタを使用して各論理セグメントを識別し、オフセットを使用してセグメント内のデータの位置を示します。
各論理セグメントは、仮想アドレス空間にマッピングされ、セグメントベースアドレス Base + セグメント境界 Limit で記述されるメモリブロックに対応します。プログラムがメモリデータにアクセスする際、変数の論理アドレスセレクタ + オフセットを線形アドレス base + オフセットに変換する必要があります:
セグメントレジスタはセレクタ→Descriptor の関係をキャッシュします。Descriptor にはセグメントベースアドレスやさまざまな属性、権限フラグが含まれています。
1)属性、アクセス権などのチェックを行います。
2)DS データセグメントレジスタの後半の Descriptor 部分から、そのセグメントがマッピングされた仮想アドレス空間のベースアドレス base を取得します。
3)変数のセグメントオフセットとそのセグメントの線形空間内のベースアドレス base を加算して、その変数の線形アドレスを取得します。
セグメントレジスタキャッシュがヒットしない場合:
プロセスのロード段階で、LDT のセグメントマッピング関係データ構造が作成され、LDT アドレスが GDT に登録された後、そのプロセスの LDT のセレクタは GDT をインデックスし、LDT の Descriptor が記述するテーブルアドレスを取得し、セレクタ→Descriptor の関係を LDTR レジスタにキャッシュします。さらに、そのプロセスの CS、DS、SS がそれぞれのセレクタにロードされ、CPU はセレクタの TI フィールドに基づいて対応する Descriptor table をインデックスし、対応する Descriptor を取得し、セレクタ→Descriptor の関係を CS、DS、SS の各セグメントレジスタにキャッシュします。
Descriptor#
Descriptor はセグメントのベースアドレス、長さ、およびさまざまな属性(読み取り / 書き込み、アクセス権など)を記述します:
1)Base フィールドはそのセグメントのベースアドレスを記述します。
2)Limit フィールドはそのセグメントの長さを記述します。
3)DPL フィールドはコードがこのセグメントにアクセスするために必要な最小権限を示します。
CPU がセグメントに対応する Descriptor を取得した後、Descriptor 内のさまざまな属性フィールドを使用してアクセスをチェックします。一旦アクセスが合法であることが確認されると、CPU は Descriptor 内のベース(すなわちセグメントがマッピングされた仮想アドレス空間のベースアドレス)とプログラム内の論理アドレスのオフセットを加算します。これにより、論理アドレスに対応する線形アドレスが得られます。
セレクタ & GDT/LDT#
セレクタ:
プログラムは論理アドレスのオフセット部分のみを保存および使用します。セレクタはプログラムから見える論理アドレスの構成要素であり、その変更と割り当てはリンカとローダーによって行われ、Descriptor table をインデックスしてセグメントに対応する Descriptor を取得するために使用されます:
1)Index:Descriptor table のインデックス。
2)TI:GDT または LDT をインデックスすることを示します:
a. TI=0 の場合、Global Descriptor Table をインデックスします。
b. TI=1 の場合、Local Descriptor Table をインデックスします。
3)RPL:Requested Privilege Level、要求される権限レベル。RPL はセグメント選択レジスタの下位 2 ビットに存在し、プログラムがセグメントにアクセスする際に追加のチェックを行います。
GDT/LDT:
システムには、すべてのプロセスがアクセスできる少なくとも 1 つのグローバル Descriptor table があります。システムには 1 つまたは複数のローカル Descriptor table があり、特定のプロセスにプライベートであり、複数のプロセスで共有されることができます。これらは単にセレクタ→Descriptor のマッピングテーブルであり、大部分は線形リストとして実装されています:
1)GDT はメモリ内の位置がベースアドレス Base とテーブル境界 Limit で記述されます。複数のプロセスで共有されるセグメントのマッピング関係を格納します。たとえば、カーネルコードセグメント、共有ライブラリセグメント、カーネルデータセグメントなどです。
2)LDT はプロセス間で共有アクセスを必要としないプライベートセグメントのマッピング関係を格納します。たとえば、ユーザコードセグメント、ユーザデータセグメント、ヒープ、スタックなどです。LDT はメモリ内の位置が GDT 内の Descriptor によって記述され、システム内にいくつの LDT が存在するかは GDT 内の Descriptor の数によって決まります。
3)GDTR および LDTR レジスタ:これらの 2 つのレジスタは、CPU が GDT/LDT マッピングテーブルを迅速に見つけるのを助けます:
a. GDTR:GDT ベースアドレス Base + テーブル境界 Limit
b. LDTR:LDT はメモリ内の位置が GDT 内の Descriptor によって記述されます。Descriptor は LDTR にキャッシュされるため、LDTR の構造はセグメントレジスタと同じです(セレクタ + Descriptor)。
セレクタを使用して GDT/LDT をインデックスするプロセスは以下の図のようになります:
セレクタの TI フィールドは GDT または LDT をインデックスすることを示します。LGDT/SGDT 命令を使用して GDTR の読み書きを行い、マッピングテーブルアドレスを取得します。プロセス切り替えを行う際、LDTR の値は新しいプロセスに対応する LDTR に変更されます。
セグメントレジスタ#
毎回セレクタを使用して GDT/LDT をインデックスし、論理セグメントのマッピングされた仮想アドレスを取得します。このタイプのオーバーヘッド最適化:空間を時間で交換することは、私たちが非常に慣れていることです。x86 は 6 つの 16 ビットセグメントレジスタを提供します。各セグメントレジスタは、セレクタの後に Descriptor レジスタを追加して論理セグメントのマッピングされた仮想アドレス Descriptor をキャッシュします:
1)CS(Code-Segment、コードセグメント):コードセグメントのセレクタ→Descriptor を保存します。
2)DS(Data-Segment、データセグメント):データセグメントのセレクタ→Descriptor を保存します。
3)SS(Stack-Segment、スタックセグメント):スタックのセレクタ→Descriptor を保存します。
4〜6)ES、FS、GS:追加の 3 つのデータセグメントのセレクタ→Descriptor を保存できます。プログラムが自由に使用します。
セグメントレジスタが新しいセレクタをロードすると、CPU は自動的にそのセレクタがインデックスされた Descriptor を不可視の Descriptor レジスタにキャッシュします。つまり、CPU はセグメントレジスタを更新する際にのみ Descriptor table をインデックスします(たとえば、バインドされたスレッドを切り替えるときにセグメントレジスタが更新されます)。
ページングメカニズム#
仮想アドレス空間を複数のページに分割し、物理アドレス空間も同様に分割され、メモリの断片化を減少させます。
ページングメカニズムは、物理メモリが不足しているときに、あまり使用されないページをディスクのスワップスペース(Linux のスワップパーティションや Windows の仮想メモリファイルなど)に移動することを許可します。これはメモリの仮想化メカニズムの基本的な機能と見なすことができます。
32 ビット内部アドレスバスの仮想アドレス空間は、アドレス可能なメモリ$2^{32}B=4\cdot2^{10}\cdot2^{10}\cdot2^{10}B=4GB$
です;(64 ビット内部アドレスバスは、そのうち 48 ビットのアドレス可能なメモリ 256TB を使用します)
x86 アーキテクチャは 4KB を超えるページサイズを許可します(たとえば、2MB、4MB);しかし、x86-32 ページの典型的なサイズは $4KB=4\cdot2^{10} B$ であり、4GB のメモリから 1024×1024 のページを分割できます。
ページテーブルエントリの数は、$[内部アドレスバス(仮想アドレス空間のサイズ) \cdot プロセス数]$ に比例します。
CPU がデータにアクセスするフロー:
CPU がデータにアクセスする際、プロセスの仮想アドレス(仮想ページ VFN + オフセット)を実際の物理アドレス(物理ページ PFN + オフセット)に変換する必要があります;
1)TLB を検索:CPU は最初に TLB 内でその仮想アドレスの物理アドレスを検索します。TLB がヒットすれば、この物理アドレスを直接使用してアクセスします。
2)ページテーブルを検索:TLB がミスした場合、CPU は仮想アドレス内の各ページテーブルインデックスに基づいて、対応するページテーブルエントリを検索します。ページテーブルエントリには、その仮想ページに対応する物理ページのベースアドレスが格納されています。
3)ページテーブルエントリに有効なマッピングがない場合(たとえば、そのページが物理メモリにない場合)、ページフォルト割り込みが発生します。OS はページをディスクのスワップスペースから物理メモリにロードし、ページテーブルの対応するページテーブルエントリ PTE を更新し、P ビットを 1 に設定し、他のフィールドを適切に設定します。最後に、ページフォルト処理プログラムから戻り、CPU は再度ページテーブルを検索し、対応するマッピングを TLB に挿入します。
4)PFN + オフセットで、対応する物理アドレスを得ます。
TLB#
Translation Lookaside Buffer は最近使用されたページマッピング(VFN→PFN)をキャッシュします。CPU が特定の線形アドレスにアクセスする際、そのページのマッピングが TLB に存在する場合、ページテーブルを検索することなく、その線形アドレスに対応する PFN を取得できます。CPU は PFN と線形アドレスのオフセットを加算して物理アドレスを得ます。TLB がヒットしない場合はページテーブルを検索し、TLB を更新します。
ページテーブル#
ページテーブルデータ構造は、Virtual Frame Number→Physical Frame Number のマッピング関係を格納します。TLB のヒット率と各キャッシュのページテーブルサイズを考慮して、ページテーブルは通常多段階ページテーブルとして実装されます。
x86-32 プロテクトモードでは、ページテーブルは二段階ページテーブルとして実装されます。これにより、TLB をキャッシュする際、1 つのページテーブル内の $2^{20}$ 個のページマッピング関係をキャッシュするのではなく、$2^{10}$ のページテーブルの 1 つをキャッシュし、各ページテーブルには $2^{10}$ のページマッピング関係が含まれます。CR3 は $2^{10}$ 個の項目を持つページディレクトリ(各項目 4B、ページディレクトリサイズ 4KB)を指します。各ページディレクトリ項目は、1024 個の 4KB サイズのページテーブルを指します。PAE が有効でない 4KB サイズのページは以下のようになります。
4KB ページサイズには 12 ビットのページオフセットをインデックスする必要があります。TLB は一度に 2102^{10} 210 個のページマッピング関係 PTE をキャッシュすることを規定しています。つまり、10 ビットで一度にキャッシュできるページマッピング関係 PTE をエンコードし、残りの 10 ビットは自然に 2102^{10} 210 個の二次ページテーブルをエンコードします。
明らかに、仮想アドレスの上位 20 ビットは、ちょうど二次ページテーブルデータ構造全体をインデックスするためにエンコードされています。
1)ページテーブルエントリ:VFN→PFN のマッピング関係を格納します。次の 10 ビットは 2102^{10} 210 個の TLB が一度にキャッシュできるページマッピング関係をエンコードし、二次ページテーブルに格納されます。PTE から VFN→PFN のマッピング関係を取得することで、線形アドレス VFN + オフセットに対応する物理アドレス PFN + オフセットを特定できます。
2)ページディレクトリエントリ:上位 10 ビットはすべての二次ページテーブルをインデックスするためにエンコードされており、すなわち PDE は 10 ビットの 2 進数エンコード xxxx xxxx xx → 二次ページテーブルのベースアドレスのマッピング関係を格納します。すべての PDE は一次ページテーブルに格納され、PDE のサイズは 4B で、ページディレクトリには 1024 個の PDE が含まれ、1 つの 4KB の物理ページを占めます。
ページフォルト#
PDE と PTE には P(Present)フィールドが含まれています。
1)P=1 の場合、物理ページは物理メモリにあり、CPU はアドレス変換を完了した後、そのページに直接アクセスできます。
2)P=0 の場合、物理ページは物理メモリにありません。CPU がそのページにアクセスすると、ページフォルト割り込みが発生し、ページフォルト処理プログラムにジャンプします。OS は通常、スワップスペースに格納されているページを物理メモリにロードし、アクセスを続行できるようにします。プログラムの局所性の特徴により、OS はそのページの近くのページも一緒に物理メモリにロードし、CPU のアクセスを容易にします。
物理アドレス拡張 PAE#
PAE を有効にすると、各レベルのページテーブルサイズは依然として 4KB ですが、ページマッピング関係表データ構造は三段階ページテーブルとして実装されます。各レベルのページテーブル項目は 32 ビットから 64 ビットに拡張され、追加のアドレスビットを使用します。1 ビットを 1 段階ページテーブルにエンコードし、9 ビットを 2 段階ページテーブルにエンコードし、9 ビットを 3 段階ページテーブルにエンコードします。これにより、2 段階および 3 段階ページテーブルはそれぞれ $2^{9}=512$ 個のマッピングデータを持ち、2 段階ページテーブルスキームの半分になります。CR3 は 1 段階ページテーブルのベースアドレスを指し、1 段階ページテーブルは 4 つの項目を含むテーブルです。
PDBR/CR3 レジスタ#
ページディレクトリベースレジスタまたは CR3 レジスタは、トップレベルページテーブルの物理アドレスを格納します。プロセスが実行される前に、そのトップレベルページテーブルの基準アドレスを CR3 に格納する必要があり、トップレベルページテーブルの基準アドレスは 4KB ページ境界に整列されている必要があります。
64 ビットサブモードメモリ管理の改善#
アドレス空間:
64 ビットの線形アドレスを使用しますが、実際には有効な仮想アドレス空間は 48 ビットに制限されており、高位 48〜63 ビットは同じです。プログラムは $2^{48} B=256TB$ の仮想アドレスメモリ空間にアクセスできます。物理アドレス空間に関しては、実際に有効なアドレス空間は 52 ビットに制限されています。
セグメンテーションメカニズムは無効化されていませんが、その効果は弱まっています:
CPU はもはやセグメントベースアドレスを使用してアドレス変換を行わず、CS、DS、ES、SS などのセグメントレジスタのベースアドレスは 0 に設定されます。FS および GS セグメントレジスタは、依然としてセグメントベースアドレスを保持しており、特定の操作、たとえばローカルデータアドレッシングや OS 内部データ構造管理(スレッドローカルストレージなど)に使用されます。さらに、セグメント長のチェックは無効化されており、CPU はセグメントサイズをチェックしません。
ページングメカニズムの最適化:
メモリページのサイズは 4KB、2MB、1GB です。PAE は必ず有効にする必要があり、有効にすると OS は四段階ページテーブルを使用します —— 追加の第四段階ページマッピングテーブル(Page-Map Level 4 Table、略して PML 4 Table)を追加します。これにより、48 ビットの線形アドレスを 52 ビットの物理アドレスに変換するのを助けます。PML4 の物理ベースアドレスは CR3 に存在します;
PML4 項目:PML3 の物理ベースアドレス、アクセス権、およびメモリ管理情報を含みます。
PML3 項目:PML2 の物理ベースアドレス、アクセス権、およびメモリ管理情報を含みます。
PML2 項目:PML1 の物理ベースアドレス、アクセス権、およびメモリ管理情報を含みます。
PML1 項目:VDN→PFN、アクセス権、およびメモリ管理情報を含みます。
割り込み&例外アーキテクチャ#
サブカテゴリ | 原因 | 非同期 / 同期 | 戻り動作 | 例 | |
---|---|---|---|---|---|
割り込み | I/O デバイスからの信号 | 非同期 | 常に次の命令に戻る | 外部デバイスの応答要求、たとえばキーボードの打鍵 | |
例外 | エラー | 潜在的に回復可能なエラー | 同期 | 現在の命令に戻る可能性がある | 必ずしも致命的なエラーではない、たとえばページフォルト |
トラップ | 意図的な例外 | 同期 | 常に次の命令に戻る | システムコールの要求、たとえばファイルの読み取り | |
終了 | 回復不可能なエラー | 同期 | 戻らない | 致命的なエラー、たとえば奇数エラー |
割り込みアーキテクチャ#
いくつかの例外や割り込みは、順次実行される命令フローを中断し、まったく異なる実行パスに入ります。現代のコンピュータアーキテクチャは、多くの割り込みイベントによって駆動されています。割り込みメカニズムにより、外部ハードウェアデバイスが CPU の現在の実行タスクを中断し、CPU に自分のサービスを提供させることができます。割り込みはデバイスから「割り込みコントローラ」に転送され、CPU に送信されます(MSI を除く)。
PIC(Programmable Interrupt Controller)#
プログラム可能な割り込みコントローラ PIC は、広く使用されている最初の割り込みコントローラであり、PIC は UP(単一プロセッサ)プラットフォームでのみ機能し、MP(マルチプロセッサ)プラットフォームでは使用できません。8 つの割り込みピン(IR0〜IR7)があり、外部デバイスに接続されています。外部デバイスが CPU の処理を必要とする場合、対応する割り込みラインを介して信号を送信し、割り込みをトリガーします。
主なレジスタ:
1)IRR(Interrupt Request Register): 現在要求されている割り込みを記録します。外部デバイスが IR 割り込み要求を発信し、その要求がマスクされていない場合、対応する IRR の位置に 1 が設定されます。
2)ISR(In Service Register): 現在処理中の割り込みを記録します。CPU が応答し、割り込みの処理を開始すると、ISR の対応する割り込み位置に 1 が設定されます。
3)IMR(Interrupt Mask Register): 特定の割り込みラインをマスクします。IMR の対応する割り込み位置が 1 の場合、対応する IR ピンの割り込み要求はマスクされ、CPU はその割り込みに応答しません。
割り込み処理のフロー:
1)外部デバイスが割り込み信号を発信します。この割り込みがマスクされていない場合、IRR の対応するビットが 1 に設定されます。
2)PIC は INT ピンを介して CPU に割り込みが発生したことを通知します。
3)CPU は INTA(割り込み応答)ピンを介して PIC に応答し、割り込み要求を受け取ったことを示します。
4)PIC は CPU の応答を受け取った後、IRR の優先度が最も高い割り込み要求をクリアし、ISR の対応するビットを 1 に設定して、割り込みが処理中であることを示します。
5)CPU は再度 INTA を介して 2 回目のパルスを発信し、PIC は優先度に基づいて対応する割り込みベクタを提供し、それを CPU のデータバスに送信します。
6)CPU が割り込み処理を完了した後、EOI(End of Interrupt)レジスタに書き込むことで PIC に割り込み処理が完了したことを通知し、PIC は ISR の対応するビットをクリアします。
APIC(Advanced Programmable Interrupt Controller)#
マルチプロセッサ(MP)システムでは、APIC システムにより複数の CPU が協調して作業し、割り込み処理のタスクを共有できます。各 CPU の LAPIC は、IPI メカニズムを介して通信および協力できます。この割り込み制御メカニズムは、高効率の割り込み処理をサポートするだけでなく、マルチコアプロセッサシステムの割り込みバランスを最適化します。
構成:
1)LAPIC(Local APIC): 各 CPU コアには LAPIC が装備されており、ローカル割り込み信号を処理します。IRR(Interrupt Request Register)を介して現在の割り込み要求の状態を保存し、ISR(Interrupt Service Register)を介して処理済みの割り込みフラグを保存します。CPU は EOI(End of Interrupt)レジスタに書き込むことで、LAPIC にその割り込みが処理されたことを通知し、他の割り込みの処理を許可します。
2)IOAPIC(I/O APIC): 通常、南橋チップ(または低速 I/O コントローラ)に位置し、外部デバイスの割り込み要求を受信し、それらを特定の CPU の LAPIC に転送します。IOAPIC には通常、複数の割り込み入力ピン(通常 24 個)があり、各ピンは外部デバイスに接続できます。
プロセッサ間割り込み(IPI): IPI(Inter-Processor Interrupt)は、1 つの CPU が他の CPU に割り込み信号を送信することを許可します。これは、マルチコアシステムにおけるプロセスの移動、負荷バランス、TLB のフラッシュなどの操作に非常に重要です。LAPIC のICR(Interrupt Command Register)を介して、システムはターゲット CPU を指定し、IPI 割り込みを送信できます。
割り込み伝達プロセス:
1)外部デバイスが割り込みをトリガーすると、IOAPIC が割り込み要求信号を受信します。IOAPIC は **PRT(Programmable Redirection Table)を使用して、割り込み要求のルーティング情報を検索します。
2)PRT の設定に基づいて、IOAPIC は割り込み情報をRTE(Redirection Table Entry)** としてフォーマットし、システムバスに渡します。
3)システムバスは、割り込みメッセージをターゲット CPU の LAPIC に送信します。
4)LAPIC は割り込みメッセージを受信した後、IRR レジスタの情報に基づいて処理を行うかどうかを決定します。その割り込みが処理条件を満たす場合、LAPIC は ISR レジスタをトリガーし、最終的に割り込みをプロセッサに渡します。
例外アーキテクチャ#
割り込みは外部デバイスによって生成され、CPU の現在実行中の命令とは無関係です。例外は CPU 内部で生成され、その原因は CPU が現在実行中の命令に問題があることです。
例外の発生原因は、重大性に応じて分類されます:
1)エラー(Fault):何らかのエラー状況によって引き起こされ、一般的にはエラー処理プログラムによって修正可能です。エラーが発生すると、プロセッサは制御を対応する処理プログラムに渡します。前述のページフォルトがこのカテゴリに該当します。
2)トラップ(Trap):特定の命令を実行した後に引き起こされる例外です。トラップは意図的な例外であり、トラップの最も重要な用途は、ユーザープログラムとカーネルの間にプロセスのようなインターフェース(すなわちシステムコール)を提供することです。Linux でシステムコールを実現するために使用される INT 80 命令はこのカテゴリに該当します。
3)終了(Abort):深刻な回復不可能なエラーを指し、プログラムの終了を引き起こします。典型的なものは、いくつかのハードウェアエラーです。
IDT#
CPU は割り込みディスクリプタテーブル(Interrupt Descriptor Table)を介して、割り込みまたは例外ベクタ番号に対応する処理プログラムのエントリ線形アドレスを検索できます。
IDT データ構造は、ベクタ→Descriptor のマッピング関係を格納します。Descriptor には処理ルーチンが存在するセグメントのセレクタとセグメント内オフセット、およびさまざまな属性フラグが含まれています。
IDT テーブルの基準アドレスはIDTR
レジスタに格納されます:
1)Base:IDT ベースアドレス
2)Limit:IDT テーブルの最大バイト数(通常は 8 バイトのアラインメント)
割り込みゲート & トラップゲート#
割り込みゲートは IDT 内の Descriptor です。特定の割り込みまたは例外が発生したときに、どの割り込みサービスルーチン ISR にジャンプすべきかを記述します。その形式はセグメント Descriptor に似ています(ただし、一部の特殊属性があります)。区別するために System Descriptor と呼ばれ、その S ビット(セグメントタイプ)は、セグメント Descriptor か System Descriptor かを区別するために使用されます。
1)オフセット:割り込みサービスルーチン ISR のアドレスを指します。2 つのオフセットはそれぞれ上位 16 ビットと下位 16 ビットを表します。
2)セレクタ:ISR が存在するコードセグメントを指します。
3)タイプ:割り込みゲートのタイプであり、割り込みゲートが割り込みにどのように応答するかを決定します。
4)DPL(Descriptor Privilege Level):割り込みゲートの特権レベルを示します。割り込みゲートとトラップゲートの DPL は、INT n 命令を使用して割り込みまたは例外を引き起こすときにのみチェックされ、ハードウェアによって生成された割り込みや例外はチェックされません。
5)S ビット(Segment Descriptor):この Descriptor がセグメント Descriptor か System Descriptor かを制御します。
6)P(present):この割り込みゲートが有効かどうかを示し、0 に設定すると無効になります。
トラップゲートは割り込みゲートに似ています(以下、形式は完全に同じですが)、例外処理に使用されます。割り込みゲートとトラップゲートの唯一の違いは、プログラムが割り込みゲートを介してジャンプした後、EFLAGS レジスタの IF ビットが自動的にクリアされ、割り込みが無効になることです。トラップゲートにはそのような効果はありません。
I/O アーキテクチャ#
コンピュータが処理するタスクは実際には 2 つだけです:CPU の計算と I/O 操作。I/O は CPU が外部デバイスにアクセスする方法です。デバイスは通常、レジスタとデバイス RAM を介してその機能を CPU に示し、CPU はデバイスのレジスタと RAM を読み書きすることでデバイスへのアクセスやその他の操作を行います。
現代のコンピュータアーキテクチャでは、ポートI/O は次第に廃止されており、特にほとんどの x86 システムでは、MMIO が主流の I/O アクセス方式となっており、ほぼすべての外部デバイス(グラフィックカード、ネットワークカード、ストレージコントローラなど)は MMIO を介して通信しています。MMIO はより良い性能を提供しますが、いくつかの単純な I/O デバイス(低速のシリアルポートなど)にとっては、ポートI/O が依然として一定の利点を持つ可能性があるため、紹介します。
ポート I/O#
x86 はポート I/Oに 65536 個の 8 ビット I/O ポートを割り当てており、ポート I/Oアドレス空間は 0x0000 から 0xFFFF までで、合計 64KB です。I/O ポートアドレス空間は独立しており、線形アドレス空間や物理アドレス空間の一部ではありません。CPU は I/O ポートアドレスを介してデバイスレジスタにアクセスします。
アクセス方法:CPU はIN
命令を実行して、指定された I/O ポートからデータをレジスタに読み込み、OUT
命令はレジスタから指定された I/O ポートにデータを書き込みます。たとえば、IN AX, 0x60
は、アドレス0x60
の I/O ポートのデータを AX レジスタに読み込みます。
CPU は特定の信号(たとえば I/O 信号)を介して I/O 操作とメモリ操作を区別します。2 つまたは 4 つの連続した 8 ビット I/O ポートは、16 ビットまたは 32 ビットの I/O ポートを構成できます;
制限:ポート I/O の最大の欠点の 1 つは、速度が相対的に遅いことです。なぜなら、各 I/O ポートは独立したパスを介してアクセスされる必要があり、異なるデバイスは割り込みやポーリングを介して処理する必要があるからです。これにより、ポート I/O は現代のコンピュータシステムでの使用が次第に MMIO に置き換えられ、特に効率的なデータ交換が必要なシーンでの使用が減少しています。
MM I/O#
- *Memory map IO:** デバイスレジスタまたはメモリ領域が物理アドレス空間にマッピングされます。** メモリアクセス命令(
MOV
など)を介してデバイスレジスタやデバイス RAM にアクセスでき、特別な I/O 命令は必要ありません。
現代の CPU とシステムは効率的なメモリアクセスをサポートしているため、MMIO はポート I/O よりも高いアクセス速度と大きな帯域幅を提供できます。また、標準のメモリアクセス命令を使用するため、プログラミングとハードウェア設計が簡素化されます。
アクセス方法:たとえば、あるデバイスのレジスタが物理アドレス0xA0000
にマッピングされている場合、MOV AX, [0xA0000]
を実行することで、そのデバイスレジスタの内容を読み取ることができます。
I/O レジスタの状態は外部デバイスの状態を反映しているため、MMIO アドレス領域は通常、CPU のキャッシュによる最適化を受けず、キャッシュの不一致を引き起こす可能性があります。特に、TLB(Translation Lookaside Buffer、アドレス変換後備バッファ)にキャッシュすることはできません。これは、MMIO 操作のアクセスが、特に頻繁にアクセスされる I/O デバイスに対して、より高い遅延の影響を受ける可能性があることを意味します。
DMA#
直接メモリアクセス(Direct Memory Access)は、デバイスが CPU をバイパスして直接メモリにデータをコピーまたは読み取ることを許可します。デバイスがメモリにデータをコピーする際に CPU を経由すると、CPU には大量の割り込み負荷がかかり、割り込み処理中は CPU が他のタスクを使用できなくなり、システムの性能向上に不利です。DMA を使用することで、CPU は初期化のみを担当し、転送動作は DMA コントローラ(DMAC)が行います。
DMA 転送プロセス:
DMA 転送を実現する際、DMAC はバスを直接制御します。DMA 転送の前に、CPU はバス制御権を DMAC に渡し、DMA 転送が終了した後、DMAC はすぐにバス制御権を CPU に返します。
1)DMA 要求:CPU は DMAC を初期化し、I/O ポートに操作命令を発信し、I/O ポートが DMA 要求を提出します。
2)DMA 応答:DMAC は DMA 要求の優先度判別とマスク判別を行い、その後バス裁定ロジックにバス要求を提出します。CPU が現在のバスサイクルを完了した後、バス制御権を解放します。この時、バス裁定ロジックはバス応答を発信し、DMA が応答されたことを示し、DMAC を介して I/O ポートに DMA 転送を開始するよう通知します。
3)DMA 転送:DMAC がバス制御権を取得した後、CPU はサスペンドするか、内部操作のみを実行し、DMAC が読み取り / 書き込み命令を発信し、RAM と I/O ポート間で DMA 転送を直接制御します。
4)DMA 終了:指定されたバッチデータ転送が完了した後、DMAC はバス制御権を解放し、I/O ポートに終了信号を発信します。I/O ポートが終了信号を受信すると、I/O デバイスの動作を停止し、CPU に割り込み要求を提出し、CPU は今回の DMA 転送操作の正確性を判断するコードを実行し、非介入状態から退出します。
DMA は CPU が転送を直接制御する必要がなく、割り込み処理方式のように現場を保持し、復元するプロセスもありません。ハードウェア(DMAC)を介して RAM と I/O デバイス間に直接データを転送する通路を開くことで、CPU の効率を大幅に向上させます。DMA 操作がアクセスする必要があるのは連続した物理メモリであることに注意が必要です。
クロック#
OS 内の多くのイベントはクロックによって駆動されます。たとえば、プロセススケジューリング、タイマーなどです。
1)周期的クロック(Periodic Timer):最も一般的で、クロックは固定頻度でクロック割り込みを生成します。周期的クロックには通常、カウンタがあり、固定値から 0 まで減少して割り込みを生成するか(PIT(Programmable Interrupt Timer、プログラム可能な割り込みタイマー)など)、固定値から増加し、特定の閾値に達すると割り込みを生成し、自動的に閾値を固定値増加させ、カウンタを継続的に増加させます(HPET(High Precision Event Timer、高精度イベントタイマー)など)。
2)ワンショットタイマー(One-shot Timer):ほとんどのクロックはこの方式に設定できます。PIT や HPET のように、閾値に達すると割り込みを生成する動作は周期的クロックに似ていますが、割り込みを生成した後、閾値は自動的に増加せず、ソフトウェア(タイマー割り込み処理関数など)がその閾値を増加させる必要があります。これにより、ソフトウェアは次回のクロック割り込みの到来時間を動的に調整する能力を持つことができます。
x86 は PIT、RTC(Real Time Clock、リアルタイムクロック)、TSC(Time Stamp Counter、タイムスタンプカウンタ)、LAPIC Timer、HPET など、さまざまなクロックを提供します。OS は必要に応じてこれらの中の 1 つまたは複数を使用できますが、同時に複数のクロックを使用すると、過剰なクロック割り込みが発生し、システムの性能に影響を与える可能性があります。高精度のクロックが利用可能な場合、現代のオペレーティングシステムは通常、低精度のクロックを無効にし、必要に応じて高精度のクロックを使用して低精度のクロックをシミュレートします。
X64 レジスタ#
一般レジスタ#
互換性のある x86-32 汎用レジスタ | 関数呼び出しプロセスで一時変数や操作スタックのポインタなどを保存 |
---|---|
eax | 算術累加器で、通常は加算を実行し、関数呼び出しの戻り値もここに格納されます。 |
ebx | データストレージ |
ecx | 通常はカウンタとして使用され、for ループなどに使用されます。 |
edx | I/O ポインタ |
esi | 文字列操作時に目的アドレスを格納し、edi と組み合わせて文字列コピーなどの操作を実行します。 |
edi | 文字列操作時にデータソースアドレスを格納します。 |
esp | スタックポインタレジスタ(Stack Pointer):スタックのトップを指します。 |
ebp | ベースポインタレジスタ(Base Pointer):スタックの底を指し、通常は ebp + オフセットの形式で関数のローカル変数をスタック内に配置します。 |
r8d~r15d | x64 拡張の 8 つのレジスタの下位部分 |
x64 アーキテクチャでは、上記の汎用レジスタが 64 ビットバージョンに拡張され、名前も更新されました。32 ビットモードプログラムとの互換性を保つため、上記の名前を使用することも可能であり、これは 64 ビットレジスタの下位 32 ビットにアクセスすることに相当します。
64 ビット汎用レジスタ | 機能説明 |
---|---|
rax | 通常、関数呼び出しの戻り値を格納するために使用されます。 |
rsp | スタックのトップを指し、スタックの最上部を指します。 |
rdi | 最初の引数 |
rsi | 2 番目の引数 |
rdx | 3 番目の引数 |
rcx | 4 番目の引数 |
r8 | 5 番目の引数 |
r9 | 6 番目の引数 |
rbx | データストレージ、Callee Save 原則に従います。 |
rbp | データストレージ、Callee Save 原則に従います。 |
r12~r15 | データストレージ、Callee Save 原則に従います。 |
r10~r11 | データストレージ、Caller Save 原則に従います。 |
レジスタによる引数の伝達は高速で、メモリの読み書き回数を減らすことができます。CPU 命令を生成する際に、スタックを使用するかレジスタを使用するかが決定されます。
32 ビット時代は汎用レジスタが少なく、関数呼び出し時には引数の大部分がスレッドのスタックを介して伝達されます(レジスタを使用して伝達することもあります。たとえば、C++ のthis ポインタは ecx レジスタを使用して伝達されます)。
x64 時代には、レジスタリソースが豊富になり、引数の伝達はほとんどがレジスタを使用して行われます。
ステータスレジスタ / RFLAGS#
フラグレジスタと翻訳されることもあります。内部には多くのフラグビットがあり、CPU が命令を実行する過程での一連の状態を記録します。これらのフラグのほとんどは CPU によって自動的に設定および変更されます。x64 アーキテクチャでは、元の eflags レジスタが 64 ビットの rflags にアップグレードされましたが、その上位 32 ビットには新しい機能は追加されず、将来の使用のために保持されています。
ステータスレジスタビット | 機能説明 |
---|---|
CF | キャリーフラグ |
PF | 奇偶フラグ |
ZF | ゼロフラグ |
SF | 符号フラグ |
OF | 補数オーバーフローフラグ |
TF | トレースフラグ |
IF | 割り込みフラグ |
命令ポインタ / RIP#
ポインタ / 命令レジスタ;eip は次に実行する命令のアドレスを格納し、CPU の作業はeipが指す命令を繰り返し取得し、その命令を実行し、命令レジスタは次の命令を指し続けます。このプロセスを繰り返します。脆弱性攻撃では、ハッカーは指令レジスタに格納されている次の命令のアドレスを変更し、悪意のあるコードを実行できるようにします。
これは PC ポインタではありませんか?
メモリ管理レジスタ#
ディスクリプタテーブルレジスタ#
GDTR | GDT アドレス |
---|---|
LDTR | LDT アドレス |
セグメントレジスタ#
セグメントレジスタ | 論理セグメントセレクタ→ディスクリプタのマッピングを格納 |
---|---|
cs | コードセグメント |
ds | データセグメント |
ss | スタックセグメント |
es | 拡張セグメント |
fs | データセグメント |
gs | データセグメント |
制御レジスタ#
CPU の状態を管理およびチェックするために使用されます。これらのレジスタは CPU の動作モードや特性を決定します。x86-64 の 64 ビットモードでは CR8 が導入され、タスク優先度レジスタ(TPR)として定義されており、オペレーティングシステムは割り込みの優先度に基づいて TPR を使用して、外部割り込みがプロセッサを中断することを許可するかどうかを制御できます。
名前 | 目的 |
---|---|
CR0 | 基本的な CPU 動作フラグ |
CR1 | 予約済み |
CR2 | ページフォルト線形アドレス |
CR3 | 仮想アドレッシング状態 |
CR4 | プロテクトモード動作フラグ |
CR5 | 予約済み |
CR6 | 予約済み |
CR7 | 予約済み |
CR8 | タスク優先度レジスタ(TPR) |
CR9 | 予約済み |
CR10 | 予約済み |
CR11 | 予約済み |
CR12 | 予約済み |
CR13 | 予約済み |
CR14 | 予約済み |
CR15 | 予約済み |
デバッグレジスタ / DR#
ソフトウェアデバッグをサポートするためのレジスタのセットです。プログラムがデバッグ可能であるためには、実行を中断し、再開することができることが重要です。中断された場所が私たちが設定したブレークポイントです。
解釈実行(PHP、Python、JavaScript)や仮想マシン実行(Java)の高級言語では、これは簡単に行えます。なぜなら、これらの実行はすべてインタプリタ / 仮想マシンの制御下にあるからです。C、C++ のような「低レベル」言語では、コードが CPU の機械命令にコンパイルされて実行されるため、CPU がデバッグサポートを提供する必要があります。