微信搜 「yes的練級攻略」乾貨滿滿,否則來掐我,回覆【123】一份20W字的算法刷題筆記等你來領。 我的文章彙總:https://github.com/yessimida/yes 歡迎 star !java
你好,我是 yes。git
關於內存訪問你可能聽過度段,分頁,還有段頁式。github
可是爲何要分段?又爲何要分頁?算法
有了分頁爲何還要分段?chrome
這就須要看一看歷史的發展,知曉歷史以後就知道這一切其實都是天然而然的。安全
這些概念也不是硬塞出來的。微信
正文
1971 年 11 月 15 日,Intel 推出世界第一塊我的微型處理器 4004(4位處理器)。性能
隨後又推出了 8080(8 位處理器)。.net
那時候訪問內存就只有直白天然的想法,用具體物理地址。3d
全部的內存訪問就是經過絕對物理地址去訪問的,那時候尚未段的概念。
段的概念是起源於 8086,這個 16 位處理器。
限於當時的技術背景和經濟,寄存器只有 16 位,而地址總線是 20 位。
那 16 的位的寄存器如何能訪問 20 位的地址?
2 的16 次方若是直着來如何能訪問到 2 的 20 次方所表達的數?
直着來是不可能的,所以就須要操做一下。
也就是引入段的概念,讓 CPU 經過「段基地址+段內偏移」來訪問內存。
有人可能就問你這都只有 16 位,兩個 16 位加起來最多隻能表示 17 位呀。
你說的沒錯。
因此再具體一點的計算規則實際上是:段基地址左移 4 位(就是乘16)再加上段內偏移,這樣獲得的就是 20 位的地址。
好比如今的要訪問的內存地址是0x05808,那麼段基地址能夠是 0x0580,偏移量就是 0x0008。
這樣內存的尋址空間就擴大到 20 位了。
至於爲何稱之爲段,其實就是由於寄存器只有 16 位一段只能訪問 64 KB,因此須要移動基地址,一段一段的去訪問全部的內存空間。
對了,專門爲分段而生的寄存器爲段寄存器,當時裏面直接存放段基地址。
不過漸漸地人們就考慮到安全問題,由於在這個時候程序之間的地址沒有隔離,個人程序能夠訪問你的程序地址,這就很不安全。
因而在 1982 年 80286 推出時,就有了保護模式。
其實就是 CPU 在訪問地址的時候作了約束,會判斷地址是否在容許的範圍內,會判斷當前的程序對目的地址是否有訪問權限。
搞了個 GDT (全局描述符表)存放全部段描述符。
段寄存器裏面也不是直接放段基地址了,而是放了一個叫選擇子的東西。
大體能夠認爲就是段描述符的索引,也就是經過這個索引去找到段描述符,因此叫選擇子。
這個選擇子裏面還有一點屬性。
這個 T1 就是標明要去哪一個表找,而 RPL 就是特權級了,一共分爲四層,0 爲最高特權級,3 爲最低特權級。
當地址訪問時,若是 RPL 的權限低於目標特權級(DPL)時,就會拒絕訪問,因而就起到了保護的做用。
因此稱之爲保護模式,以前的那種沒有判斷權限的稱之爲實模式。
當時 80286 的地址總線已是 24 位,可是用於尋址的通用寄存器仍是 16 位,雖然段基地址的位數已經足夠訪問到 24 位(由於已經放到 GDT 中,且有 24位)。
可是因每次一段只有 64 KB,這樣訪問就很不方便,須要不斷的更換段基地址,因而 80286 很快就被淘汰,換上了 80386。
這是 Intel 第一代 32 位處理器。
除了段寄存器仍是 16 位以外,地址總線和寄存器都是 32 位,這就意味着之前爲了尋址搞的段機制其實沒用了。
由於單單段內偏移就能夠訪問到 4GB 空間,可是爲了向前兼容段機制仍是保留了下來,段寄存器仍是 16 位是由於夠用了,因此不必擴充。
不過上有政策,下有對策。
雖然說段機制保留了,可是咱能夠「忽悠」着用,把段基值都設置爲 0 ,就用段內偏移地址來訪問內存空間就行了。
這其實就意味着每一個段的起始地址都是同樣的,那就等於不分段了,這就叫平坦模式。
Linux 就是這樣實現的。
那爲何要分頁?
由於分段粒度太粗了,致使內存碎片大,不利於管理。
當時加載到內存等於一個段都得搞到內存中,而段的範圍過大,舉個例子。
假設此時你有 200M 內存,此時有 3 個應用在運行,分別是 LOL、chrome、微信。
此時內存中明明有 30MB 的空閒,可是網易雲加載不進來,這內存碎片就有點大了。
而後就得把 chrome 先換到磁盤中,而後再讓 chrome 加載進來到微信的後面,這樣空閒的 30MB 就連續了,因而網易雲就能加載到內存中了。
可是這樣等於要把 50MB 的內存來個反覆橫跳,磁盤的訪問太慢了,因此效率就很低。
整體而言能夠認爲分段內存的管理粒度太粗了,因此隨着 80386 就出來了個分頁管理,一個更加精細化的內存管理方式。
簡單地說就是把內存等分紅一頁一頁,每頁 4KB 大小,按頁爲單位來管理內存。
你看按一頁一頁來管理這樣就不用把一段程序都加載進內存,只須要將用到的頁加載進內存。
這樣內存的利用率就更高了,能同時運行的程序就更多了。
而且因爲一頁就 4KB, 因此內存交換的性能問題得以緩解,畢竟只要換必定的頁,而不須要整個段都換到磁盤中。
對應的還有個虛擬內存的概念。
分頁機制構造了一個虛擬內存空間,讓每一個進程誤覺得本身掌控全部的內存。
再具體一點就是每一個進程都有一個頁表,頁表中有物理頁號和屬性,這樣尋址的時候經過頁表就能利用虛擬地址找到對應的物理地址。
屬性用來作權限的一些管理。
就理解爲進程想要內存中的任意一個地址都行,沒問題,反正背地裏偷偷的會換成能夠用的物理內存地址。
若是物理內存滿了也沒事,把不經常使用的內存頁先換到磁盤中,即 swap,騰出空間來就行了,到時候要用再換到內存中。
上面提到的虛擬地址也叫線性地址,簡單地說就是經過繞不開的段機制獲得線性地址,而後再經過分頁機制轉化獲得物理地址。
最後
至此咱們已經知曉了爲何有分段,又有分頁,還有段頁式。
一開始限於技術和成本因此寄存器的位數不夠,所以爲了擴大尋址範圍搞了個分段訪問內存。
而隨後技術起來了,位數都擴充了,寄存器其實已經能夠訪問所有內存空間了,因此分段已經沒用了。
可是爲了向前兼容仍是保留着分段訪問的形式,而且隨着軟件的發展,同時運行各類進程的需求愈加強烈。
爲了更好的管理內存,提升內存的利用率和內存交互性能引入了分頁管理。
因此就變成了先分段,而後再分頁的段頁式。
固然也能夠和 Linux 那樣讓每一段的基地址都設爲 0 ,這樣就等於「繞開」了段機制。
至此今天的內容就差很少了,這篇文章沒有深刻具體的分段和分頁的細節,以後再做一篇文章來闡述細節。
歡迎關注個人公衆號【yes的練級攻略】,更多硬核文章等你來讀。
更多文章可看個人文章彙總:https://github.com/yessimida/yes 歡迎 star !
我是 yes,從一點點到億點點,歡迎在看、轉發、留言,咱們下篇見。
本文分享自微信公衆號 - yes的練級攻略(yes_java)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。