以下は、第IV章の収穫です。
保護モード
保護モードとは何ですか?保護モードの直接の定義は非常に抽象的なものだそうです、我々としても私たちは私たちのために何ができる保護モードと保護モードを必要とする理由を見てみましょうか?
保護モードは、リアルモードに関連しており、その引数の問題リアルモードのいくつかを解決します。モードに対処する8086CPU、使用説明書、レジスタサイズの下でリアルモードです。
だから、リアルモードの問題は何ですか?なぜ私たちは、保護モデルが必要ですか?
なぜ私たちは、保護モードが必要なのか
同じ特権レベルに属する①リアルモード、ユーザプロセスとシステムプロセスは、対等な立場で、どのユーザがコールを処理できるようになるか、システムプロセスを呼び出すことができます。
②リアルモードで、制限なしで、非常に簡単で、メモリの内容を変更するために、プロセスが物理アドレスである直接アクセス。
③リアルモードは、セグメントベースは、自由にすべてのメモリアクセスを修正することができます。
上記の3つのポイントは、セキュリティ上の問題でも大きな問題です。私たちは、保護モードの「保護」から見ることができ、保護モードで動作しているプログラムをより安全になり、より多くの「セキュリティ」とは、別のプログラムを自由に変更することはありません。
④アクセスメモリ、常に私たちが望むメモリ・アドレスにアクセスするためのセグメントベースを変更する必要が、唯一の64キロバイトのセグメントのサイズので。
⑤メモリサイズは1M、十分です。
⑥1つのプログラムだけ、CPUリソースの浪費を実行することができます。
私たちは本当にでまだ使用CPU使用パターンコンピュータを考える場合④⑥ポイントに使用の問題は、ある、その後、プログラムの低効率がメモリにアクセスする必要がありますどのように自分自身をタイアップしなければならなかったが、また、プログラムを実行するために、完全な、最新のオペレーティングシステム上で実行されている他のプログラムは明らかに考えられません。
ここではこれらの問題を解決する方法を考えることですが、まず①の点、私たちは、それぞれのプロセスが同じ特権レベルではありません与えるオペレーティングシステムは明らかに最高レベルで、ユーザプロセスが低くなり、のようなものです、具体的な内容は、章の後に表示されます。
最初のポイントは②、プロテクトモードでは、この時点でのアドレスにアクセスするプロセスは、最終的な物理アドレスが、仮想アドレスではなく、メモリにアクセスするためのタブを使用しています。また、あなたはページングモードを下げるために、いくつかの最終的な情報を通じて仮想アドレスを物理アドレスに変換する必要があります。また、これは後の章で提供されます。
②③ポイントのために、最も直接的なアプローチは、この目的のために、セグメント記述子の読み取りと書き込みのセクション、CPUおよびオペレーティングシステムに一定の制限を行うことです。記述子は、構成は、セクションに記載されている、を含む情報:セグメント・ベース・アドレス、セグメントリミット、セグメントタイプ、セグメントを読み出し、書き込み可能かどうかを、セグメントの方向(他の拡張に、例えば、下位アドレスに延びるスタック、高いアドレス)と上のようにします。具体的な詳細は、後ほどお話します。
④⑤ポイントのために、CPU 32の開発は、アドレスバスとデータバス32には、より便利に、突然4Gになる子供がメモリアドレス空間にアクセスできるように、また、汎用レジスタ32のサイズに拡張、拡大します単純にすべてのメモリ・アドレスにアクセスすることができる汎用レジスタに依存し、さらにセグメント・ベースなしで、4を残したことはありません。もちろん、保護モードまたは最終的な物理アドレスにアクセスするセグメント・ベース・アドレス内のセグメントを使用して+オフセットアドレスに互換性、CPUであるため、私たちはほとんどの操作を行うことができるために、セグメント・ベースは、セグメントに単独で、0に設定されていますオフセットアドレスは、物理メモリのすべてにアクセスすることができますので、このとしても知られている「フラットモード。」また、CPUはまた、使用手順を展開しない例えばベースアドレッシングもはやのみBXによって制限され、ベースレジスタとしてSPが、すべてのレジスタは、ベースレジスタとしてを通してあり;アドレッシングインデックス付けすることはなくなりSiのみ、インデックスレジスタジに限定されず、SP以外の全ての汎用レジスタに加えて、インデックス・レジスタとして使用することができます。
GDT
セグメント記述子は、特殊なデータ構造は、「グローバルディスクリプタテーブル」と呼ばれる記述子の複数が実際に保存されたセグメント記述子アレイで保持し、情報のセグメントを記述する。これは明らかに登録することはできませんあなただけの大きなメモリ内部に保存することができ、グローバル記述子テーブルを保持しています。しかし、メモリに対して、専用CPUが呼び出されるように、等、L1、L2キャッシュを登録し、またはロット遅い記述子バッファときに、レジスタに格納された使用済みセグメント記述子によって取得されたセグメント記述子の効率を向上させますそして、このようにメモリアドレッシングを高速化、メモリアクセスを減らすために、レジスタから取り出しました。
のは、記述子の構造を説明してみましょう:
下位32ビット0〜15の16ビットの32〜19ビットが記載セグメント境界を達成するために、ブロックの境界を表しています。23ビューG、G = 1の境界値への特異的結合は、セグメント限界粒径が4キロバイト、G = 0で表し、実際のセグメントの範囲=(記述子セグメントの範囲、粒子サイズは1バイトセグメントの範囲であることを示し+1)* -1セグメント限界粒径。
低い32 7から31 32ビット高く、0から16および31から24は、集合的に三箇所に分散する理由の32ビット・セグメント・ベースを記述する?答えは単純であり、互換性の問題は、だけにして、20まで延長セグメント・ベース32、セグメント限界を拡大背面に追加するために、従って異なる場所において散乱されます。
Sは、セグメントは、システム・セグメントまたはセグメント、CPUの目には、システムと呼ばれる使用何か、データと呼ばれる使用何かにすべてのソフトウェアにハードウェアで表します。したがって、コードセグメント、データセグメントなど、スタック・セグメント・データ・セグメントに属する表されるように、S。
タイプタイプは、合計4つの期間を指定しました。唯一のSだけでその意味を入力し、決定しました。図は、異なる意味で、システムとデータのセグメントを入力しています。
私たちは、主にデータセグメントの重要性の種類を見てください。セグメントは、コードセグメントである場合、実行可否を表す、X、R、C、Aの種類、読み取り可能であり、それは訪問するかどうか、同じです。ダンセグメント、実行可否を表すX、W、E、A、タイプ、および書き込み可能である場合、伝播方向は、アクセスされたか否か。
どのような特権レベルセグメントのDPLの代表者が所属。
Pは、メモリセグメントの存在または非存在を表し、セグメント0 =なし、1が存在するセグメントを表しています。
AVLビットオペレーティングシステムの代わりに利用できる、特別な意味自由に使用できません。
Lは、64ビットのコードセグメントまたは32ビットを表します。
D / B. このビットのコードセグメントD、16ビットまたは32ビットの実効アドレス及びオペランドを使用するかどうかを指定するために使用するコードセグメントです。このビットは、スタックセグメントのためのものであるスタック・セグメント・レジスタの使用を指定するか、ESP SP範囲SPレジスタをアドレス登録するために使用されるBは、ESP範囲をアドレッシングレジスタは0xFFFFFFFFのある、0xFFFFであること。
G表すチャプター境界の粒子サイズは4キロバイトまたは1Bです。
グローバル記述子は、複数のプログラムがディスクリプタ・テーブルで独自に定義することができ、一般的なことを示します。私たちはステップの一つは、メモリの操作は、CPUがこの操作が有効であるディスクリプタの情報をチェックする際に、CPUは場所を記述したグローバル・シンボル・テーブルを知らせ、グローバル記述子テーブルをロードすることである保護モードに入ります。
A20アドレス行
在实模式下,A20地址线是默认禁用的,原因是还未进入保护模式之前,地址总线还是要模拟20位的效果,即只保留20位以内的地址,如果地址超过20位,地址就会回绕到0,将地址20位(从0开始算)舍弃,所以要将A20地址线给禁用掉。但进入保护模式后,我们需要恢复地址总线的原貌,即使地址超过20位,地址也不应该回绕到0,所以此时将A20地址线打开,我们就能访问超过20位的地址了。因此,打开A20地址线,是进入保护模式的步骤之一。
CR0的PE位
进入保护模式的最后一个步骤是,打开CR0的PE位,CR0是控制寄存器。控制寄存器是CPU的窗口,它既可以展示CPU的内部状态,也可以控制CPU的运行机制。CR0的第0位,PE位,就是保护模式的开关,我们打开PE位,就是告诉CPU接下来我们要进入保护模式。
进入保护模式
由上面可以知道,进入保护模式的步骤如下:
① 打开A20地址线
② 加载GDT
③ 将CR0的PE位置为1
下面我们开始编写代码:
首先是boot.inc文件的改动,主要定义了描述符的各个位,方便设置整个段描述符。
LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2 DESC_G_4K equ 1_000_000_000_000_000_000_000_00b DESC_D_32 equ 1_000_000_000_000_000_000_000_0b DESC_L equ 0_000_000_000_000_000_000_000b DESC_AVL equ 0_000_000_000_000_000_000_00b DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 DESC_LIMIT_VIDEO2 equ 0000_000_000_000_000_000b DESC_P equ 1_000_000_000_000_000b DESC_DPL_0 equ 00_000_000_000_000_0b DESC_DPL_1 equ 01_000_000_000_000_0b DESC_DPL_2 equ 10_000_000_000_000_0b DESC_DPL_3 equ 11_000_000_000_000_0b DESC_S_CODE equ 1_000_000_000_000b DESC_S_DATA equ DESC_S_CODE DESC_S_sys equ 0_000_000_000_000b DESC_TYPE_CODE equ 1000_0000_0000b DESC_TYPE_DATA equ 0010_0000_0000b DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \ DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \ DESC_P + DESC_DPL_0 + DESC_S_CODE + \ DESC_TYPE_CODE + 0x00 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \ DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \ DESC_P + DESC_DPL_0 + DESC_S_DATA + \ DESC_TYPE_DATA + 0x00 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \ DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + \ DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b RPL0 equ 00b RPL1 equ 01b RPL2 equ 10b RPL3 equ 11b TI_GDT equ 000b TI_LDT equ 100b
下面是loader.S的改动:
%include "boot.inc" section loader vstart=LOADER_BASE_ADDR LOADER_STACK_TOP equ LOADER_BASE_ADDR jmp loader_start ;构建GDT,设置段描述符 ;第一个段描述符不作使用 GDT_BASE: dd 0x0000_0000 dd 0x0000_0000 ;代码段描述符 CODE_DESC: dd 0x0000FFFF dd DESC_CODE_HIGH4 ;数据段、栈段描述符 DATA_STACK_DESC: dd 0x0000FFFF dd DESC_DATA_HIGH4 ;显存段 VIDEO_DESC: dd 0x8000_0007 dd DESC_VIDEO_HIGH4 GDT_SIZE equ $ - GDT_BASE GDT_LIMIT equ GDT_SIZE - 1 ;预留60个描述符 times 60 dq 0 ;设置各个选择子 SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0 SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0 ;用于设置GDTR寄存器的值 gdt_ptr dw GDT_LIMIT dd GDT_BASE loadermsg db '2 loader in real.' loader_start: mov sp,LOADER_BASE_ADDR mov bp, loadermsg mov cx, 17 mov ax, 0x1301 mov bx, 0x001f mov dx, 0x1800 int 0x10 in al,0x92 or al,0000_0010b out 0x92,al ;进入保护模式的三个步骤 lgdt [gdt_ptr] mov eax, cr0 or eax, 0x0000_0001 mov cr0, eax ;刷新流水线 jmp dword SELECTOR_CODE:p_mode_start [bits 32] p_mode_start: mov ax, SELECTOR_DATA mov ds, ax mov es, ax mov ss, ax mov esp, LOADER_STACK_TOP mov ax, SELECTOR_VIDEO mov gs, ax mov byte [gs:160], 'P' jmp $
像之前一节那样编译及加载程序到硬盘后,执行bochs,第二行会显示字符P,最后一行会显示“2 loader in real",结果如下:
另一个值得注意的指令是:jmp dword SELECTOR_CODE:p_mode_start,这个指令是用来刷新流水线的,因为在进入保护模式之前,p_mode_start后面的指令也会被放上流水线,指令会按照16位译码,其实本来应该按照32位译码才能正常执行,所以我们需要清除流水线上的这些指令,保证这些指令按32位译码,这样才能正常地运行下去。