iT邦幫忙

2024 iThome 鐵人賽

DAY 19
0
自我挑戰組

Linux Kernel 網路巡禮系列 第 19

記憶體管理 (4) - 略過 Segmentation

  • 分享至 

  • xImage
  •  

在 Intel 的開發人員手冊中提到,當 CPU 以 64 位元模式執行時,分段機制在大多數情況下實際上已被「關閉」。當程式運作時,等同於直接在線性地址空間中執行。這裡所謂的「關閉」更準確地說是「被略過」(bypass),因為分段機制本質上仍然存在,但透過 flat model 運作,使其對於大多數應用來說幾乎可以忽略。

https://ithelp.ithome.com.tw/upload/images/20241003/201527031W6LaBEQWE.png
在 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 完成的。


上一篇
記憶體管理 (3) - Segmentation 機制
下一篇
記憶體管理 (5) - 記憶體 Layout 與權限管理
系列文
Linux Kernel 網路巡禮30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言