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