這節我們討論linux是如何利用x86結構中的段機制的,更確切的說是如何繞過linux的段機制的。
我們決定從linux的可移植性開始討論。我們說linux是一個廣泛移植的操作移動,它支援x86,Alpha,arm等多種體繫結構。但是很多的結構其實都是不支援段機制的,比如arm,Alpha等,但是他們都支援分頁機制。linux為了能移植到x86上,做了不少工作。
首先我們說,x86是肯定有段機制的,那麼我們要在x86上運行程式,那不可避免要用到段機制。於是我們想到我們先前所想到的段描述符中有一個表示以位元組為單位還是以頁為單位表示一個段長度的屬性位。我們當時說,當G=1時表示以頁(4KB)為單位,那麼一個段最大長度能到4GB。根據這一點,我們把一個段的段基址固定設定為0,然後讓G=1,於是我們一個段的最大長度就是4GB了,呐,這個很顯然就能和我們4GB的線性地址空間一一映射了。通過這樣的處理,我們說現在x86的段機制已經形同虛設了,邏輯地址和線性地址可以混為一談了。
但是x86還規定說,必須為程式碼片段和資料區段建立不同的段,所以linux為程式碼片段和資料區段分別建立了一個基地址為0,段長度為4GB的段描述符。不僅如此,由於linux核心運行在特權級0,使用者程式運行在特權級3,x86規定說特權級為3的使用者程式是不能存取權限級為0的核心代碼的,所以linux又分別為核心和使用者程式分別建立程式碼片段和資料區段。
於是在arch/x86/include/asm/segment.h中這樣定義四個段(即在機器啟動過程中段寄存器中放的值):
#define __KERNEL_CS (GDT_ENTRY_KERNEL_CS*8)#define __KERNEL_DS (GDT_ENTRY_KERNEL_DS*8)#define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS*8+3)#define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS*8+3)
其中:
#define GDT_ENTRY_DEFAULT_USER_CS 14#define GDT_ENTRY_DEFAULT_USER_DS 15#define GDT_ENTRY_KERNEL_BASE (12)#define GDT_ENTRY_KERNEL_CS (GDT_ENTRY_KERNEL_BASE+0)#define GDT_ENTRY_KERNEL_DS (GDT_ENTRY_KERNEL_BASE+1)
於是上邊的定義的結果是下邊這樣:
#define __KERNEL_CS 0x00C0 /*核心程式碼片段,index=12,TI=0,RPL=0*/#define __KERNEL_DS 0x00D0 /*核心程式碼片段,index=13,TI=0,RPL=0*/#define __USER_DS 0x00E3 /*使用者程式碼片段,index=14,TI=0,RPL=3*/#define __USER_CS 0x00F3 /*使用者程式碼片段,index=15,TI=0,RPL=3*/
於是我們可以用12,13,14,15四個索引來找到我們四個段所對應的段描述符,並且我們把核心程式碼片段的特權聲明為0,使用者程式碼片段的特權為3,TI為0表示我們總是訪問通用描述元表。
在我們對應的段描述符中我們把G設定為1,段上限規定為0xfffff,就巧妙的繞過了x86的段機制。
但在這裡我不能忽略的一個問題就是,我們把四個段的上限全部設定為4G,那就完全破壞了段的保護,就是說,我們有可能隨隨便便就修改了我們的其他段的資料。所幸,我們現在還是個線性地址,所幸此時我們還沒把資料裝載進記憶體,因此,我們就有處理這個問題的辦法,這就是下面要講的分頁機制了。