家好,我是呼嚕嚕,由于x86保護模式是比較復(fù)雜晦澀的,所以特地單拉出來,實模式和保護模式一個重要的更新就是對內(nèi)存的管理與保護,并且隨著軟件的發(fā)展,為了極致地壓榨CPU的性能,硬件和軟件都做出了許多努力,為了更好的管理內(nèi)存,引入分段,分頁,段頁等等。本文會沿著內(nèi)存的主線,穿插于實模式和保護模式之間,并結(jié)合歷史淵源,更好地講解這里面的發(fā)展與變化。
當(dāng)計算機啟動時,實模式運行的時間對我們?nèi)藖碚f是無感的,但是并不是其不重要,本文筆者想講的故事,它的起點來源一個產(chǎn)品,一個劃時代芯片,8086,其是Intel公司推出的最早,也是最流行的面向個人電腦的CPU型號
我們可以看到上圖有10個引腳,由于芯片是對稱的,所以8086芯片一共(只)有20個引腳。不像現(xiàn)在的CPU那樣成百上千的都有,腳這么多可不僅僅是為了爬得快
我們一起來看下8086的引腳圖:
這些引腳有哪些作用?主要有下面這幾種:
NMI(17):非屏蔽中斷請求信號,不受IF影響,此信號一出現(xiàn),當(dāng)前指令,執(zhí)行結(jié)束后立即進行中斷處理。
INTR(18):可屏蔽中斷請求信號,輸入高電平有效。
CLK(19):系統(tǒng)時鐘,輸入
RESET(21):復(fù)位信號,輸入,高電平有效。復(fù)位信號使處理器馬上結(jié)束現(xiàn)行操作,對處理器的內(nèi)部寄存器進行初始化
READY(22):數(shù)據(jù)準(zhǔn)備好信號線,輸入,高電平有效,由存儲器或I/O端口發(fā)來。CPU在每個總線周期的T3狀態(tài)對READY采樣,若為低電平,則自動插入一個或幾個等待狀態(tài)Tw,直到變?yōu)楦唠娖讲拍苓M入T4狀態(tài)
TEST(23):等待測試信號,輸入,CPU執(zhí)行 WAIT指令時,每隔5個時鐘周期對引腳進行一次測試,若為高電平,CPU處于等待狀態(tài);低電平時執(zhí)行下一條指令。
RD(32):讀控制信號,輸出。RD=0,表示執(zhí)行一個對存儲器或I/O端口的讀操作。
BHE/S7(34):高八位數(shù)據(jù)總線允許/狀態(tài)復(fù)用引腳輸出。
MN/MX(33):最小/最大工作方式控制信號,輸入。接高電平時為最小工作方式。
...大家了解一下即可
這里需要特別注意地址總線,我們知道CPU除了還能訪問內(nèi)存,還能訪問硬件,這些都是通過總線來實現(xiàn)的。
總線是貫穿整個系統(tǒng)的是一組電子管道,是連接各個部件的信息傳輸線,是各個部件共享的傳輸介質(zhì),稱作總線,它攜帶信息字節(jié)并負(fù)責(zé)在各個計算機部件間傳遞。總線按系統(tǒng)總線傳輸信息內(nèi)容的不同,可以分為3 種:數(shù)據(jù)總線、地址總線和控制總線。
我們可以發(fā)現(xiàn)8086的尋址空間是1M,這個是怎么得來的呢?尋址空間主要受地址總線寬度影響,地址總線寬度20,也就表示有20根地址線,又因為內(nèi)存的單位是字節(jié)Byte,所以2^20B=1024KB=1MB
對總線感興趣地,拓展可見:什么是計算機中的高速公路-總線?
由于8086那個時代CPU、內(nèi)存都很昂貴, CPU 和寄存器等寬度都是 16 位的,在段不重疊的情況下,能表示的最大地址0xFFFF,最大可尋址2^16=64KB,然而8086有20根地址線,可尋址的最大內(nèi)存空間是1MB。CPU和寄存器的尋址能力遠遠不能滿足使用
所以Inte工程師們耗盡頭發(fā),發(fā)明了分段技術(shù),將內(nèi)存分為一個個"段",段最大可為64KB,段由三部分組成:
那么16 的位的寄存器究竟該如何能訪問20位的地址空間呢?
計算方式是: 實際物理地址=segment段基址 <<4 + offset段內(nèi)偏移地址,左移4位就是乘以16。這樣就實現(xiàn)用16位的寄存器,生成20位的地址。從而擴大CPU尋址能力,實現(xiàn)對1MB內(nèi)存空間的尋址
為了實現(xiàn)分段,同時8086引入專門為分段而生的段寄存器,如CS、DS、ES、SS:
在采用分段機制之前,工程師要在程序中要訪問內(nèi)存,需要把物理地址寫死在程序中,簡單而粗暴,但是如果其他程序也同時需要同一塊內(nèi)存地址,只能排隊等待,這太讓人著急了,所以采用分段機制的另一個重要的好處是:程序可以重定位
重定向就是將程序中指令的地址改成另一個地址,但該地址處的內(nèi)容還是原內(nèi)存地址處的內(nèi)容。即使分段后,程序還是直接操作同一塊實際物理內(nèi)存,但在程序中的邏輯地址是不一樣的,這樣計算機多道程序得以勉強的"并發(fā)"運行。筆者認(rèn)為分段的初衷更多是程序重定向問題的解決
由于這樣程序中指令了只用到16位地址,縮短了指令長度,也變相地提高了程序執(zhí)行速度。
但隨著8086的普及,人們漸漸發(fā)現(xiàn)"實模式"(那個時候還沒有實模式、保護模式的概念,只有一個工作模式)有個最大問題,就是安全問題,實模式哪怕引入段后,還是直接操作系統(tǒng)的實際內(nèi)存,程序之間的地址沒有隔離,自己寫個程序可以訪問別人的程序地址,甚至是操作系統(tǒng)的程序地址,所以一不小心就直接把操作系統(tǒng)給干掛了,所以那個時候的程序員編寫程序都得小心翼翼的
保護模式概念首次出現(xiàn)于80286,并將以前"老辦法"稱為實模式,80286 雖然有了保護模式,地址總線是 24根,尋址空間變成了 2^24=16MB, 但其CPU、通用寄存器還是16位, 即單獨的一個寄存器還是只能訪問64KB的空間,要想訪問完整的 16MB 內(nèi)存,只能頻繁地變換段基址,非常影響計算機的性能
因此80286太雞肋了,很快Intel推出了80386DX,CPU、寄存器、地址總線都是32位的,尋址空間直接達4GB,在當(dāng)時CPU非常昂貴的時代背景下,可以說"硬件直接拉滿",從這個時候開始,保護模式才大放異彩!
需要注意的是80386并不是立即升到32位的,先出的80386SX的CPU、通用寄存器還是16位,地址總線是 24根
此時CPU、寄存器、地址總線都支持尋址4GB,更換偏移地址,就能夠訪問內(nèi)存的每一個字節(jié),那么其實已經(jīng)不需要分段機制了。但是為了向前兼容,兼容性是CPU能否長久保持生命力的一個重要保證,還是保留了分段機制,但保護模式下的段基地址都設(shè)為了0,意味著每個段的起始地址都是一樣的,其實在操作系統(tǒng)層不再分段
那時的程序員訪問內(nèi)存時被迫用多個小段再加上不斷換段基址的方式訪問,非常容易寫著寫著就忘了前面的內(nèi)存地址,對程序員的心智產(chǎn)生極大的負(fù)擔(dān),不再分段也叫做平坦模式,嗯,對程序員來說以后訪問內(nèi)存操作一路平坦
保護模式與實模式相比有了許多變化,我們先來看下80386和8086寄存器的前后對比,由于80386的寄存器大部分變成32位,同時還必須兼容實模式,所以實模式只用寄存器的前16位
80386寄存器主要為3類:
為了幫助大家理解,筆者特點畫了張圖,其中粉紅色代表80386的擴展部分:
當(dāng)然80386還有其他一些特殊的寄存器,比如IDTR、GDTR、CR0、CR1、CR2和CR3等,這個我們留待下文再講
我們需要思考一個問題,保護模式是如何保護 程序訪問內(nèi)存時安全的?
保護程序訪問內(nèi)存時安全的,其實換個角度就是,讓程序只能訪問安全的內(nèi)存,更進一步地說,我們可以對內(nèi)存進行權(quán)限控制,規(guī)定哪些內(nèi)存可以被哪一類地程序訪問。
所以保護模式下會在訪問內(nèi)存時增加了許多"描述信息",比如段自身的訪問權(quán)限,段的最大長度限制(16位)、段的線性基址(32位)、段的特權(quán)級、段是否在內(nèi)存、讀寫許可等等相關(guān)信息
那么這些信息,首先需要一個數(shù)據(jù)結(jié)構(gòu)來保存所有的相關(guān)描述信息,這就是 段描述符,段描述符8個字節(jié)長,也就是64bit。需要注意每個段都需要一個段描述符。
下面我們就是80386段描述符的結(jié)構(gòu)圖:
段描述符核心就是:段基地址,段界限,訪問權(quán)限D(zhuǎn)PL。 段描述符的具體參數(shù),筆者這里就不詳細(xì)貼出來了,太多太雜,感興趣地可以自行去看Global Descriptor Table - OSDev Wiki
如果我們直接通過一個64bit段描述符來引用一個段的時候,就必須使用一個64bit長的段寄存器裝入這個段描述符,但是我們剛剛看到段寄存器仍然是16bit,這是Intel為了兼容實模式。所以我們就無法直接通過段寄存器來直接引用64bit的段描述符。
而且每個段都有自己的段描述符,這些信息非常龐大,不是一個或者幾個寄存器就能夠保存的下去的,需要在內(nèi)存中開辟出一段空間,當(dāng)操作系統(tǒng)啟動時,加載到內(nèi)存中。在這個專門的內(nèi)存空間中,所有的段描述符都依次排放在一起,這就構(gòu)成一個 全局描述符表GDT(Global Descriptor Table ),GDT是全局的,所以對一個系統(tǒng)來說是唯一的
又因為全局描述符表GDT是在內(nèi)存中的,CPU是無法直接找到的,需要告訴它,這就是需要一個全新的寄存器GDTR,來專門告訴CPU,GDT在內(nèi)存的位置
問題又來了,現(xiàn)在全局描述符表GDT有了,有了它我們就能去找內(nèi)存所有的段,但是我們?nèi)绾稳ゲ檫@張表呢?我們這里借鑒一下實模式(同時也是為了兼容實模式),在保護模式下,段寄存器(比如 ds、ss、cs)中存放的不再是尋址段的基地址,而是一個一個"GDT表索引",稱為段選擇符(或稱段選擇子)
在保護模式下,通過段寄存器存放的段選擇符(或稱段選擇子),由段選擇符從全局描述符表GDT中找到8個字節(jié)長的段描述符,段描述符里存儲著段基址,再加上偏移地址就可以得到實際內(nèi)存物理地址。這里我們只考慮了段模式,頁模式暫不展開,其實頁模式也是基于段模式的
我們段寄存器還是16位,那么段選擇符也是16位的,其中的13bit用來作"索引index",下面我們看下80386段選擇符的結(jié)構(gòu)圖:
當(dāng)?shù)刂吩L問時,如果段選擇符的請求特權(quán)級別RPL 的權(quán)限低于段描述符的特權(quán)級DPL時(一共分為四層:0、1、2、3,其中0為最高特權(quán)級,3 為最低特權(quán)級),就會拒絕訪問,于是就達到了"保護"的作用!
LDT局部描述符表,LDT結(jié)構(gòu)和GDT是差不多的,主要區(qū)別在于GDT是全局的,而LDT是局部的(local),GDT在整個操作系統(tǒng)中是唯一的,而LDT在系統(tǒng)中可以存在多個
每一個LDT自身作為一個段存在,存放在LDT類型的段里,這個LDT既然也是段,那么它也會有一個描述符,就放在GDT里面。寄存器LDTR內(nèi)容是一個段選擇符,它是用來到GDT里面尋找LDT的
LDT只是一個可選的數(shù)據(jù)結(jié)構(gòu),我們可以完全不使用它,使用它或許可以帶來一些方便性,但同時也帶來復(fù)雜性,如果我們想讓自己的操作系統(tǒng)內(nèi)核保持簡潔,以及可移植性,則最好不要使用它。這里只做簡單地科普介紹
IDT,Interrupt Descriptor Table,即中斷描述符表,和GDT類似,記錄著0~255的中斷號和調(diào)用函數(shù)之間的關(guān)系,與中段向量表有些相似,但要包含更多的信息。
中斷機制是操作系統(tǒng)中極為重要的一個部分。操作系統(tǒng)在管理輸人輸出設(shè)備時,在處理外部的各種事件時,都需要通過中斷機制進行處理,操作系統(tǒng)在管理輸人輸出設(shè)備時,在處理外部的各種事件時,都需要通過中斷機制進行處理
實模式下,16位的中斷機制依賴的是中斷向量表,中斷向量表初始化在0x0000處,位置是固定的。為了讓操作系統(tǒng)的代碼中的邏輯地址和實際物理地址一致,操作系統(tǒng)啟動時會把system模塊搬到零地址處,這樣中斷向量表就會被覆蓋
而在保護模式下,中斷機制用的是中斷描述符表(IDT),位置是不固定的,設(shè)計操作系統(tǒng)時可以靈活設(shè)置,只需最后把其地址賦值給IDTR寄存器。中斷描述符表寄存器IDTR是一個48位的寄存器,其低16位保存中斷描述符表的大小,高32位保存IDT的基址。
當(dāng)中斷發(fā)生時,CPU獲取到中斷向量后,通過IDTR的值,去查找IDT中斷描述符表,得到相應(yīng)的中斷描述符,再根據(jù)中斷描述符記錄的信息來作權(quán)限判斷,運行級別轉(zhuǎn)換,最終調(diào)用相應(yīng)的中斷處理程序
在分段機制下的保護模式一切都?xì)q月靜好,直到有一天,我們系統(tǒng)有大量程序在運行,比如微信,釘釘?shù)龋褍?nèi)存都占了,只剩下2個空閑內(nèi)存段1和空閑內(nèi)存段2。現(xiàn)在我們想在我們系統(tǒng)中運行百度網(wǎng)盤(假設(shè)運行需占用2個內(nèi)存段),明明我們內(nèi)存中有足夠的內(nèi)存段,但就因為不是連續(xù)的,會導(dǎo)致百度網(wǎng)盤運行失敗。
我們只能把釘釘先關(guān)了,然后百度網(wǎng)盤才能正常打開 或者把釘釘先移到磁盤中,然后就可以運行百度網(wǎng)盤了,這個叫內(nèi)存交換,但是段的大小比較大,而且磁盤和內(nèi)存相比要慢很多,所以這種方式效率不高。
通過上面的小例子,相信大家理解了分段機制一些不足的地方:段的大小比較大,而且由于段的大小是不固定的,導(dǎo)致內(nèi)存碎片化(內(nèi)存有斷斷續(xù)續(xù)的間隙,且每個間隙都不一樣大!);程序無法動態(tài)使用內(nèi)存;程序只能存放在連續(xù)的內(nèi)存中......
所以Intel引入了分頁機制,分頁的初衷是為了解決內(nèi)存不足,但由于80286的段交換時性能堪憂,決定引入分頁,同時為了兼容x86的分段機制,就形成獨特的段頁機制
將內(nèi)存劃分為一個個比段更精細(xì)的"頁",頁的大小固定為4K,方便更精細(xì)化管理。由于分段機制下,程序都是需要提前指定基地址,加載到指定內(nèi)存中,現(xiàn)在為了實現(xiàn)程序運行時,內(nèi)存地址自動分配,并按需加載。那必須得先解除線性地址與物理地址對應(yīng)的關(guān)系,這一切需要增加一個"中間層"來實現(xiàn)。
這個中間層主要是3個部分:CR3 控制寄存器,頁目錄表page directory,頁表page Table。當(dāng)頁功能開啟時,段部件產(chǎn)生的地址就不再是物理地址了,而是線性地址,線性地址還要經(jīng)頁部件轉(zhuǎn)換后,才是物理地址。我們來看下段頁機制的工作流程:
CPU內(nèi)部有一個控制寄存器CR3,存放著當(dāng)前進程的頁目錄表的物理內(nèi)存基地址,頁目錄表存放的是頁表的物理內(nèi)存基地址,頁表存放的是頁的物理內(nèi)存基地址
其中當(dāng)操作系統(tǒng)開啟分頁后,分頁機制接收的線性地址其實是虛擬地址,在操作系統(tǒng)看來它是連續(xù)的,但它實際上通過頁表映射到多個不連續(xù)的物理內(nèi)存頁,這樣就極大的利用了物理內(nèi)存,不會出現(xiàn)使用分段機制后產(chǎn)生的大量內(nèi)存碎片那種情況。
因為頁表需要映射整個內(nèi)存地址,如果是單一的,那么線性地址前20位都查一張表的話,2^20=1M, 每個頁表項是4字節(jié),如果頁表項全滿的話, 便是4M大小,換句話說就是頁表本身也占用了4MB的物理內(nèi)存空間。如果我們結(jié)合系統(tǒng)資源分配和調(diào)度運行的基本單位-進程來說,為了保證進程的正常執(zhí)行,每個進程都得有自己的頁表,那么如果進程一多,頁表會占有很大的內(nèi)存空間。
所以現(xiàn)代操作系統(tǒng)都是采取二級頁表的方式:頁目錄表和頁表,這也是我們上圖畫的結(jié)構(gòu)。其實本質(zhì)就是拆分,把一個大表(頁表)拆成多個小表,而且不一次性地將全部頁表項建好,可以在需要時能夠動態(tài)創(chuàng)建頁表。 然后統(tǒng)一由一個頁目錄表來存儲這些頁表 ,其中頁目錄項和頁表項一樣,大小都是4KB
我們將二級頁表內(nèi)存轉(zhuǎn)換流程聯(lián)系在一起就是:將線性地址,分為高10位、中間10位、低12位三個部分,其中高10位作為頁目錄表的索引(頁目錄表中有2^10=1024個項,PDE),中間10位作為頁表的索引(每個頁表也有1024個項,PTE), 低12位就是偏移地址,大小2^12=4KB,和頁的固定大小正好相等。
所以二級頁表能夠?qū)ぶ?/span>4KB*1024*1024=4G,這也是32根地址線能夠?qū)ぶ返淖畲蟮刂妨恕?/span>
分頁其實并不是由操作系統(tǒng)決定的,而是由CPU決定的。因為線性地址到物理地址的轉(zhuǎn)換算法如上圖,已經(jīng)固定流程套路,而且是比較復(fù)雜的(從頁目錄表到頁表再到物理頁),為了加快轉(zhuǎn)換的效率,我們直接在硬件上讓它自動執(zhí)行轉(zhuǎn)化。所以CPU中集成了專門用來干這項工作的硬件模塊,這個模塊被稱為頁部件
當(dāng)程序中給出一個線性地址時,頁部件分析線性地址, 按照以上算法,自動在頁表中檢索到物理地址。我們需要注意的是CR3寄存器存放的是實際物理地址,這個是給CPU看的,不是給操作系統(tǒng)看的。操作系統(tǒng)要訪問內(nèi)存就必須知道它的線性地址才行,線性地址必須連續(xù),至于線性地址的對應(yīng)實際物理地址可以不連續(xù)!
頁目錄表和頁表的參數(shù)如下,和之前的gdt是類似的,大家感興趣地可以自行查閱intel開發(fā)手冊,我們這就不展開了
段機制實現(xiàn)虛擬地址到線性地址的轉(zhuǎn)換,分頁機制實現(xiàn)線性地址到物理地址的轉(zhuǎn)換,一切的改變都是為了更好地管理與保護內(nèi)存!
通過本文的閱讀與理解,帶著大家穿插了解那個年代x86的歷史淵源,大家會更容易明白實模式和保護模式的區(qū)別以及分段,段頁的所遇到的局限和改進,許多奇奇怪怪地設(shè)定都是為了向前兼容,難免負(fù)重而行,但一個成熟的產(chǎn)品,良好的兼容性就是它生命力重要的體現(xiàn)。
實模式和保護模式是現(xiàn)代操作系統(tǒng)的前置知識,即使現(xiàn)代操作系統(tǒng)已經(jīng)天翻地覆的改變,但依舊有他們的影子,理解它們,會讓大家對底層知識有更深刻地理解。筆者能力有限,本文還是有許多細(xì)節(jié)沒有講到,歡迎大家討論
感謝你的閱讀,我們下期再見~
參考資料:
英特爾? 64 位和 IA-32 架構(gòu)開發(fā)人員手冊:卷 3A-英特爾?
https://pdos.csail.mit.edu/6.828/2008/readings/i386/s02_03.htm
作者:小牛呼嚕嚕 ,首發(fā)于公眾號「小牛呼嚕嚕」,更多高質(zhì)量好文等你關(guān)注!
融界2024年3月5日消息,據(jù)國家知識產(chǎn)權(quán)局公告,北京小米移動軟件有限公司申請一項名為“任務(wù)處理模式的切換方法、裝置及存儲介質(zhì)“,公開號CN117648162A,申請日期為2022年9月。
專利摘要顯示,本公開關(guān)于一種任務(wù)處理模式的切換方法、裝置及存儲介質(zhì),所述方法應(yīng)用于終端設(shè)備,所述方法,包括:確定所述終端設(shè)備的當(dāng)前運行狀態(tài)是否滿足CPU調(diào)頻條件;若所述終端設(shè)備的當(dāng)前運行狀態(tài)滿足所述CPU調(diào)頻條件,調(diào)整CPU的運行頻率,并生成任務(wù)處理模式切換消息;其中,所述任務(wù)處理模式切換消息用于通知所述終端設(shè)備內(nèi)處于運行狀態(tài)的多個應(yīng)用程序調(diào)整任務(wù)處理模式;調(diào)整后的任務(wù)處理模式與所述CPU調(diào)整后的所述運行頻率適配。
本文源自金融界
天有人找小編求助,說自己電腦的CPU風(fēng)扇噪音總是特別大,很吵,問有沒有辦法安靜一點的方法,重點是:不花錢……呃,小編還真有辦法。不過首先要確定自己主板支持調(diào)壓方式控制風(fēng)扇(3Pin風(fēng)扇插頭),或者是直接使用4Pin插頭的調(diào)速風(fēng)扇。
首先右鍵單擊開始菜單,選擇設(shè)置。
接下來進入設(shè)置的系統(tǒng)項。
選中電源和睡眠項進入。
在電源和睡眠內(nèi)下拉頁面,找到其他電源設(shè)置。
單擊右側(cè)的“更改計劃設(shè)置”。
下拉頁面找到“更改高級電源設(shè)置”進入。
重點來了,下拉滾動條,找到“處理器電源管理”項,點擊展開到“系統(tǒng)散熱方式”,修改“接通電源”為“被動”即可(筆記本會多一個使用電池的選項,默認(rèn)就是被動,不用理會)。
這樣一來,風(fēng)扇轉(zhuǎn)速的高低將由Win10決定,低負(fù)載的時候轉(zhuǎn)速可以保持安靜,高負(fù)載的時候溫度一升高CPU風(fēng)扇還會很吵。注意,小編再強調(diào)一下,必須是使用3Pin風(fēng)扇的話必須是支持調(diào)壓的主板,或者是使用4Pin調(diào)速風(fēng)扇,才可以實現(xiàn)由Win10接管風(fēng)扇轉(zhuǎn)速管理哦!