顯示具有 計算機結構 標籤的文章。 顯示所有文章
顯示具有 計算機結構 標籤的文章。 顯示所有文章

2008年2月7日 星期四

IA-32 Architecture (4/4)

要用那個 Segment Register ?

在 IA-32 架構中,一共有六個 segment register,而這幾個 segment register 又有個別不同的用途,使用上可以根據所要進行的操作來決定要用那個 register。

取得指令

當要讀取在記憶體中的指令時,CS register 便可提供指令所在的 segment base address,而 offset 部分則是由 IP(或是 EIP) register 所提供。

因此,指令所在的記憶體位址可以用下面的形式來表示:
CS:(E)IP

stack 的操作

當 CPU 進行 stack 的操作時(例如:push、pop),SS register 就會扮演 segment base address 的角色,而 offset 的部分則是 SP(或是 ESP) register。

存取資料

若存取記憶體是為了要讀寫資料的話,則預設由 DS 提供資料所在的 segment base address,而 offset 的部分則會根據定址模式的不同會有不同。


Input / Output

I/O 裝置相當多,不過大致可分為三種,分別是:
  1. input device
    這一類的裝置包含滑鼠、鍵盤.... 等等。
  2. output device
    這一類裝置包含螢幕、印表機 .... 等等。
  3. input/output device
    這一類裝置的代表則是磁碟機。

I/O 裝置與系統的溝通,都會被包含在 system bus 中;而為了減少因為眾多 I/O 連結至系統造成實作複雜、效能低落的情況發生,在 system bus 與 I/O 裝置之間加上了一個 I/O controller,以下用一張圖來說明:


因為若是讓 I/O 裝置直接與 system bus 銜接,CPU 就必須要了解並知道如何與 I/O 裝置進行溝通,因此效能肯定低落;透過 I/O controller,讓 I/O 裝置到 system bus 的訊號可以有一定的規範,CPU 就可以專心處理使用者的程式而非這些繁瑣的 I/O 工作。


一般在 I/O controller 中會有三個 register,分別是 data register、status register、command register。 若 CPU 要與 I/O 裝置進行溝通,就僅會跟相對應該裝置的 controller 進行溝通,而不是以直接的方式。

假設以送一個字元到印表機列印為例,會進行以下步驟:
  1. 一開始就會檢查 status register,確認印表機是開機或是關機、忙碌或是閒置、紙張是否用光 .... 等等
  2. 在 status register 中,bit 4 可用來判斷是否開機、bit 5 可用來判斷紙張是否用光、bit 7 可用來判斷是否忙碌 .... 等等
  3. data register 則是保存所要列印的資料,最後 command register 中則是包含 CPU 所要傳送至印表機的指令

而 CPU 是如何與 I/O controller 內部的 register 進行溝通呢? 答案是透過 I/O ports,而 I/O port 即為 I/O controller 內部 register 所在的位址。

接著說明 mapping I/O port 的方式,一共有兩種,分別是:
  1. memory-mapped I/O
    每個 I/O port 都會有其對應的記憶體位址。
  2. isolated I/O
    I/O address 空間與一般程式所使用的記憶體是分開的,自己獨立成為 I/O address space。在 IA-32 架構中提供了 64 KB 的空間作為 I/O address space 之用,可以用在 8-bit、16-bit、32-bit 的 I/O port 上,不論其使用方式,只要不超過 64 KB 的空間即可。

存取 I/O 裝置的方式

撰寫組合語言,可以直接透過 I/O controller 與 I/O 裝置進行歐通,但其實這樣是很辛苦的,因為有許多很 routine 的動作必須要完成(一般這是由 device driver 負責的),因此不但不方便而且也容易出錯,以磁碟裝置為例,若是沒寫好,說不定還會把資料全毀也不一定。

而為了避免這個情況發生,OS 通常都會提供 I/O 的標準存取方式;在 Linux 中稱為 system call,在 windows 中則稱為 interrupt

IA-32 Architecture (3/4) - Real Mode Memory Architecture

在 real-mode 中,IA-32 的處理器就會使用 8086 的模式進行運作,其中差異最大的,當然就是僅有 20-bit address bus,定址空間僅有 1 MB(範圍介於 0x00000 ~ 0xFFFFF)。

由於 8086 系列的 CPU 中 register 的長度僅有 16 bits,如果僅用單一個 register 儲存記憶體位址,最大僅能表示到 64 KB(216 bytes),因此在 1 MB 的記憶體空間中,每一段大小都是 64 KB,並連續組合而成的。

而僅有 16-bit register 要如何表現 20-bit 長度的記憶體位址呢? 以下先用一張圖來表示:


左邊是可以透過 register 呈現出來的位址長度(僅有 16 bits),而右邊則是 20 bits 的定址長度,而問題在於如何將 16-bit 轉換為 20-bit 呢??

顯而易見的,每表示一個位址就必須要兩個 register 來作組合,其中一個作為 segment base,另一個則為 offset,以下用一張圖來說明:


接著說明位址轉換的步驟:
  1. 由於 register 僅有 16 bits 的關係,因此在 20 bits 的位址中,僅能表現前面的 16 bits,最後的 4 個 bit 則一律補 0,透過此方式來將 16-bit 轉換為 20-bit
  2. 接著加上另外一個 register 所儲存的 offset 值

透過以上這兩個步驟,就可以透過兩個 register 的方式來表示 20-bit 的記憶體位址。

【註】由於 register 僅有 16 bits 的關係,因此每個 segment 啟始位址的最後 2 個 bit 都一定是 0,例如:0x00000、0x00010、0x00020。


透過 segment 的方式,每個 memory address 都可以由 segment base 與 offset 的搭配來進行轉換而取得,一般會以「segment:offset」的形式來呈現,以上面的圖為例,1100:450H 就可以轉換為 0x11450(0x11000 + 0x0450)。

不過有一個地方是必須注意到的,每一組 logical address 都可以轉換為唯一的一組 physical address,但每一個 physical address 不一定可以轉換為為一一組 logical address,以下用圖舉個例子:

從圖中可以看出,原本兩個不同的 segment(0x1000 與 0x1200),但是加上不同的 offset(0x20A9 與 0xA9) 後,都產生出相同的 physical address(0x120A9)。

由於每個 segment 的大小不一定,因此擁有不同的 offset 也是會有可能的。

最後,在 real mode,每一個 segment 的基本大小就是 64 KB,若要轉換為更大的 physical memory segment,則必須仰賴 offset 的設定。

IA-32 Architecture (2/4) - Protected Mode Memory Architecture

簡介

在 IA-32 架構中,protected mode 是標準的記憶體架構,不但支援 segmentation,還支援 paging。

首先,memory address 的轉換是必須要知道的,以下先用一張圖來說明:


在 protected mode 支援了複雜的 segmentation 機制,其中 segment 單元會將 logical address 轉為 32-bit linear address,而 paging 單元會將 32-bit linear address 轉為 32-bit physical address。

若是沒有 paging 的機制,則 logical address 會直接轉換為 32-bit physical address。

接著以下用一張圖說明 segment 單元將 logical address 轉為 32-bit linear address 的過程:


在 segment register 中儲存的是 segment descriptor table 的索引值,用來取得相對應的 segment descriptor;而 segment descriptor 中則包含了 32-bit segment base address、size、存取權限 .... 等資訊

【註】descriptor table 包含了許多 segment descriptor,而每一個 segment descriptor 都有其 index 作為識別。

除了之外,為了將 logical address 轉為相對應的 linear address,還需要加入一個 offset 值,而 offset 則可以是 16-bit 或是 32-bit 的數值。


Segment Registers

每一個 segment register 都分為 visible 與 invisible 兩個部分,而我們一般提到的 segment register 都是指其 visible 的部分(長度為 16 bits),也其為 segment selector,以下用一張圖來說明:


其中 segment selector 共分為三個部分:
  1. Index
    使用 index 就可以取得在 local descriptor table(或是 global descriptor table) 中的 segment descriptor。
    根據上面的圖,可以看出 index 的長度為 13 bits,因此可以從 descriptor table 取得 8192(213) 個不同的 segment descriptor
  2. Table Indicator (TI)
    用來決定哪種 descriptor table 要被使用:
    0:global descriptor table
    1:local descriptor table
  3. Requester Privilege Level (RPL)
    設定資料的存取權限之用。數值越小,存取權限越大,例如:Linux kernel 使用 0,而一般程式使用 3。

Segment Descriptors

顧名思義,segment descriptor 所提供的是 segment 的相關屬性,包含了 32-bit base address、20-bit segment 長度、各種控制、狀態資訊 .... 等等,以下用一張圖來表示:


以下針對上圖的各欄位進行說明:

Base Address

由上圖三個紅色部分所組成的,長度為 32-bit,因此最大可以指向實體記憶體空間為 4GB 的 segment。
而若要轉為 linear address,就必須要加上 offset 值。

G(granularity bit)

用來指定 segment size 的單位。

有兩種情況:
  1. G = 0,則每單位為 1 byte
  2. G = 1,則每單位為 4KB。

Segment Limit

由上圖的兩個藍色部分所組成,長度為 20-bit,用來指定每個 segment 的大小,但須搭配上面的 G bit,因此會有兩種情形:
  1. G = 0,則可定址空間為 1 byte ~ 1 MB(220 x 1 byte)
  2. G = 1,則可定址空間為 4 KB ~ 4 GB(220 x 4KB)

D/B bit

若在 CS(code segment) 中,此 bit 稱為 D bit。會有以下兩種情形:
  1. D = 0,則運算元與 offset 長度預設為 16 bits
  2. D = 1,則運算元與 offset 長度預設為 32 bits。

若在 DS(data segment) 中,此 bit 稱為 B bit,作為控制 stack 的長度與 stack pointer 之用。會有以下兩種情形:
  1. B = 0,則 stack 相關的操作會使用 SP register,而 stack 空間上限為 0xFFFF
  2. B = 1,則 stack 相關的操作會使用 ESP register,而 stack 空間上限為 0xFFFFFFFF。

一般來說,在 protected mode 中,B = 1(set);若在 real mode 中,則 B = 0(clear)。

S bit

用來判斷 segment 屬於 system segment 或是 application segment。會有以下兩種情形:
  1. S = 0,則屬於 system segment
  2. S = 1,則屬於 application(code or data) segment。

DPL(Descriptor Privilege Level)

定義 segment 的權限等級。

Type

用來判斷 segment 的類型。

以 application segment 為例,分為 code segment 與 data segment。

假設是 data segment(儲存資料的記憶體位址),Type 可以將其定義為 read-only、read-write .... 等等不同類型;若是 code segment(儲存指令的記憶體位址),則 Type 可以定義為 execute-only、execute/read-only .... 等等不同類型。

P bit

用來判斷是否此 segment 是存在的的。

假設 P = 1,當有 segment selector 讀進此 segment register 時,CPU 會產生 segment-not-present 的例外。


Segment Descriptor Table

segment descriptor table 即為儲存 segment descriptor 的陣列,其中的每個 segment descriptor 都有所屬的特定 index。

而 descriptor table 一共可以分為以下三種類型:
  1. GDT(Global Descriptor Table)
  2. LDT(Local Descriptor Table)
  3. IDT(Interrupt Descriptor Table)

每個 descriptor table 的大小介於 8 bytes ~ 64 KB 之間。

其中 IDT(Interrupt Descriptor Table) 是作為中斷處理之用;而 GDT(Global Descriptor Table) 與 LDT(Local Descriptor Table) 最多可以包含 213 = 8192 個長度為 8 bits 的 segment descriptor。

另外,每個 descriptor table 都有一個相對應的 register,而這個 register 作用是在於儲存 32-bit 的 linear base address 與 16-bit 的 descriptor table 大小,分別是:
  • GDT => GDTR
  • LDT => LDTR

這兩個 register 有指令可以使用他們(load => lgdt、lldt,store => sgdt、sldt),不過通常這個部分都是由 OS 來作。

最後描述一下 GDT 與 LDT 的差異所在:
  • GDT(Global Descriptor Table)
    數量只有一個,所有在系統中的 task 都可以使用此 table 中的 descriptor,通常 GDT 中所包含的都是給 OS 所用的 code 及 data。
  • LDT(Local Descriptor Table)
    數量可以有多個,每個 LDT 中所包含的 descriptor 都是屬於某支程式,而有些 LDT 的 descriptor 可能包含 code、data、stack .... 等等。


Segmentation Models

在 IA-32 架構下,單一 segment 最大可以擴展到 4GB。若是將 segment base address 設定為 0,並設定其 size 為 4 GB,就可以達成 segment 的大小為 4 GB 的目的,而這種作法在 UNIX 或 Linux 都見的到,稱為 flat model

另外還有一種稱為 multisegment model,以下先用一張圖來表示:


一般的程式可能會使用超過 6 個 segment,在正常的情況下,相關的 segment 資訊都會讀入 segment descriptor 中(超過 6 個),但每一次可用的僅有 6 個 segment(因為僅有 6 個 segment register),若要啟用尚未使用的 segment,只要將指向 segment 的 selector 讀入 segment register,CPU 就可以從相對應的 descriptor table 中存取該 segment。

2008年2月5日 星期二

IA-32 Architecture (1/4)

簡介

若要撰寫 assembly,就必須要了解處理器的架構,目前最常見的事 IA(Intel Architecture)-32 架構,市面上的 Pentium、Celeron、Xeon .... 等等 CPU 都是屬於 IA-32 架構,並支援兩種不同的記憶體架構,分別為 real mode 與 protected mode。

關於 80x86 處理器的演化過程以及每個階段所加入的改變,可以參考 小木偶的網頁 - 組合語言準備工作 一文得到詳細的說明。


Processor Execution Cycle

CPU 的執行週期大概可以分為三個階段:
  1. Fetch:從記憶體中取得指令
    放置正確的位址到 address bus 上,並在 control bus 上輸入 memory read 訊號讓指令可以在正確的記憶體中被取出。
  2. Decode:將指令解碼
    此階段必須辨識從記憶體中取出的指令為何,並進行解碼的動作,這裡有相對應的 schema 可以將其轉為 machine language。
  3. Execute:執行指令
    執行指令時,CPU 中的控制電路會負責時間的控制,而 ALU(Arithmetic & Logic Unit) 則會負責針對資料進行數學及邏輯相關運算處理。

這三個階段通常被稱為 fetch-decode-execute cycle(或是 execution cycle),以下用一張簡單的圖來說明:

其實 Execution Cycle 通常不是在主記憶體中完成,而是在 cache 中,因為 cache 的速度比記憶體更快,不過由於這一段(CPU <-> cache <-> memory)是透明的,因此撰寫應用程式時不需要考量到這個。


Process Registers

在 IA-32 架構中,提供了 10 個 32-bit 以及 6 個 16-bit 的 register,根據用途不同可分為 generalcontrolsegment .... 等幾種;其中 general 還細分為 datapointerindex 三種 register。

以下針對不同的 register 進行說明:

Data Registers

首先先用一張圖說明 data register 的組成:


這 4 個 32-bit 的 data register 可以用在數學、邏輯、或是其他不同的處理。且看其結構,可以知道為了與上一代的 CPU 相容的設計:
  1. 4 個 32-bit register:EAX、EBX、ECX、EDX
  2. 8 個 16-bit register:AX、BX、CX、DX
  3. 8 個 8-bit register:AH、AL、BH、BL、CH、CL、DH、DL

每一個 register 都是獨立的,因此可以根據需求決定使用 8-bit、16-bit、或是 32-bit 的 register。

但存取時就必須注意一下,假設要讀取 EAX 中的 lower 16 bits 的資料,可以直接存取 AX,會得到相同的效果(因為 AX 本來就是 EAX lower 16 bit 的部分);若是要存取 AX 中 higher 8 bit 的資料,則可以直接存取 AH(因為 AH 為 AX 的 higher 8 bit)。

一般來說,這幾個 register 進行任何數學、邏輯處理都不會有問題,但還是有特例,例如:進行乘法時必須用到 EAX(或是 AX、或是 AL) register;進行迴圈處理時,ECX(或 CX) 則是儲存迴圈執行次數的 register;這些特例都是必須要注意的。

Pointer & Index Registers

首先用一張圖說明 pointer & index register 的組成:


首先,這兩種 register 與 data register 類似,也是 32-bit 與 16-bit 的組合,使用上就端看所處理的資料長度來進行不同的運用。

index register 通常用於字串處理的指令上,除此之外,可以當作一般的 data register 使用。

pointer register 則是專門負責管理 stack 狀態(其實也可以將其當作 data register 使用,不過還是多用於 stack 上)。

Control Registers

control register 有兩個,分別是 flags register 以及 instruction register,以下用一張圖來表示:


instruction pointer register(有時稱為 program counter register) 的作用在於儲存下一個準備被執行的指令所在的記憶體位址之用,當指令從記憶體中被取出後,instruction pointer register 的內容則會更新為下一個準備執行的指令所在的記憶體位址。

另外若是使用傳送 jump、procedure call、interrupt 等控制訊號,亦會更改 instruction pointer register 的內容。

使用上若是需要 32-bit 長度,可使用 EIP;若是 16-bit 則使用 IP。

flags register 也是相同,包含 32-bit 及 16-bit;以 EFLAGS(32-bit) 來說,包含了 6 個 status flag、1 個 control flag、10 個 system flag(詳細名稱可以參考上圖),而 EFLAGS 中的每個 bit 都是獨立有其意義的,可以是 1(set) 或 0(clear),可以用指令修改,例如:clc 指令可設定 carry flag 為 0,stc 指令則可設定 carry flag 為 1。

但 flag register 通常是用來記錄運算、邏輯處理的狀態之用,以下舉幾個例子來說明:
  1. 進行減法時結果為 0,ZF(zero flag) 會設定為 1
  2. 處理字串時,可以使用 DF(direction flag) 來決定掃瞄字串的方向(forward or backward)
  3. 將 TF(trap falg) 設定為 1,可以讓 CPU 以一行一行的方式執行程式,對於 debug 會有相當幫助
  4. ID(identification flag) 則是用來決定是否支援 CPUID 這個指令

Segment Registers

最後 6 個 16-bit register 就是 segment register,以下用一張圖來說明 segment register 的組成:


在 IA-32 架構下,記憶體會被分為數個 segment,每個 segment 是一小部分的記憶體空間,而 segment register 中儲存的則是指向這些記憶體空間的位址。

但由於只有 6 個 segment register,因此 CPU 最多同一時間僅能存取六個不同的 memory segment。

另外,程式在邏輯上分為兩個部分,分別是 codedata;其中 code 的部分所存的僅有指令,而 data 的部分則是僅有程式所需要的資料。

而 CS(code segment) 中所儲存的正是 code 所在的記憶體位址,而 DS(data segment) 儲存的則是 data 所在的記憶體位址,另外,SS(stack segment) 中儲存的則是指向程式的 stack segment 的記憶體位址。

另外三個 ES、FS、GS,則是額外的 segment register,可以作為類似其他的 segment register 使用,例如:當一個 DS 不足以儲存程式所使用資料的記憶體位址,則可能會借用這三個額外 segment register 來使用。

2008年1月26日 星期六

PC Assembly Language 學習筆記(1) - Computer Organization

Memory (記憶體)

在記憶體中的每個單位為 1 byte,每個 byte 在記憶體中都會被一個唯一的數字所標示出來,此數字即為該 byte 的 memory address(記憶體位址)。

以下用一張圖來表示 memory address:


上圖中,memory address 為 0 的值為 2A,為 1 的值為 45 .... 以此類推;一般來說,memory的使用不會單單只有一個 byte,因為一般的工作都會用到不少的 memory,因此都是一次處理多個 byte。

另外,所有儲存在 memory 中的資料都是數字,而 character(字元) 的呈現則必須仰賴一張對照表將數字轉為字元,其中最有名的即為 ASCII;ASCII 由於每個字元所佔的 memory 為 1 byte,因此可以表現的字元只有 256 個(其實只會用到 7 bits,因此實際僅能呈現 128 的不同字元),而目前越來越流行的 Unicode,則是每個字元所佔的 memory 為 2 bytes,相對的可以呈現的字元又更多,因此常被用來解決多國語言的需求。

以下舉個範例,字元 A 在 ASCII 與 Unicode 的情況下的儲存方式:(0x 開頭表示 Hexadecimal 十六進位)
  • ASCII:0x41
  • Unicode:0x0041


CPU (Central Processing Unit)

在 CPU 中,有一個很重要的部分,稱為 register(暫存器);將特定的資料放到特定的 register,進而執行特殊的指令,這是暫存器的主要用途,當然也可以存放一般資料,而且速度遠遠快過 memory,不過數量有限,使用上必須要注意。

而 CPU 在處理的,稱為 Machine Language(機器語言),這種語言一般人看不懂,通常都是由 compiler 將設計好的程式轉成 Machine Language;一般來說,每種 CPU 都會有他自己的 Machine Language,因此同樣的 Machine Language 在不同架構的 CPU 下是不能正常執行的。


80x86 CPU

x86 架構的 CPU 是目前普及率最高的 CPU,一般的電腦都搭配著這一類的 CPU,以下大概介紹一下這些 CPU:

8088、8086

這一代的 CPU 用於早期的 PC,其中所使用的 register 都是 16 bits,分別是 AX、BX、CX、DX、SI、DI、BP、SP、CS、DS、SS、ES、IP、FLAGS .... 等等。

由於此代 CPU 只能在 real mode 中運作;而在這種模式下,程式最大僅能用到 1MByte 的 memory,且程式中可以存取任何 memory address,甚至是屬於其他程式的 memory address 也可以,因此造成 debug 以及安全上的顧慮。

此外,程式記憶體也被分為多個 segment,其中每一個 segment 不能超過 64K。

80286

比起上一代的 CPU,register 的部分依然只是 16 bits;但此代的 CPU 多了幾個指令,並可以在 16-bit protected mode 中運作;在此種模式下,最大可以用到 16 MByte 的 memory,而且程式之間的 memory address 在存取上不會互相有衝突,但每個 segment 最大還是只有 64K。

80386

此代 CPU 可說是大躍進,除了每個 register(EAX、EBX、ECX、EDX、ESI、EDI、EBP、ESP、EIP、EFLAGS) 都變成 32 bits 外,還另外多了兩個 16 bits 的 register 為 FS 與 GS。

並加入了 32-bit protected mode,在此模式下,最大可以使用到 4 Gbyte 的 memory(此即為 32 bits 作業系統的最大記憶體限制)。

此外,程式依然會被分為多個 segment,但每個 segment 最大可以到 4 GByte。

80486 / Pentium / Pentium Pro

此代 CPU 沒有很特別,主要只是時脈(速度)上的提升。

Pentium MMX

此代的 CPU 加了 MMX(MultiMedia eXtensions) 指令集,用來加速圖形的處理。

Pentium II

此代 CPU 即為 Pentium Pro + MMX 而已,在主要架構上並沒有突破性的改變

Pentium III

僅為時脈(速度)上的提升。


8086 16-bit Registers

在 8086 CPU 中提供了許多 16-bit register,以下分別介紹他們的用途:

general purpose registers

此種 register 有四個,分別是 AX、BX、CX、DX,用來進行處理時存放一般資料用。

其中每一個 16-bit register 都包含了兩個 8-bit register,以下以 AX 為例:

其中 AH 為 higher 8 bits,而 AL 則為 Lower 8 bits。

而有一個觀念必須了解的是,AH 與 AL 實際上是獨立不互相影響的,但對於 AX 來說 AH 與 AL 又是其一部份,這是必須要注意的!

index register

此種 register 有兩種,分別是 SI 與 DI,通常作為 pointer 之用,但也可以拿來跟 general purpose register 一樣使用。

但是與 general purpose register 不一樣的是,index register 並非由兩個 8-bit register 所組成。

base/stack pointer

此種 register 分別為 BP(base pointer) 與 SP(stack pointer),用來指向 machine language stack 中的資料。

segment register

此種 register 有四種,分別是 CS(code segment)、DS(data segment)、SS(stack segment)、ES(extra segment),用來標示 memory 是被哪些不同的程式所使用,其中 ES 是被用來暫時作為 segment register 用。

instruction pointer register

此即為 IP,與 CS 搭配,用來指向下一個 CPU 準備執行的指令所在的 memory address,一般來說,當指令執行後,IP 就會自動指向下一個準備執行的指令所在的 memory address。

FLAGS register

此即為 FLAGS,用來儲存前一個指令執行完後所產生的重要訊息,而這些訊息是以單一 bit 的方式進行儲存。


80386 32-bit Registers

在 80386 CPU 的時代,register 大躍進變為 32 bits。

舉例來說,原本的 AX 變成了 EAX,而為了向下相容的考量,其中 AX 則變為 EAX 的 lower 16 bits,不過需要注意的是,沒有代表 higher 16 bits 的 register,因此沒辦法直接存取 32-bit register 中 higher 16 bits 的部分。

許多原本 16-bit register 都轉變為 32-bit 了,但 segment register 還是有維持原本的 16 bits,另外還多了 FS 與 GS 的 16-bit register,也是作為 segment register 之用。


Real Mode

在 real mode 中,memory 的使用被限制在 1 MByte(220 bytes) 內,可用的 address 範圍為 0x00000 ~ 0xFFFFF。

由 memory address 的可用範圍可知,存取需要 20 bits 的長度,但在 8086 的 CPU 架構中僅有 16-bit register,因此必須使用兩個 16 bits 值的組合來表示一個 memory address。
其中前面的 16 bits 稱為 selector,必須存於 segment register 中,而後面的 16 bits 稱為 offset,其中實際 memory address 的計算方式如下:
16 * selector + offset (即為 selector 補 0 到後方再與 offset 相加)
而 real mode 有幾個問題:
  1. 單一 selector 只能參照到 64K 大小的 memory,但如果程式碼大小超過 64K,就必須強制分成多個片段來執行;同樣的,不只程式碼,連太大的資料也是會有相同問題。

  2. segment address 的衝突問題,舉例來說:04808 可以被以下幾個組合所參照:
    • 047C:0048 (047C0 + 0048 = 04808)
    • 047D:0038 (047D0 + 0038 = 04804)
    • 047E:0028 (047E0 + 0028 = 04808)

由於種種問題,造成在 real mode 中設計程式是一件非常困難的事情。


16-bit Protected Mode

在 80286 CPU 出現後,提供了 16-bit protected mode,其中處理 selector 的部分與 real mode 是完全不同的。

在 real mode 中,selector 用來表示 memory address 的前段部分;而在 protected mode 中,selector 則是 descriptor table 的 index。

說到 program segment,在兩個不同的模式中都必須要做,只是在 real mode 中,program segment 是存於實體記憶體的固定位置上;而在 protected mode 中則不是。

在 protected mode 中儲存 program segment 的方式稱為 virtual memory,基本的觀念即是「僅儲存目前正在執行程式的 code 與 data 在記憶體中,其他程式的 code 與 data 則存放於磁碟機中」,換另外一支程式要跑時,再從磁碟機中移到記憶體中去執行,而這些步驟由 OS 處理,程式中不需考慮到這些實作細節。

在 protected mode 中,所有個 segment 都被視為是 descriptor table 中的一個 entry,此 entry 中包含了許多資訊,包括:是否目前存於記憶體中? 如果在記憶體中,存於那個位置? 存取權限 .... 等等;而每個 entry 的 index 則是 selector 的值,存於 segment register 中。

此外,雖然已經是 protected mode,程式間所使用的 memory 不會互相有影響,但是畢竟 register 還是只有 16 bits,可以處理的資料量還是很少,假設要處理大量的資料依然是一個很大的挑戰。


32-bit Protected Mode

80386 CPU 中出現了 32-bit protected mode,其中與 16-bit protected mode 最主要的差異有兩點:
  1. offset 長度擴充為 32 bits,因此表示每個 segment 最大可以到 4GBytes

  2. 每個 segment 被分為更小的單元,稱為 page,每個 page 大小為 4K。而在 virtual memory 中所處理的單元改為 page 而非之前的 segment;這表示每次程式處理時,只有 segment 中的一部份會在 memory 中,此舉是為了讓 memory 做更有效率的利用。
目前的 Windows 9X, Windows NT/2000/XP, Linux .... 等等,都是屬於 paged 32-bit protected mode。


Interrupts (中斷)

有時候在程式執行的過程中,需要對於特定事件即時回應,而這種機制稱為 interrupt(中斷)

會造成 interrupt 的情況不只一種,可能是滑鼠移動,或是按下鍵盤 .... 等等,而每種情況都會產生不同的 number 作為識別,並將程式的控制權交給 interrupt handler

在實體記憶體的開頭,包含了稱為 interrupt vector 的資訊,並存於 table 中,而每個由 interrupt 所產生的 number 則是包含在此 table 中的 index。

interrupt 有分為兩種,分別是 external interrupt 與 internal interrupt;其中 external interrupt 發生於 CPU 之外,有許多裝置都會產生此種 interrupt,包含滑鼠、鍵盤、磁碟機 ... 等等;而 internal interrupt 則是發生於 CPU 內,有可能是因為發生 error(稱為 traps) 或是 interrupt 指令的執行(稱為 software interrupts)。

一般情況下,中斷處理完後,interrupt handler 會將控制權移回程式中,也會復原在原本 interrupt 發生前所有在 register 中的值,但僅有 traps 例外,會造成程式的中斷。