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 が渡した仮想アドレスを物理アドレスに変換した後、外部アドレスバスを介してメモリにアクセスします。外部アドレスバスのビット数は物理アドレス空間のサイズを決定し、外部アドレスバスのビット数は内部アドレスバスのビット数以上でなければなりません。
メモリ管理ユニット(MMU)には、基底レジスタbase
があります:プロセス空間 0 のアドレスがマッピングされた物理アドレスを保存します。MMU は次の作業を行います:
physical address = virtual address + base;
プロセス空間はゼロから始まり、仮想アドレスはプロセス空間のオフセットに相当します。
線形アドレス空間#
線形アドレス空間のサイズは、CPU の内部アドレスバスのビット数によって決まります。内部アドレスバスは CPU の実行ユニットに接続されており、内部アドレスバスのビット数は CPU のビット数と一致することが多いです。線形アドレス空間は、各プログラムが自分だけの仮想アドレス空間を持っていると考えるものです。線形アドレス空間は、物理アドレス空間の一部または全体にマッピングされます。1 つのハードウェアプラットフォーム上には複数の線形アドレス空間が存在する可能性があります。
ページングメカニズムが有効でない場合、線形アドレスと物理アドレスは同じです。CPU がページングメカニズムを使用する場合、線形アドレスを物理アドレスに変換する必要があります。
論理アドレス / セグメントアドレス#
x86 初期のセグメントメカニズムの歴史的負担;論理アドレスはプログラムが直接使用するアドレスです;16 ビットのセグメントインデックス子 + 32 ビットまたは 64 ビットのオフセットで構成されます。以下の文のポインタ変数 p が保存しているのは、変数 a の論理アドレスのオフセットであり、リンカとローダーはそのオフセットが所在するセグメントインデックス子を割り当てます。CPU はセグメントレジスタまたはインデックス子に基づいてマッピングテーブル(ディスクリプタテーブル)からセグメントに対応するセグメント基底アドレス、セグメント境界、権限などを取得します。その後、セグメント基底アドレス + オフセットを生成して仮想アドレスを得ます。
int a = 100;
int *p = &a;
セグメンテーションメカニズム#
プロセスデータは論理的にコードセグメント、データセグメント、スタックセグメントなどに分割されます。プログラムはセレクタを使用して各論理セグメントを識別し、オフセットを使用してセグメント内のデータの位置を示します。
各論理セグメントは仮想アドレス空間のセグメント基底 + セグメント境界で記述されるメモリブロックにマッピングされます。プログラムがメモリデータにアクセスする際には、変数の論理アドレスセレクタ + オフセットを線形アドレス base + オフセットに変換する必要があります:
セグメントレジスタはセレクタ→ディスクリプタの関係をキャッシュします。ディスクリプタにはセグメント基底とさまざまな属性、権限フラグがあります;
1)属性、アクセス権などのチェックを行います;
2)DS データセグメントレジスタの後半のディスクリプタ部分から、そのセグメントがマッピングされた仮想アドレス空間の基底 base を取得します;
3)変数のセグメントオフセットとそのセグメントの線形空間内の基底 base を加算して、その変数の線形アドレスを取得します。
セグメントレジスタキャッシュがヒットしない場合:
プロセスのロード段階で、LDT のセグメントマッピング関係データ構造が作成され、LDT アドレスが GDT に登録された後、そのプロセスの LDT のセレクタは GDT をインデックスし、LDT のディスクリプタによって記述されたテーブルアドレスを取得し、セレクタ→ディスクリプタの関係を LDTR レジスタにキャッシュします。さらに、そのプロセスの CS、DS、SS が対応するセレクタにロードされ、CPU はセレクタの TI フィールドを使用して対応するディスクリプタテーブルをインデックスし、対応するディスクリプタを取得し、セレクタ→ディスクリプタの関係を CS、DS、SS の対応するセグメントレジスタにキャッシュします。
ディスクリプタ#
ディスクリプタはセグメントの基底、長さ、およびさまざまな属性(読み取り / 書き込み、アクセス権など)を記述します:
1)Base フィールドはそのセグメントの基底を記述します。
2)Limit フィールドはそのセグメントの長さを記述します。
3)DPL フィールドはコードがこのセグメントにアクセスするために必要な最小権限を示します。
CPU がセグメントに対応するディスクリプタを取得した後、ディスクリプタ内のさまざまな属性フィールドを使用してアクセスをチェックします。一旦アクセスが合法であることが確認されると、CPU はディスクリプタ内の base(すなわちセグメントがマッピングされた仮想アドレス空間の基底)とプログラム内の論理アドレスのオフセットを加算します。これにより、論理アドレスに対応する線形アドレスが得られます。
セレクタ & GDT/LDT#
セレクタ:
プログラムは論理アドレスのオフセット部分のみを保存および使用します。セレクタはプログラムから見える論理アドレスの構成要素であり、その変更と割り当てはリンカとローダーによって行われ、ディスクリプタテーブルをインデックスしてセグメントに対応するディスクリプタを取得するために使用されます:
1)Index:ディスクリプタテーブルのインデックス。
2)TI:GDT または LDT をインデックスすることを示します:
a. TI=0 の場合、グローバルディスクリプタテーブルをインデックスします;
b. TI=1 の場合、ローカルディスクリプタテーブルをインデックスします。
3)RPL:要求された特権レベル。RPL はセグメント選択レジスタの下位 2 ビットに存在し、プログラムがセグメントにアクセスする際に追加のチェックを行います。
GDT/LDT:
システムには、すべてのプロセスがアクセスできるグローバルディスクリプタテーブルが少なくとも 1 つ存在します。システムには、特定のプロセスがプライベートに使用できるローカルディスクリプタテーブルが 1 つ以上存在する可能性があります。これらは単にセレクタ→ディスクリプタのマッピングテーブルであり、ほとんどは線形リストで実装されています:
1)GDT はメモリ内の位置が基底アドレス Base とテーブル境界 Limit で記述されます。複数のプロセスが共有する必要があるセグメントのマッピング関係を格納します。例えば、カーネルコードセグメント、共有ライブラリセグメント、カーネルデータセグメントなど。
2)LDT はプロセス間で共有する必要のないプライベートセグメントのマッピング関係を格納します。例えば、ユーザコードセグメント、ユーザデータセグメント、ヒープ、スタックなど。LDT はメモリ内の位置が GDT 内のディスクリプタによって記述され、システム内にいくつの LDT が存在するかは GDT 内にいくつの対応するディスクリプタがあるかによります。
3)GDTR と LDTR レジスタ:これらの 2 つのレジスタは、CPU が GDT/LDT マッピングテーブルを迅速に見つけるのを助けます:
a. GDTR:GDT 基底アドレス Base + テーブル境界 Limit
b. LDTR:LDT はメモリ内の位置が GDT 内のディスクリプタによって記述されます。ディスクリプタは LDTR にキャッシュされるため、LDTR の構造はセグメントレジスタと同じです(セレクタ + ディスクリプタ)。
セレクタを使用して GDT/LDT をインデックスするプロセスは以下の図のようになります:
セレクタの TI フィールドは GDT または LDT をインデックスすることを示します。LGDT/SGDT 命令を使用して GDTR の読み書きを行い、マッピングテーブルのアドレスを取得します。プロセス切り替えを行う際、LDTR の値は新しいプロセスに対応する LDTR に変更されます。
セグメントレジスタ#
毎回セレクタを使用して GDT/LDT をインデックスし、論理セグメントのマッピングされた仮想アドレスを取得します。このタイプのオーバーヘッド最適化:スペースを時間に交換することは、私たちには非常に馴染みがあります。x86 は 6 つの 16 ビットセグメントレジスタを提供しており、各セグメントレジスタはセレクタの後にディスクリプタレジスタを追加して論理セグメントのマッピングされた仮想アドレスディスクリプタをキャッシュします:
1)CS(コードセグメント):コードセグメントのセレクタ→ディスクリプタを保存します。
2)DS(データセグメント):データセグメントのセレクタ→ディスクリプタを保存します。
3)SS(スタックセグメント):スタックのセレクタ→ディスクリプタを保存します。
4〜6)ES、FS、GS:追加の 3 つのデータセグメントのセレクタ→ディスクリプタを保存できます。プログラムは自由に使用できます。
セグメントレジスタが新しいセレクタをロードすると、CPU は自動的にそのセレクタがインデックスされたディスクリプタを不可視のディスクリプタレジスタにキャッシュします。つまり、CPU はセグメントレジスタを更新する際にのみディスクリプタテーブルをインデックスします(例えば、バインドされたスレッドを切り替える際にセグメントレジスタが更新されます)。
ページングメカニズム#
仮想アドレス空間を複数のページに分割し、物理アドレス空間も同様に分割され、メモリの断片化を減少させます;
ページングメカニズムは、物理メモリが不足しているときに、あまり使用されないページをディスクのスワップスペース(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 を更新します。
ページテーブル#
ページテーブルデータ構造は、仮想フレーム番号→物理フレーム番号のマッピング関係を格納します。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 段階ページテーブルを 2 ビットでエンコードし、2 段階ページテーブルを 9 ビット、3 段階ページテーブルを 9 ビットでエンコードします。これにより、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(プログラマブル割り込みコントローラ)#
プログラマブル割り込みコントローラ PIC は、広く使用されている最初の割り込みコントローラであり、PIC は UP(単一プロセッサ)プラットフォームでのみ機能し、MP(マルチプロセッサ)プラットフォームでは使用できません。8 つの割り込みピン(IR0〜IR7)があり、外部デバイスに接続されています。外部デバイスが CPU の処理を必要とする場合、対応する割り込みラインを介して信号を送信し、割り込みをトリガーします。
主なレジスタ:
1)IRR(割り込み要求レジスタ): 現在要求されている割り込みを記録します。外部デバイスが IR 割り込み要求を発信し、その要求がマスクされていない場合、対応する IRR の位置に 1 が設定されます。
2)ISR(サービス中レジスタ): 現在処理中の割り込みを記録します。CPU が応答し、割り込みの処理を開始すると、ISR の対応する割り込み位置が 1 に設定されます。
3)IMR(割り込みマスクレジスタ): 特定の割り込みラインをマスクします。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(割り込み終了)レジスタに書き込むことで PIC に割り込み処理が完了したことを通知し、PIC は ISR の対応するビットをクリアします。
APIC(高度なプログラマブル割り込みコントローラ)#
マルチプロセッサ(MP)システムでは、APIC システムにより複数の CPU が協調して作業し、割り込み処理のタスクを共有できます。各 CPU の LAPIC は、IPI メカニズムを介して通信および協力できます。この割り込み制御メカニズムは、高効率の割り込み処理をサポートするだけでなく、マルチコアプロセッサシステムの割り込みバランスを最適化します。
構成:
1)LAPIC(ローカル APIC):各 CPU コアには LAPIC が装備されており、ローカル割り込み信号を処理します。IRR(割り込み要求レジスタ)を介して現在の割り込み要求の状態を保存し、ISR(割り込みサービスレジスタ)を介して処理された割り込みフラグを保存します。CPU は EOI(割り込み終了)レジスタに書き込むことで、LAPIC にその割り込みが処理されたことを通知し、他の割り込みの処理を許可します。
2)IOAPIC(I/O APIC):通常、南橋チップ(または低速 I/O コントローラ)に位置し、外部デバイスの割り込み要求を受信し、それを特定の CPU の LAPIC に転送します。IOAPIC には通常、複数の割り込み入力ピン(通常は 24 個)があり、各ピンは外部デバイスに接続できます。
プロセッサ間割り込み(IPI):IPI(Inter-Processor Interrupt)は、1 つの CPU が他の CPU に割り込み信号を送信できるようにします。これは、マルチコアシステムにおけるプロセスの移動、負荷バランス、TLB のフラッシュなどの操作に非常に重要です。LAPIC のICR(割り込みコマンドレジスタ)を介して、システムはターゲット CPU を指定し、IPI 割り込みを送信できます。
割り込み伝達プロセス:
1)外部デバイスが割り込みをトリガーすると、IOAPIC は割り込み要求信号を受信します。IOAPIC は **PRT(プログラマブルリダイレクションテーブル)を使用して割り込み要求のルーティング情報を検索します。
2)PRT の設定に基づいて、IOAPIC は割り込み情報をRTE(リダイレクションテーブルエントリ)** としてフォーマットし、システムバスに渡します。
3)システムバスは、ターゲット CPU の LAPIC に割り込みメッセージを送信します。
4)LAPIC は割り込みメッセージを受信した後、IRR レジスタの情報に基づいて処理を行うかどうかを決定します。その割り込みが処理条件を満たす場合、LAPIC は ISR レジスタをトリガーし、最終的に割り込みをプロセッサに渡します。
例外アーキテクチャ#
割り込みは外部デバイスによって生成され、CPU の現在実行中の命令とは無関係です。例外は CPU 内部で生成され、その原因は CPU が現在実行中の命令に問題があることです。
例外の発生原因は深刻度によって分類されます:
1)エラー(Fault):何らかのエラー状況によって引き起こされ、一般的にはエラー処理プログラムによって修正可能です。エラーが発生すると、プロセッサは制御を対応する処理プログラムに渡します。前述のページフォルトはこのカテゴリに属します。
2)トラップ(Trap):特定の命令を実行した後に引き起こされる例外です。トラップは意図的な例外であり、トラップの最も重要な用途は、ユーザープログラムとカーネルの間にプロセスのようなインターフェース(すなわちシステムコール)を提供することです。Linux でシステムコールを実現するために使用される INT 80 命令はこのカテゴリに属します。
3)終了(Abort):深刻な回復不可能なエラーを指し、プログラムの終了を引き起こします。典型的なものは、いくつかのハードウェアエラーです。
IDT#
CPU は割り込みディスクリプタテーブル(IDT)を介して割り込みまたは例外ベクタ番号に対応する処理ルーチンのエントリ線形アドレスを検索できます;
IDT データ構造はベクタ→ディスクリプタのマッピング関係を格納します。ディスクリプタには処理ルーチンが所在するセグメントのセレクタとセグメント内のオフセット、さまざまな属性フラグが含まれています;
IDT テーブル基底はIDTR
レジスタに格納されています:
1)Base:IDT 基底アドレス
2)Limit:IDT テーブルの最大バイト数(通常は 8 バイトアライン)
割り込みゲート & トラップゲート#
割り込みゲートは IDT 内のディスクリプタであり、特定の割り込みまたは例外が発生したときに、どの割り込みサービスルーチン ISR にジャンプするべきかを記述します。その形式はセグメントディスクリプタに似ています(ただし、いくつかの特殊な属性もあります);区別するために、これはシステムディスクリプタと呼ばれ、その S ビット(セグメントタイプ)はセグメントディスクリプタとシステムディスクリプタを区別するために使用されます。
1)オフセット:割り込みサービスルーチン ISR のアドレスを指します;2 つのオフセットはそれぞれ上位 16 ビットと下位 16 ビットを示します。
2)セレクタ:ISR が所在するコードセグメントを指します。
3)タイプ:割り込みゲートのタイプであり、割り込みゲートがどのように割り込みに応答するかを決定します。
4)DPL(ディスクリプタ特権レベル):割り込みゲートの特権レベルを示します;割り込みゲートとトラップゲートの DPL は、INT n 命令によって割り込みまたは例外が引き起こされたときのみチェックされ、ハードウェアによって生成された割り込みまたは例外はチェックされません。
5)S ビット(セグメントディスクリプタ):このディスクリプタがセグメントディスクリプタかシステムディスクリプタかを制御します;
6)P(存在):この割り込みゲートが有効かどうかを示し、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#
- * メモリマップ I/O:** デバイスレジスタまたはメモリ領域が物理アドレス空間にマッピングされます。** メモリアクセス命令(例えば
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(プログラマブル割り込みタイマー))か、固定値から増加し、ある閾値に達したときに割り込みを生成し、自動的に閾値を固定値だけ増加させ、カウンタは引き続き増加します(例えば HPET(高精度イベントタイマー))。
2)単発計時クロック(One-shot Timer):ほとんどのクロックはこの方式に設定可能で、PIT や HPET のように、閾値に達したときに割り込みを生成する周期的クロックと似た動作をしますが、割り込みを生成した後、閾値は自動的に増加せず、クロック割り込み処理関数などのソフトウェアによってその閾値を増加させる必要があります。これにより、ソフトウェアは次回のクロック割り込みの到来時間を動的に調整する能力を得ることができます。
x86 は PIT、RTC(リアルタイムクロック)、TSC(タイムスタンプカウンタ)、LAPIC タイマー、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 | 第二引数 |
rdx | 第三引数 |
rcx | 第四引数 |
r8 | 第五引数 |
r9 | 第六引数 |
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が指す命令を取り出し、その命令を実行し、命令レジスタは次の命令を指し続けるというプロセスを繰り返します。x64 アーキテクチャでは、32 ビットの eip が 64 ビットのripレジスタにアップグレードされます。
脆弱性攻撃では、ハッカーは指令レジスタに格納された次の命令のアドレスを変更するために多くの努力をし、悪意のあるコードを実行できるようにします。
これは 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 がデバッグサポートを提供する必要があります。