內存尋址
曾經(jīng)有一個(gè)叫“阿蘭.圖靈”的天才,它設想出了一種簡(jiǎn)單但運算能力幾乎無(wú)限發(fā)達的理想機器——這可不是一個(gè)具體的機械玩藝,而是一個(gè)思想模型——用來(lái)計算能想象得到的所有可計算函數。這個(gè)有趣的機器由一個(gè)控制器,一個(gè)讀寫(xiě)頭和一條假設兩端無(wú)限長(cháng)的帶子組成。工作帶好比存儲器,被劃分成大小相同的格子,每格上可寫(xiě)一個(gè)字母,讀寫(xiě)頭可以在工作帶上隨意移動(dòng),而控制器可以要求讀寫(xiě)頭讀取其下方工作帶上的字母。
你可千萬(wàn)別覺(jué)得這個(gè)機器傻得可愛(ài),它可是當代馮.諾依曼體系計算機的理論鼻祖。它帶來(lái)的“數據連續存儲和選擇讀取思想”可是目前我們使用的幾乎所有機器運行背后的靈魂。計算機體系結構中的核心問(wèn)題之一就是如何有效地進(jìn)行內存尋址,因為所有運算的前提都是先要從內存中取得數據,所以?xún)却鎸ぶ芳夹g(shù)從某種程度上代表了了計算機技術(shù)。
下面就開(kāi)始一起聊聊關(guān)于尋址的故事。
馮.諾依曼體系計算機系統由運算器、存儲器、控制器、輸入設備、輸出設備五大部件組成。運算器就是我們熟知的CPU中的AUL(算術(shù)邏輯單元),存儲器是內存,控制器是CPU中的控制單元;輸入設備就是我們的鼠標鍵盤(pán)等;輸出設備就是顯示器,打印機等。
計算機的內存尋址技術(shù)和世界上的其它事物一樣都經(jīng)歷了由簡(jiǎn)單到復雜,由笨拙到優(yōu)雅的過(guò)程。自我聽(tīng)說(shuō)計算機到今天,內存尋址方法發(fā)生了幾次決定性的變革(“史前”的內存尋址方法我連資料都沒(méi)由找到,真是無(wú)據可查了!),而每次變革都帶來(lái)了軟件技術(shù)的發(fā)展注入了新鮮血液。
讓我們沿著(zhù)Intel公司的腳步來(lái)回顧一下歷史吧!(我實(shí)在沒(méi)機會(huì )接觸除Intel以外的處理器!!!)
20年前intel推出了一款8位處理器——8080,它有1個(gè)主累加器(寄存器A)和6個(gè)次累加器(寄存器B,C,D,E,H和L),幾個(gè)次累加器可以配對(如組成BC, DE或HL)使用來(lái)訪(fǎng)問(wèn)16位的內存地址,也就是說(shuō)8080可訪(fǎng)問(wèn)到64K內的地址空間。另外那時(shí)還沒(méi)有段的概念,訪(fǎng)問(wèn)內存都要通過(guò)絕對地址,因此程序中的地址必須進(jìn)行硬編碼,而且也難以重定位,因此當時(shí)的軟件大都是些可控性弱,結構簡(jiǎn)陋,數據處理量小的工控程序。
人類(lèi)從來(lái)都是不斷前進(jìn)的,很快幾年后intel就開(kāi)發(fā)出了16位的新處理器——8086,這便是內存尋址的第一次飛躍。
8086處理器引入了一個(gè)重要概念——段。段描述了一塊有限的內存區域,區域的起始位置存在專(zhuān)門(mén)的寄存器(段寄存器)中。另外8086處理器可以尋址到
系統所需要作的僅僅是:把16位的段地址左移動(dòng)4位后,再與16位的偏移量相加便可獲得一個(gè)20位的內存地址,見(jiàn)圖1

圖 1
Intel內存地址的描述形式也很貼近上圖,采用了“段地址:偏移量”的形式來(lái)描述內存地址,比如A815:CF2D就代表段首地址在A815,段內偏移位CF2D。
為了支持段機制,8086為程序使用的代碼段,數據段,堆棧段分別提供了專(zhuān)門(mén)的16位寄存器CS,DS和SS,此外還給內存和字符串拷貝操作留下了一個(gè)目的段寄存器:ES。
段式內存管理帶來(lái)了顯而易見(jiàn)的優(yōu)勢——程序的地址不再需要硬編碼了,調試錯誤也更容易定位了,更可貴的是支持更大的內存地址。程序員開(kāi)始獲得了自由。
人們的欲望在繼續膨脹。intel的80286處理器于1982年問(wèn)世了,它的地址總線(xiàn)位數增加到了24位,因此可以訪(fǎng)問(wèn)到
為了和過(guò)去兼容,80286內存尋址可以以?xún)煞N方式進(jìn)行,一種是先進(jìn)的保護模式,另一種是老式的8086方式,被成為實(shí)模式。啟動(dòng)時(shí)候處理器處于實(shí)模式只能訪(fǎng)問(wèn)
真正的第二次內存尋址飛躍發(fā)生在80386身上,它近乎完美的設計將計算機技術(shù)推向了一個(gè)新高度.
80386的地址總線(xiàn)擴展到了32位,可尋址空間一下擴充為
保護模式真得是太精妙了,我恨不得用專(zhuān)門(mén)用一本書(shū)來(lái)討論它,但即使那樣我也擔心不能真正觸其精華。不過(guò)還是借用那句老話(huà)”簡(jiǎn)單就是美麗”,我爭取用最小的篇幅揭示保護模式的真實(shí)面目。
保護模式和實(shí)模式好比一對孿生兄弟,它們外貌很像,從程序角度來(lái)看幾乎看不出什么區別,它們都是通過(guò)段寄存器去訪(fǎng)問(wèn)內存地址,都通過(guò)中斷和設備驅動(dòng)程序來(lái)操作硬件,表面上能感覺(jué)得到的差異就是保護模式能訪(fǎng)問(wèn)的空間是
但實(shí)際上保護模式和實(shí)模式采用了兩種截然不同的思路,保護模式帶來(lái)的最可貴的優(yōu)點(diǎn)不是單純的擴大了內存尋址范圍,而是對內存尋址從機制上提供了保護,將系統的執行空間按權限進(jìn)行了化分。
這種劃分到底會(huì )帶來(lái)那些好處啦? 我們來(lái)推敲一下吧。
如果你的機器只允許一個(gè)任務(wù)使用系統資源,比如說(shuō)系統內存,那么你完全不需要保護資源,因為系統中再沒(méi)有什么值得你去偷窺的東西了,更別說(shuō)去破壞什么了。
可惜那樣的時(shí)代已經(jīng)一去不復返了,如今的系統需要支持多個(gè)用戶(hù)同時(shí)運行多個(gè)任務(wù)。為了防止你去偷看別人的任務(wù),或惡意或由于你的荒唐行為而破壞別人的任務(wù),系統有責任將每個(gè)任務(wù)隔離開(kāi)來(lái),讓大家各自運行在不同的空間,免收干擾。這就是保護的第一個(gè)方面——任務(wù)間保護,要做到任務(wù)間保護需要借助虛擬內存技術(shù)(我們后面分析它),其基礎之一就是保護模式。
除了任務(wù)間保護外,另一個(gè)必須保護的東西就是操作系統本身,它可是資源調配的首腦呀!決不能讓你有機可承,擅自進(jìn)入。必須有一道鐵絲網(wǎng),將你和操作系統隔離開(kāi),使你不得越雷池一步。要想拉起這道鐵絲網(wǎng),就需要借助保護模式中的特權級機制。操作系統放在高特權級里,你的任務(wù)被放在低特權級里。你沒(méi)有權利去偷看操作系統的內容。有什么要求只能請示“領(lǐng)導”(就是保護機制),獲得拼準后才能給你提供服務(wù)。這點(diǎn)可謂是保護模式的最直接應用。
80386之所以能有變化多端的保護手段,追其根本源自保護模式下內存尋址方式發(fā)生革命。傳統上我們知道段方式尋址時(shí),是直接從段寄存器中取得的段的首地址,但是在保護模式中是要多經(jīng)過(guò)一次檢查手續才能獲得想要的段地址。
這里可千萬(wàn)別再說(shuō)“簡(jiǎn)單就是美了”,多了這一次中間倒手過(guò)程可是保護模式下尋址的關(guān)鍵技術(shù)所在呀。倒手的原因我想大概是因為,雖然80386有的通用寄存器(EAX,EDI等等)被擴充倒了32位,但是其中的段寄存器(DS,ES等)仍然只有16位,顯然不可能再用16位的段寄存器直接存放

線(xiàn)性地址屬于中間地址,它還需要一次轉換才能映射到實(shí)際的物理地址上(下面會(huì )看到)。線(xiàn)性地址長(cháng)成的空間稱(chēng)為線(xiàn)性空間,它和物理地址空間結構想同,都為32位,最大可達
這個(gè)索引指針被稱(chēng)作是段選擇子(見(jiàn)圖2),它共有16位,其中14位用來(lái)作為索引,另外2位(RPL)用來(lái)作描述請求特權級。通過(guò)索引從表中獲得的信息,被稱(chēng)為段描述符,它含有段的相關(guān)地址信息等。
改變尋址方法的另一個(gè)原因主要是為了完成保護使命。多用戶(hù)多任務(wù)環(huán)境下,內存尋地工作不再是簡(jiǎn)單地取得32位的內存地址就可以直接不假思索地放到地址總線(xiàn)上去了讀寫(xiě)內存了,此刻必須先要對需訪(fǎng)問(wèn)的地址進(jìn)行合法性檢查,看看訪(fǎng)問(wèn)者是不是有權利去訪(fǎng)問(wèn)它要求的地址。如果發(fā)現有非法訪(fǎng)問(wèn)企圖,則立刻阻止(CPU會(huì )產(chǎn)生一個(gè)一異常)這種危險行為。讀到這里,多數的朋友一定要問(wèn),靠什么進(jìn)行檢查請求的合法性呢?更細心的朋友還會(huì )繼續問(wèn),檢查需要什么信息?這些信息放在那里?
考慮到尋址過(guò)程和合法性檢測過(guò)程需要在同一現場(chǎng)一起進(jìn)行,所以最理想是能把段地址信息和檢測合法性用到的屬性信息能放在一起(需要的空間更大了),于是系統設計師門(mén)便把屬性信息和段的基地址和界限都柔和在了一起,形成了一個(gè)新的信息單元——段描述符號,它整整占用了8個(gè)字節。顯然寄存器太小,不夠存放段描述符,所以段描述符都被統一存在專(zhuān)門(mén)的系統段描述符號表中(GTD或LDT)保存。
說(shuō)到這里,聰明的朋友可能已經(jīng)能大概猜出段描述符表中的內容是什么了。內容里一定包含了段基地址、短的大小信息、段的屬性信系,而且在屬性信息里包含了還有和訪(fǎng)問(wèn)權限有關(guān)的信息。的確如此,下面圖示描述了段描述符的詳細信息,其中和保護關(guān)心最大的信息要數DPL了(見(jiàn)圖3)。

這種間接尋址方式不僅體現在普通任務(wù)尋址上,而且對于中斷處理同樣適用。傳統上中斷處理查詢(xún)方法是在中斷產(chǎn)生后,CPU會(huì )在中斷向量表中搜索中斷服務(wù)例程(ISR)的地址,地址形式還是段+偏移量。在保護模式中中斷產(chǎn)生后,CPU會(huì )從中斷描述符表(IDT)中根據中斷號取得中斷服務(wù)例程的段選擇子和偏移量,然后通過(guò)段選擇子從段描述附表(GDT)中獲得ISR的段信息再結合偏移量得到需要的實(shí)際物理地址。
中斷尋址過(guò)程如圖4

計算機世界和人類(lèi)世界一樣最初是沒(méi)有等級之分的,但當人類(lèi)社會(huì )物質(zhì)文明逐步發(fā)達后,等級也隨之而來(lái)了;同樣當計算機上的應用軟件越來(lái)月豐富后,這個(gè)虛擬世界也逐漸形成了級別等級。我們不去評價(jià)人類(lèi)社會(huì )等級制度,我們只來(lái)看看計算機世界中的等級制度,而且只陷于保護模式中的等級制度。
80386中共規定有4個(gè)特權級,由0到3。0級別權限最高,3級最小。標準的作法是將操作系統核心運行在0級,應用程序運行其它幾個(gè)低級別。不過(guò)為了簡(jiǎn)化操作,往往只會(huì )用到0和3兩個(gè)級別。80386中的每個(gè)段描述符號中都有DPL字段,它規定了訪(fǎng)問(wèn)該段的最低特權級,只用高于次特權級別的程序能有權訪(fǎng)問(wèn)它。所以在訪(fǎng)問(wèn)內存地址時(shí)要將當前特權級(CPL,一般來(lái)說(shuō)就是當前代碼段的特權級別)和被訪(fǎng)問(wèn)段的特權級別比較,如果大于等于才允許訪(fǎng)問(wèn)。
處理當前特權級別和段的特權級別外,有時(shí)還需要使用請求特權級別(RPL),這個(gè)子段出自段選擇字,主要用來(lái)輔助特權保護。比如可以在訪(fǎng)問(wèn)某個(gè)段時(shí),指定其請求特權級,那么特權檢查時(shí),規則變?yōu)閷?span lang="EN-US" twffan="done">RPL和CPL中特權更高的那個(gè)和被訪(fǎng)問(wèn)段的DPL比較。例如,操作系統中的某個(gè)例程會(huì )把一些資料寫(xiě)到用戶(hù)段中。若沒(méi)有特別檢查,那么用戶(hù)可以把一個(gè) DPL為 0 的 段(用戶(hù)程序不能存取它)傳到操作系統處理例程中,因為系統例程有全權寫(xiě)入DPL為0的段,因此用戶(hù)程序就可以破壞該段中的資料了。為了避免這個(gè)問(wèn)題,系統 API 在存取用戶(hù)傳入的段時(shí),可以先把該段選擇子的 RPL設定成和用戶(hù)程序的 CPL 相同,就不會(huì )意外寫(xiě)入原先用戶(hù)無(wú)權存取的段了。 (
但RPL在linux好像沒(méi)被怎么用到)
虛擬內存可是個(gè)怎么強調也不過(guò)分的概念,它的存在極大地方便了程序設計任務(wù),徹底解放了程序員的手腳。下面我們就看看虛擬內存的作用以及如何在存儲管理機制的基礎上實(shí)現它。
我們知道程序代碼和數據必須駐留在內存中才能得以運行,然而系統內存數量很有限,往往不能容納一個(gè)完整程序的所有代碼和數據,更何況在多任務(wù)系統中,可能需要同時(shí)打開(kāi)子處理程序,畫(huà)圖程序,瀏覽器等很多任務(wù),想讓內存駐留所有這些程序顯然不大可能。因此我們能首先能想到的就是將程序分割成小分,只讓當前系統運行它所有需要的那部分留在內存,其它部分都留在硬盤(pán)。當系統處理完當前任務(wù)片段后,再從外存中調入下一個(gè)待運行的任務(wù)片段。的確老式系統的確這樣處理大任務(wù),而且這個(gè)工作是由程序員自行完成。但是隨之程序語(yǔ)源越來(lái)越高級,程序員對系統體系的依賴(lài)程度降低了,很少有程序員能非常清楚的駕馭系統體系了,因此放手讓程序員負責將程序片段化和按需調入輕則降低效率,重則使得機器崩潰;再一個(gè)原因是隨程序越來(lái)越豐富,程序行為幾乎無(wú)法準確預測,程序員自己都很難判斷下一步需要載入那段程序。因此很難再靠預見(jiàn)性靜態(tài)分配固定大小的內存,然后再機械地輪換程序片進(jìn)內存執行。系統必須采取一種能按需分配,不要程序員干預地新技術(shù)。
虛擬內存[1]技術(shù)就是一種由操作系統接管的按需動(dòng)態(tài)內存分配方法,它允許程序不知不覺(jué)種使用大于實(shí)際物理的存儲空間(其實(shí)是將程序需要的存儲空間以頁(yè)的形式分散存儲在物理內存和磁盤(pán)上),所以說(shuō)虛擬內存徹底解放了程序員,從此程序員不用過(guò)分關(guān)心程序大小和載入,可以自由編寫(xiě)程序了,繁瑣的事情都交給操作系統去作吧。
虛擬內存是將系統硬盤(pán)空間和系統實(shí)際內存聯(lián)合在一起為進(jìn)程使用,給進(jìn)程提供了一個(gè)比內存大的多的虛擬空間。在程序運行時(shí),只把虛擬地址空間的一小部分映射到內存,其余都存儲在硬盤(pán)上(也就是說(shuō)程序虛擬空間就等于實(shí)際物理內存加部分硬盤(pán)空間)。當訪(fǎng)問(wèn)被訪(fǎng)問(wèn)的虛擬地址的不在內存時(shí),則說(shuō)明該地址未被映射到內存,而是被存貯在硬盤(pán)中,因此需要的虛擬存儲地址被隨即調入到內存;同時(shí)當系統內存緊張時(shí),也可以把當前不用的虛擬存儲空間換出到硬盤(pán),來(lái)騰出物理內存空間。系統如此周而復始地運轉——換入、換出,而用戶(hù)幾乎無(wú)法查覺(jué),這都是拜虛擬內存機制所賜。
Linux的swap分區就是硬盤(pán)專(zhuān)門(mén)為虛擬存儲空間預留的空間。經(jīng)驗大小應該是內存的兩倍左右。有興趣的話(huà)可以使用 swapon -s 查看交換分區大小,還可以用vmstat 查看當前每秒換入換出的數據大小(在si/so字段下)
大道理很好理解,無(wú)非是用內存和硬盤(pán)空間合成為虛擬內存空間。但是這一過(guò)程中反復運行的地址映射(虛擬地址映射到物理地址)和虛擬地址換入換出卻值得仔細推敲。系統到底是怎么樣吧虛擬地址映射到物理地址上的呢??jì)却嬗秩绾文懿粩嗟暮陀脖P(pán)之間換入換出虛擬地址呢?
利用段機制能否回答上述問(wèn)題呢?我們上面提到過(guò)邏輯地址通過(guò)段機制后變?yōu)橐粋€(gè)32位的地址,足以覆蓋
因為使用頁(yè)機制的原因,通過(guò)段機制轉換得到的地址僅僅是作為一個(gè)中間地址——線(xiàn)性地址了,該地址不代表實(shí)際物理地址,而是代表整個(gè)進(jìn)程的虛擬空間地址。在線(xiàn)性地址的基礎上,頁(yè)機制接著(zhù)會(huì )處理線(xiàn)性地址映射:當需要的線(xiàn)性地址(虛擬空間地址)不在內存時(shí),便以頁(yè)為單位從磁盤(pán)中調入需要的虛擬內存;當內存不夠時(shí),又會(huì )以頁(yè)為單位把內存中虛擬空間的換出到磁盤(pán)上。可見(jiàn)利用頁(yè)來(lái)管理內存和磁盤(pán)(虛擬內存)大大方便了內存管理的工作。毫無(wú)疑問(wèn)頁(yè)機制是虛擬內存管理簡(jiǎn)直是“天配”。
使用頁(yè)機制, 前面我們提到了線(xiàn)性地址是32位。它其中高20位是對頁(yè)表的索引,低12位則給出了頁(yè)面中的偏移。線(xiàn)性地址經(jīng)過(guò)頁(yè)表找到頁(yè)框基地址后和低12位偏移量相加就形成了最終需要的物理地址了。 在實(shí)際使用中,并非所有頁(yè)表項都是被存放在一個(gè)大頁(yè)表里,因為每個(gè)頁(yè)表項4字節,如果要在一個(gè)表中存放2的20次方個(gè)頁(yè)表項,就需要 兩級頁(yè)表搜索如同看章回小說(shuō),先找到在那一章里,然后在找在該章下那一節。具體過(guò)程看看下圖5。 [1] .之所以稱(chēng)為虛擬內存是和系統中的邏輯內存和物理內存而言的,邏輯內存是站在進(jìn)程角度看到的內存,因此是程序員關(guān)心的內容。而物理內存是站在處理器角度看到的內存,由操作系統負責管理。虛擬內存可以說(shuō)是這映射這兩種不同視角內存的一個(gè)技術(shù)手段。 綜上所述。地址轉換工作需要兩種技術(shù),一是段機制,二是頁(yè)機制。段機制處理邏輯地址向線(xiàn)性地址映;頁(yè)機制則負責把線(xiàn)性地址映射為物理地址。兩級映射一同完成了從程序員看到的邏輯地址轉換到處理器看到的物理地址這一艱巨任務(wù)。 你可以將這兩種機制分別比作一個(gè)地址轉換函數,段機制的變量是邏輯地址,函數值是線(xiàn)性地址;頁(yè)機制的變量是線(xiàn)性地址,函數值是物理地址。地址轉換過(guò)程如下所示。 邏輯地址——(段函數)——>線(xiàn)性地址——(頁(yè)函數)——>物理地址。 雖然段機制和頁(yè)機制都參與映射,但它們分工不同,而且相互獨立互不干擾,彼此之間不必知道對方是否存在。 說(shuō)了這么多道理,下面我們結合Linux實(shí)例簡(jiǎn)要地看看段頁(yè)機制如何使用。 段機制在Linux里用得有限,并沒(méi)有被完全利用。每個(gè)任務(wù)并未分別安排各自獨立的數據段,代碼段,而是僅僅最低限度的利用段機制來(lái)隔離用戶(hù)數據和系統數據——Linux只安排了四個(gè)范圍一樣的段,內核數據段,內核代碼段,用戶(hù)數據段,用戶(hù)代碼段,它們都覆蓋0 每個(gè)用戶(hù)進(jìn)程都可以看到 說(shuō)到特權切換,就離不開(kāi)任務(wù)門(mén),陷阱門(mén)/中斷門(mén)等概念。陷阱門(mén)和中斷門(mén)是在發(fā)生陷阱和中斷時(shí),進(jìn)入內核空間的通道。調用門(mén)是用戶(hù)空間程序相互訪(fǎng)問(wèn)時(shí)所需要的通道,任務(wù)門(mén)比較特殊,它不含如何地址,而是服務(wù)于任務(wù)切換(但linux任務(wù)切換時(shí)并未真正采用它,它太麻煩了)。 對于各種門(mén)系統都會(huì )有對應的門(mén)描述符,和段描述符結構類(lèi)似,門(mén)描述符也是由對應的門(mén)選擇字索引,并且最終會(huì )產(chǎn)生一個(gè)指向特定段內偏移地址的指針。這個(gè)指針就指向的是將要進(jìn)入的入口。利用門(mén)的目的就是保證入口可控,不至于進(jìn)入到內核中不該訪(fǎng)問(wèn)的位置(回憶前面講到的中斷服務(wù)程序尋址,其中從中斷描述符號表中獲得的就是中斷門(mén)的描述符,而描述符則制定了具體的入口位置)。 我們前面大概談了談為什么要使用分頁(yè),這里看看linux中如何使用分頁(yè)。 Linux中每個(gè)進(jìn)程都會(huì )有個(gè)自的不同的頁(yè)表,也就是說(shuō)進(jìn)程的映射函數互不相同,保證每個(gè)進(jìn)程虛擬地址不會(huì )映射到相同的物理地址上。這是因為進(jìn)程之間必須相互獨立,各自的數據必須隔離,防止信息泄漏。 另外需要注意的是,內核作為必須保護的單獨部分,它有自己獨立的頁(yè)表來(lái)映射內核空間(并非全部空間,僅僅是物理內存大小的空間),該頁(yè)表(swapper_pg_dir)被靜態(tài)分配,它只來(lái)映射內核空間(swapper_pg_dir只用到768項以后的項——768個(gè)頁(yè)目錄可映射 那么在用戶(hù)進(jìn)程需要訪(fǎng)問(wèn)內核空間時(shí)如何作呢? Linux采用了個(gè)巧妙的方法:用戶(hù)進(jìn)程頁(yè)表的前768項映射進(jìn)程空間(< 
Linux分段段策略
Linux中的分頁(yè)策略
聯(lián)系客服