在 Intel 的開發人員手冊中提到,當 CPU 以 64 位元模式執行時,分段機制在大多數情況下實際上已被「關閉」。當程式運作時,等同於直接在線性地址空間中執行。這裡所謂的「關閉」更準確地說是「被略過」(bypass),因為分段機制本質上仍然存在,但透過 flat model 運作,使其對於大多數應用來說幾乎可以忽略。
在 flat model 中,所有的 segment descriptor 的 base address 都設為 0。前面提到過,邏輯地址要轉換成線性地址時,需將有效地址(offset)加上 base address。當 base address 為 0 時,有效地址與線性地址實際上是等價的。因此,雖然分段機制仍在運作,但對於記憶體地址的轉換來說,它可以被視為不存在。
在 flat model 中,所有 segment descriptor 的 base address 都設置為 0,前面說道從邏輯地址轉換到線性地址空間,就是要將有效地址加上 base address,既然所有段選擇器使用的base address都是0,那就表示有效地址和線性地址是完全等價的。因此分段機制雖然還有在工作,但是對於記憶體地址轉換來說是完全可以忽略的。
接下來,我們可以嘗試驗證,在目前的 Linux 作業系統中,分段機制是否確實透過 flat model 被略過。
int main() {
return 0;
}
///////////
(gdb) b main
(gdb) r
(gdb) i r
cs 0x33 51 // 00110 0 11
ss 0x2b 43 // 00101 0 11
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
我們寫了一個簡單的 C 程式,並在執行時暫停,檢視其 cs(code segment)和 ss(stack segment)暫存器。可以看到,它們的 table indicator 都為 0,表示直接使用 GDT(Global Descriptor Table),分別使用 index 6(0x110)和 index 5(0x101)的 segment descriptor。
為了能清楚地看到 GDT 的內容,我們可以透過一個簡單的 Linux kernel module 來讀取並輸出 GDT 的每個段描述符。
#include <linux/module.h>
#include <linux/kernel.h>
#include <asm/desc.h>
#include <asm/io.h>
struct desc_ptr gdtptr;
struct desc_struct *gdt;
int i;
static int __init gdt_reader_init(void)
{
// 取得 GDT 的基址與大小
native_store_gdt(&gdtptr);
printk(KERN_INFO "GDT Size: %d\n", gdtptr.size);
printk(KERN_INFO "GDT Address: %lx\n", gdtptr.address);
// 取得 GDT 的基址
gdt = (struct desc_struct *)gdtptr.address;
// 迴圈讀取 GDT 內的每一個段描述符 (descriptor)
for (i = 0; i < (gdtptr.size / sizeof(struct desc_struct)); i++) {
printk(KERN_INFO "GDT[%d]: Limit 0x%08x, Base 0x%08x\n", i, (gdt[i].limit1 << 16) + gdt[i].limit0, (gdt[i].base2 << 24) + (gdt[i].base1 << 16) + gdt[i].base0);
}
return 0;
}
static void __exit gdt_reader_exit(void)
{
printk(KERN_INFO "GDT reader module exited.\n");
}
module_init(gdt_reader_init);
module_exit(gdt_reader_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A Kernel module to read GDT entries");
這個 kernel module 的作用是讀取 GDT 的基址與大小,然後輸出每一個 Segment Descritopr 的內容。
> demsg
[338486.023621] GDT Size: 127
[338486.023622] GDT Address: fffffe000011e000
[338486.023622] GDT[0]: Limit 0x00000000, Base 0x00000000
[338486.023622] GDT[1]: Limit 0x000fffff, Base 0x00000000
[338486.023623] GDT[2]: Limit 0x000fffff, Base 0x00000000
[338486.023623] GDT[3]: Limit 0x000fffff, Base 0x00000000
[338486.023623] GDT[4]: Limit 0x000fffff, Base 0x00000000
[338486.023623] GDT[5]: Limit 0x000fffff, Base 0x00000000
[338486.023624] GDT[6]: Limit 0x000fffff, Base 0x00000000
[338486.023624] GDT[7]: Limit 0x00000000, Base 0x00000000
[338486.023624] GDT[8]: Limit 0x0000206f, Base 0x00120000
[338486.023625] GDT[9]: Limit 0x0000fe00, Base 0x0000ffff
[338486.023625] GDT[10]: Limit 0x00000000, Base 0x00000000
[338486.023625] GDT[11]: Limit 0x00000000, Base 0x00000000
[338486.023625] GDT[12]: Limit 0x00000000, Base 0x00000000
[338486.023626] GDT[13]: Limit 0x00000000, Base 0x00000000
[338486.023626] GDT[14]: Limit 0x00000000, Base 0x00000000
從輸出可以看到,GDT[5] 和 GDT[6] 的 base address 都是 0,而 limit 設置為 0xffff,這表示這兩個段涵蓋了所有的地址空間。因此,對於該程式來說,分段機制已經被略過,地址映射是直接透過 flat model 完成的。