上一篇聊到分段機(jī)制是為了提供了隔絕代碼、數(shù)據(jù)和堆棧區(qū)域的機(jī)制,能夠使得多個(gè)程序運(yùn)行在同一個(gè)內(nèi)存空間中不會(huì)相互干擾,這是對(duì)內(nèi)存平坦模型的一種保護(hù)。內(nèi)存經(jīng)過(guò)分段機(jī)制后會(huì)變?yōu)橐粋€(gè)個(gè)的段,這稱為多段模型。多段模型能夠利用分段機(jī)制的功能提供由硬件增強(qiáng)代碼、數(shù)據(jù)結(jié)構(gòu)、程序和任務(wù)的保護(hù)措施。
現(xiàn)在我們知道了分段的目的是為了什么,但是我們好像還不知道什么是段,以及段有哪些特征。
段的定義
保護(hù)模式中的 80x86 架構(gòu)提供了 4GB 的物理地址空間。這是 CPU 在地址總線上可以尋址的地址空間。這段地址空間是一種平坦模型地址空間,地址范圍從 0 到 0xFFFFFFFF。
平坦模型:相對(duì)于多個(gè)段的模型來(lái)說(shuō),平坦模型指的就是一個(gè)段,比如在實(shí)模式下,處理器最大可尋址 64 KB(2 ^ 16)的地址空間,在保護(hù)模式下,處理器最大可尋址 4GB (2 ^ 32)的地址空間,如果訪問(wèn)超過(guò)最大地址空間的數(shù)據(jù)指令,需要重新指定段。
需要注意下的是,段地址 + 偏移地址確實(shí)能尋址 1MB 的地址空間,但這卻不是平坦模型的訪問(wèn)方式,而是多段模型。
再來(lái)啰嗦一下分段機(jī)制的目的:分段機(jī)制就是把虛擬地址空間中的虛擬內(nèi)存組織成一個(gè)個(gè)長(zhǎng)度可變的段,這個(gè)段是虛擬地址到線性地址轉(zhuǎn)換的基礎(chǔ),一般來(lái)說(shuō),段由三部分組成:
段基址(Base address):段的初始地址,可以認(rèn)為是段的開(kāi)始,段基址的段內(nèi)偏移為 0 。
段限長(zhǎng)(limit):該段最大可用的偏移位置,它定義了段的長(zhǎng)度,也是段內(nèi)偏移最大能夠?qū)ぶ返降奈恢谩?/p>
段屬性(Attributes):指的是該段的特性,比如段是否可讀、是否可寫、是否能夠作為程序執(zhí)行,段的特權(quán)級(jí)等。
段基址和段限長(zhǎng)一起定義了段所映射的線性地址空間的范圍。段內(nèi)從 0 到 limit 地址范圍會(huì)對(duì)應(yīng)線性地址空間中的 base 到 base + limit ,偏移量是無(wú)法大于段限長(zhǎng)的,如果偏移地址大于段限長(zhǎng)會(huì)引發(fā)異常,除此之外,如果訪問(wèn)的這個(gè)段沒(méi)有符合段屬性,也會(huì)引發(fā)異常。
不同的段可以映射到相同的線性地址空間,這種映射是操作系統(tǒng)所允許的。也就是說(shuō)不同的段可以在線性地址空間中覆蓋或者完全重疊,如下圖所示
段基址、段限長(zhǎng)和段屬性都存儲(chǔ)在段描述符這個(gè)結(jié)構(gòu)中,可以說(shuō)段描述符就是能夠查找段重要信息的結(jié)構(gòu),在虛擬地址到線性地址的轉(zhuǎn)換過(guò)程中,就需要用到段描述符。那么段描述符被存儲(chǔ)在哪里呢?段描述符被存儲(chǔ)在段描述符表中,這個(gè)表就是一個(gè)數(shù)組,這個(gè)數(shù)組的下標(biāo)就是段選擇子,還記得我們上篇文章聊過(guò)段選擇子嗎?段選擇子中的 Index 是這么描述的:
image-20230415223616884
為了把邏輯地址轉(zhuǎn)換成為線性地址,CPU 會(huì)執(zhí)行以下操作:
使用段選擇子中的 Index 屬性通過(guò)查詢 GDT/LDT 表定位相應(yīng)的段描述符。
利用段描述符檢驗(yàn)段的訪問(wèn)權(quán)限和范圍,用于確保該段是可訪問(wèn)并且偏移量位于段界限內(nèi)。
把段描述符中取得的段基地址加上 Index ,最后形成偏移地址。
如果沒(méi)有開(kāi)啟分頁(yè)機(jī)制,那么此時(shí)的線性地址就等同于物理地址;如果開(kāi)啟分頁(yè)機(jī)制,那么此時(shí)的線性地址會(huì)經(jīng)過(guò)分頁(yè)機(jī)制轉(zhuǎn)換后才會(huì)把線性地址映射成為物理地址。
段選擇子
上篇文章提到了段選擇子,大致介紹了一下它的結(jié)構(gòu),并沒(méi)有細(xì)致說(shuō)明,這篇文章就來(lái)細(xì)致說(shuō)明一下。
段選擇子又稱段選擇符,它是一個(gè) 16 位的標(biāo)識(shí)符,如下圖所示,段選擇子并不指向段,它指向段描述符表中的段描述符。
段選擇子總共分為三個(gè)部分:
RPL(Request Privilege Level):請(qǐng)求特權(quán)級(jí),表示進(jìn)程應(yīng)該以什么權(quán)限來(lái)訪問(wèn)段,數(shù)值越大權(quán)限越小。
TI(Table Indicator):表示應(yīng)該查詢哪個(gè)表,TI = 0 查 GDT 表;TI = 1 查 LDT 表。
Index:CPU 會(huì)自動(dòng)將 Index * 8,在加上 GDT 和 LDT 中的段基址,就是要加載的段描述符。
下面是幾幅段選擇子的示意圖,大家明白圖中所示也就明白段選擇子是如何表示的了。
需要注意的是,段選擇子 0x0008 和 段選擇子 0x000f 指向的是同一個(gè)段即段 1;段選擇子 0x0010 和 段選擇子 0x0017 也指向的是同一個(gè)段即段 2。段選擇子 0ffff 指向段 8191,而段選擇子 0x0000 指向的的是一個(gè)空段,因?yàn)?CPU 不使用 GDT 表中的第一項(xiàng),所以指向該段的選擇子用作空選擇子。當(dāng)把空選擇子加載到段寄存器(CS 和 SS 除外)中時(shí),處理器不產(chǎn)生異常,但是當(dāng)含有空選擇子的段寄存器用于訪問(wèn)內(nèi)存時(shí),會(huì)產(chǎn)生異常。把空選擇子加載到 CS 和 SS 中也會(huì)產(chǎn)生異常。
段選擇子 a b c d 分別指向 linux 0.1x 中的內(nèi)核代碼段、內(nèi)核數(shù)據(jù)段、任務(wù)代碼段和任務(wù)數(shù)據(jù)段。
一般把段選擇子放在段寄存器中,每個(gè)寄存器支持特定類型的內(nèi)容引用,這部分引用可以是代碼、數(shù)據(jù)或者堆棧;每個(gè)程序都會(huì)把有效的段選擇子加載到 CS、SS 或者是 DS 中,另外,處理器還提供了另外三個(gè)段寄存器即 FS、GS、ES 作為輔助,這三個(gè)寄存器提供當(dāng)前 CPU 訪問(wèn)段寄存器不夠時(shí)使用。
從上圖可以看到,每個(gè)段寄存器都由兩部分組成,一部分是段選擇子,一部分是 "段基址、段限長(zhǎng)和段屬性信息",段選擇子是存在于段寄存器中顯示的部分,而段基址、段限長(zhǎng)和段屬性是隱藏部分。
為什么會(huì)有隱藏部分呢?
隱藏部分也被稱為描述符緩沖或者是影子寄存器,當(dāng)一個(gè)段選擇子被加載到段寄存器中可見(jiàn)部分時(shí),處理器也會(huì)同時(shí)把段基址等信息加載到段寄存器的隱藏部分,緩存在段寄存器中隱藏部分使得處理器在進(jìn)行地址轉(zhuǎn)換的時(shí)候不用再去段描述符中讀取段的相關(guān)信息。
段寄存器中的隱藏部分相當(dāng)于是段描述符的一個(gè)鏡像,或者說(shuō)是拷貝。因此操作系統(tǒng)必須要確保對(duì)段描述符的改動(dòng)反映在描述符緩沖中,如果更改了段描述符卻沒(méi)有在描述符緩沖中進(jìn)行修改,就會(huì)造成段不一致的現(xiàn)象。所以最快捷的方法就是在對(duì)描述附表做過(guò)改動(dòng)之后就立刻重新加載 6 個(gè)段寄存器。這將會(huì)把描述附表中的相應(yīng)段信息加載到描述符緩沖中。
處理器提供了兩類加載指令用于加載段的相關(guān)信息:
一類是 MOV、POP、LDS、LES、LSS、LGS 以及 LFS 指令,這些指令顯示的直接引用段寄存器;
一類是隱式加載指令,例如 CALL、JMP 和 RET 指令、IRET、INTn、INTO 和 INT3 等指令。這些指令在操作過(guò)程中會(huì)附帶改變 CS 寄存器的內(nèi)容。
段描述符
段描述符是 GDT 和 LDT 表中的一個(gè)數(shù)據(jù)項(xiàng),用于向處理器提供有關(guān)一個(gè)段的位置和大小信息以及訪問(wèn)控制的狀態(tài)信息。每個(gè)段描述符長(zhǎng)度是 8 字節(jié),含有三個(gè)主要字段:段基址、段限長(zhǎng)和段屬性,其他是一些細(xì)節(jié)字段。段描述符通常是由編譯器、鏈接器、加載器或者操作系統(tǒng)來(lái)創(chuàng)建。
這是一個(gè)比較詳細(xì)的段描述符的結(jié)構(gòu),下面來(lái)具體介紹一下這些字段的含義:
段限長(zhǎng)字段 LIMIT --- Segment limit field
段限長(zhǎng)用于指定段的長(zhǎng)度,處理器會(huì)把段描述符中兩個(gè)段限長(zhǎng)字段組合成一個(gè) 20 位的值,并且根據(jù)顆粒度標(biāo)志 G 來(lái)指定段限長(zhǎng) Limit 值的實(shí)際含義。如果 G = 0,則段長(zhǎng)度 Limit 范圍可以從 1 到 1MB 字節(jié)。如果 G = 1,則段長(zhǎng)度 Limit 的范圍可以是從 4KB 到 4GB ,單位是 4KB。
基地址字段 BASE --- Base address field
這個(gè)字段定義在 4GB 線性地址空間中一個(gè)段字節(jié) 0 所處的位置。處理器會(huì)把 3 個(gè)分立的基地址字段組合成為一個(gè) 32 位的值,段基址應(yīng)該對(duì)其 16 字節(jié)邊界,這樣做性能比較高。
段類型字段 TYPE --- Type field
類型字段指定段或門(Gate)的類型、說(shuō)明段的訪問(wèn)種類以及段的擴(kuò)展方向。這個(gè)字段依賴與描述符類型標(biāo)志 S 指明的是一個(gè)應(yīng)用描述符還是系統(tǒng)描述。TYPE 字段的編碼對(duì)代碼、數(shù)據(jù)或系統(tǒng)描述符都不同。
描述符類型標(biāo)志 S --- Descriptor type flag
表明描述符的類型,0 - 表示系統(tǒng)描述符,1 - 代碼或數(shù)據(jù)段描述符。
描述符特權(quán)級(jí) --- DPL Descriptor priviledge level
DPL 表示描述符的特權(quán)級(jí),特權(quán)級(jí)范圍從 0 - 3 ,3 最低,0 最高,DPL 用于控制對(duì)段的訪問(wèn);
我在內(nèi)核訪問(wèn)相關(guān)的描述中也提到了一個(gè)特權(quán)級(jí),大家還記得是啥嗎?
段存在標(biāo)志 --- P Segment present
P 標(biāo)志位表示一個(gè)段是在內(nèi)存中 p = 1 還是不在內(nèi)存中 p = 0。當(dāng)段描述符的 P 標(biāo)志為 0 時(shí),那么把指向這個(gè)段描述符的選擇符加載進(jìn)段寄存器將導(dǎo)致產(chǎn)生一個(gè)段不存在異常。
D/B --- 默認(rèn)操作大小/默認(rèn)棧指針大小和/或上界限 Default operation size/default stack pointer size and/or upper bound
根據(jù)段描述符表示的是可執(zhí)行代碼段、下擴(kuò)數(shù)據(jù)段還是堆棧段,這個(gè)標(biāo)志具有不同的功能(如果是 32 位,這個(gè)標(biāo)志應(yīng)該設(shè)置為 1,16 位應(yīng)該設(shè)置為 0 )。如果是可執(zhí)行代碼段時(shí),這個(gè)標(biāo)志是 D 標(biāo)志;如果是棧段和下擴(kuò)數(shù)據(jù)段,這個(gè)標(biāo)志是 B 標(biāo)志;
顆粒度標(biāo)志 --- G Granularity
這個(gè)字段用于確定段限長(zhǎng)字段 Limit 值的單位,如果顆粒度標(biāo)志為 0 ,則段限長(zhǎng)值的單位是字節(jié);如果設(shè)置了顆粒度標(biāo)志,則段限長(zhǎng)使用 4KB 單位。
可用和保留比特位 --- Available and reserved bits
段描述符的第 2 個(gè)雙字的位 20 供系統(tǒng)軟件使用,位 21 是保留位并且設(shè)置為 0 。
段描述符表
段描述符表是存儲(chǔ)段描述符的一個(gè)數(shù)組,索引是由段選擇子提供。段描述符表的長(zhǎng)度可變,最多可以包含 8192 個(gè) 8 字節(jié)的描述符,段描述符有兩種:即全局描述符表(Global descriptor table)和局部描述符表(Local descriptor table)。
描述符表由操作系統(tǒng)中的特殊數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)著,并且由內(nèi)存管理硬件來(lái)引用。虛擬內(nèi)存空間被分割成大小相等的兩半,一半由 GDT 來(lái)映射變換成為線性地址,一半由 LDT 來(lái)映射,由于段描述符表最大可以包含 8192 個(gè) 8 字節(jié)的描述符,也就是 2 ^ 13 = 8192 ,所以整個(gè)虛擬地址空間是 2 ^ 14 = 16384 個(gè)段了,通過(guò)指定 TI = 1 or 0 就可以查找到指令的段描述符。
當(dāng)發(fā)生任務(wù)切換時(shí),LDT 會(huì)更換成新任務(wù)的 LDT,但是 GDT 內(nèi)容卻不會(huì)變。因此可以看出,GDT 相當(dāng)于是全局共有的,系統(tǒng)中所有任務(wù)共享的段用 GDT 來(lái)映射,而 LDT 是當(dāng)前任務(wù)特有的,可以把 LDT 看成是操作系統(tǒng)的數(shù)據(jù)。
下面是一副關(guān)于 GDT、LDT 的映射圖。
上圖中共有六個(gè)段,分別是用于任務(wù) A 的 CodeA 、DataA,任務(wù) B 的 CodeB、DataB,用于操作系統(tǒng)的 Codeos 和 Dataos,系統(tǒng)中的任務(wù) A 和 任務(wù)B 分別是兩個(gè)不同的應(yīng)用程序,并且每個(gè)任務(wù)都有自己的 Code 和 Data,在各自的 LDT 表中保存著 Code 和 Data。包含操作系統(tǒng)內(nèi)核的兩個(gè)段 Codeos 和 Dataos 在 GDT 中映射,并且 GDT 表示任務(wù) A 和任務(wù) B 共同享有的全局映射,GDT 表還保存著 LDTA 和 LDTB。
當(dāng)任務(wù) A 在運(yùn)行時(shí),可訪問(wèn)的段包括 LDTA 的 CodeA 和 DataA,加上 GDT 映射的 Codeos 和 Dataos,任務(wù) B 運(yùn)行時(shí),可訪問(wèn)的段包括 LDTB 的 CodeB 和 DataB,加上 GDT 映射的 Codeos 和 Dataos 。任務(wù) A 在運(yùn)行時(shí),是無(wú)法訪問(wèn)任務(wù) B 的兩個(gè)段的;同樣的任務(wù) B 在運(yùn)行時(shí),也是無(wú)法訪問(wèn)任務(wù) A 的,這正是虛擬地址提供的保護(hù)機(jī)制,還記得上篇文章寫到的嗎?
GDT 本身并不是一個(gè)段,它只是線性地址空間中的一個(gè)數(shù)據(jù)結(jié)構(gòu)。GDT 的基地址 (Base Address)+ 段長(zhǎng)度(Limit)會(huì)被直接加載進(jìn) GDTR 寄存器中。GDT 的基地址應(yīng)該進(jìn)行內(nèi)存 8 字節(jié)對(duì)齊,用已得到最佳的處理性能。GDT 的限長(zhǎng)以字節(jié)為單位。
處理器并不會(huì)使用 GDT 表中的第 1 個(gè)描述符,第 1 個(gè)描述符也是空描述符,把這個(gè)描述符加載進(jìn)數(shù)據(jù)段寄存器 DS、FS、GS 和 ES 后不會(huì)產(chǎn)生異常,但是使用空描述符的段選擇符訪問(wèn)內(nèi)存時(shí)就肯定會(huì)產(chǎn)生一般保護(hù)性異常。
訪問(wèn) LDT 表需要使用其段選擇符,為了在訪問(wèn) LDT 時(shí)減少地址轉(zhuǎn)換次數(shù),LDT 的段選擇符、基地址、段限長(zhǎng)和訪問(wèn)權(quán)限需要存儲(chǔ)在 LDTR 寄存器中。
審核編輯:湯梓紅
-
處理器
+關(guān)注
關(guān)注
68文章
19100瀏覽量
228814 -
寄存器
+關(guān)注
關(guān)注
31文章
5294瀏覽量
119814 -
cpu
+關(guān)注
關(guān)注
68文章
10804瀏覽量
210829 -
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208717 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
569瀏覽量
40072
原文標(biāo)題:圖文詳解 Linux 分段機(jī)制!
文章出處:【微信號(hào):cxuangoodjob,微信公眾號(hào):程序員cxuan】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論