機器指令裏面的內存地址都是虛擬內存地址。程序裏面的每個進程,都有一個屬於本身的虛擬內存地址空間。咱們能夠經過地址轉換來得到最終的實際物理地址。
咱們每個指令都存放在內存裏面,每一條數據都存放在內存裏面。所以,「地址轉換」是一個很是高頻的動做,
「地址轉換」的性能就變得相當重要了。這就是咱們今天要講的 第一個問題,也就是 性能問題php
由於咱們的指令、數據都存放在內存裏面,這裏就會遇到咱們今天要談的 第二個問題,也就是 內存安全問題。若是被人修改了內存裏面的內容,
咱們的CPU就可能會去執行咱們計劃以外的指令。這個指令多是破壞咱們服務器裏面的數據,也多是被人獲取到服務器裏面的敏感信息。數據庫
多級頁表雖然節約了咱們的存儲空間,可是卻帶來了時間上的開銷,變成了一個「以時間換空間」的策略。本來咱們進行一次地址轉換,只須要訪問一次內存就能找到物理頁號,
算出物理內存地址。可是用了4級頁表,咱們就須要訪問4次內存,才能找到物理頁號。緩存
程序所須要使用的指令,都順序存放在虛擬內存裏面。咱們執行的指令,也是一條條順序執行下去的。也就是說,咱們對於指令地址的訪問,
存在前面幾講所說的「空間局部性」和「時間局部性」,而須要訪問的數據也是同樣的。咱們連續執行了5條指令。由於內存地址都是連續的,因此這5條指令一般都在同一個「虛擬頁」裏。
所以,這連續5次的內存地址轉換,其實都來自於同一個虛擬頁號,轉換的結果天然也就是同一個物理頁號。那咱們就能夠用前面幾講說過的,用一個「加個緩存」的辦法。
把以前的內存轉換地址緩存下來,使得咱們不須要反覆去訪問內存來進行內存地址轉換。
安全
因而,計算機工程師們專門在CPU裏放了一塊緩存芯片。這塊緩存芯片咱們稱之爲 TLB,全稱是 地址變換高速緩衝(Translation-Lookaside Buffer)。bash
這塊緩存存放了以前已經進行過地址轉換的查詢結果。這樣,當一樣的虛擬地址須要進行地址轉換的時候,咱們能夠直接在TLB裏面查詢結果,服務器
而不須要屢次訪問內存來完成一次轉換。
TLB和咱們前面講的CPU的高速緩存相似,能夠分紅指令的TLB和數據的TLB,也就是 ITLB和 DTLB。一樣的,咱們也能夠根據大小對它進行分級,變成L一、L2這樣多層的TLB。
除此以外,還有一點和CPU裏的高速緩存也是同樣的,咱們須要用髒標記這樣的標記位,來實現「寫回」這樣緩存管理策略。
數據結構
爲了性能,咱們整個內存轉換過程也要由硬件來執行。在CPU芯片裏面,咱們封裝了內存管理單元(MMU,Memory Management Unit)芯片,用來完成地址轉換。
和TLB的訪問和交互,都是由這個MMU控制的。dom
講完了虛擬內存和物理內存的轉換,咱們來看看內存保護和安全性的問題。ide
進程的程序也好,數據也好,都要存放在內存裏面。實際程序指令的執行,也是經過程序計數器裏面的地址,去讀取內存內的內容,而後運行對應的指令,使用相應的數據。
雖然咱們現代的操做系統和CPU,已經作了各類權限的管控。正常狀況下,咱們已經經過虛擬內存地址和物理內存地址的區分,隔離了各個進程。
可是,不管是CPU這樣的硬件,仍是操做系統這樣的軟件,都太複雜了,不免仍是會被黑客們找到各類各樣的漏洞。
就像咱們在軟件開發過程當中,經常會有一個「兜底」的錯誤處理方案同樣,在對於內存的管理裏面,計算機也有一些最底層的安全保護機制。函數
這些機制統稱爲 內存保護(Memory Protection)。我這裏就爲你簡單介紹兩個。
第一個常見的安全機制,叫 可執行空間保護(Executable Space Protection)。這個機制是說,咱們對於一個進程使用的內存,
只把其中的指令部分設置成「可執行」的,對於其餘部分,好比數據部分,不給予「可執行」的權限。由於不管是指令,仍是數據,在咱們的CPU看來,都是二進制的
數據。咱們直接把數據部分拿給CPU,若是這些數據解碼後,也能變成一條合理的指令,其實就是可執行的。
這個時候,黑客們想到了一些搞破壞的辦法。咱們在程序的數據區裏,放入一些要執行的指令編碼後的數據,而後找到一個辦法,讓CPU去把它們當成指令去加載,
那CPU就能執行咱們想要執行的指令了。對於進程裏內存空間的執行權限進行控制,可使得CPU只能執行指令區域的代碼。對於數據區域的內容,即便找
到了其餘漏洞想要加載成指令來執行,也會由於沒有權限而被阻擋掉。
其實,在實際的應用開發中,相似的策略也很常見。我下面給你舉兩個例子。
好比說,在用PHP進行Web開發的時候,咱們一般會禁止PHP有eval函數的執行權限。這個其實就是懼怕外部的用戶,因此沒有把數據提交到服務器,
而是把一段想要執行的腳本提交到服務器。服務器裏在拼裝字符串執行命令的時候,可能就會執行到預計以外被「注入」的破壞性腳本。這裏我放了一個例子,用這個辦法
script.php?param1=xxx // 咱們的 PHP 接受一個傳入的參數,這個參數咱們但願提供計算功能
$code = eval($_GET["param1"]); // 咱們直接經過 eval 計算出來對應的參數公式的計算結果
script.php?param1=";%20echo%20exec('rm -rf ~/');%20// // 用戶傳入的參數裏面藏了一個命令
$code = ""; echo exec('rm -rf ~/'); //"; // 執行的結果就變成了刪除服務器上的數據
還有一個例子就是SQL注入攻擊。若是服務端執行的SQL腳本是經過字符串拼裝出來的,那麼在Web請求裏面傳輸的參數就能夠藏下一些咱們想要執行的SQL,
讓服務器執行一些咱們沒有想到過的SQL語句。這樣的結果就是,或者破壞了數據庫裏的數據,或者被人拖庫泄露了數據
第二個常見的安全機制,叫 地址空間佈局隨機化(Address Space Layout Randomization)。
內存層面的安全保護核心策略,是在可能有漏洞的狀況下進行安全預防。上面的可執行空間保護就是一個很好的例子。可是,內存層面的漏洞還有其餘的可能性。
這裏的核心問題是,其餘的人、進程、程序,會去修改掉特定進程的指令、數據,而後,讓當前進程去執行這些指令和數據,形成破壞。要想修改這些指令和數據,咱們須要知道這些指令和數據所在的位置才行。
這裏的核心問題是,其餘的人、進程、程序,會去修改掉特定進程的指令、數據,而後,讓當前進程去執行
這些指令和數據,形成破壞。要想修改這些指令和數據,咱們須要知道這些指令和數據所在的位置才行。
原先咱們一個進程的內存佈局空間是固定的,因此任何第三方很容易就能知道指令在哪裏,程序棧在哪裏,數據在哪裏,堆又在哪裏。這個其實爲想要搞破壞的人創造了很大的便利。
而地址空間佈局隨機化這個機制,就是讓這些區域的位置再也不固定,在內存空間隨機去分配這些進程裏不一樣部分所在的內存空間地址,讓破壞者猜不出來。猜不出來呢,
天然就無法找到想要修改的內容的位置。若是隻是隨便作點修改,程序只會crash掉,而不會去執行計劃以外的代碼。
這樣的「隨機化」策略,其實也是咱們平常應用開發中一個常見的策略。一個你們都應該接觸過的例子就是密碼登錄功能。網站和App都會須要你設置用戶名和密碼,
以後用來登錄本身的帳號。而後,在服務器端,咱們會把用戶名和密碼保存下來,在下一次用戶登錄的時候,使用這個用戶名和密碼驗證。
咱們的密碼固然不能明文存儲在數據庫裏,否則就會有安全問題。若是明文存儲在數據庫裏,意味着能拿到數據庫訪問權限的人,都能看到用戶的明文密碼。
這個多是由於安全漏洞致使被人拖庫,並且網站的管理員也能直接看到全部的用戶名和密碼信息。
CSDN網站的用戶名密碼,用戶的損失也不會太大。可是不少用戶可能會在不一樣的網站使用相同的密碼,若是拿到這些用戶名和密碼的人,
可以成功登陸用戶的銀行、支付、社交等等其餘網站的話,用戶損失就大了去了。
好比,前幾年CSDN就發生過被人拖庫的事件。雖然用戶名和密碼都是明文保存的,別人若是隻是拿到了
因而,你們會在數據庫裏存儲密碼的哈希值,好比用如今經常使用的SHA256,生成一一個驗證的密碼哈希值。可是這個每每仍是不夠的。由於一樣的密碼,對應的哈希值都是相同的,
大部分用戶的密碼又經常比較簡單。因而,拖庫成功的黑客能夠經過彩虹表的方式,來推測出用戶的密碼。
這個時候,咱們的「隨機化策略」就能夠用上了。咱們能夠在數據庫裏,給每個用戶名生成一個隨機的、使用了各類特殊字符的 鹽值(Salt)。這樣,咱們的哈希值就再也不是僅僅使用密碼了,
來生成的而是密碼和鹽值放在一塊兒生成的對應的哈希值。哈希值的生成中,包括了一些相似於「亂碼」的隨機字符串,因此經過彩虹表碰撞來猜出密碼的辦法就用不了了。
$password = "goodmorning12345"; // 咱們的密碼是明文存儲的 $hashed_password = hash('sha256', password); // 對應的 hash 值是 054df97ac847f831f81b439415b2bad05694d16822635999880d7561ee1b77ac // 可是這個 hash 值裏能夠用彩虹表直接「猜出來」原始的密碼就是 goodmorning12345 $salt = "#21Pb$Hs&Xi923^)?"; $salt_password = $salt.$password; $hashed_salt_password = hash('sha256', salt_password); // 這個 hash 後的 slat 由於有部分隨機的字符串,不會在彩虹表裏面出現。 // 261e42d94063b884701149e46eeb42c489c6a6b3d95312e25eee0d008706035f
能夠看到,經過加入「隨機」因素,咱們有了一道最後防線。即便在出現安全漏洞的時候,咱們也有了更多的時間和機會去補救這些問題。
雖然安全機制彷佛在平時用不太到,可是在開發程序的時候,仍是要有安全意識。畢竟誰也不想看到,被拖庫的新聞裏出現的是本身公司的名字,也不但願用戶由於咱們的錯誤遭受到損失
爲了節約頁表所須要的內存空間,咱們採用了多級頁表這樣一個數據結構。可是,多級頁表雖然節省空間了,卻要花費更多的時間去屢次訪問內存。因而,
咱們在實際進行地址轉換的MMU旁邊放上了TLB這個用於地址轉換的緩存。TLB也像CPU Cache同樣,分紅指令和數據部分,也能夠進行L一、L2這樣的分層。
而後,我爲你介紹了內存保護。不管是數據仍是代碼,咱們都要存放在內存裏面。爲了防止由於各類漏洞,致使一個進程能夠訪問別的進程的數據或者代碼,甚至是執行對應的代碼,
形成嚴重的安全問題,咱們介紹了最經常使用的兩個內存保護措施,可執行空間保護和地址空間佈局隨機化。
經過讓數據空間裏面的內容不能執行,能夠避免了相似於「注入攻擊」的攻擊方式。經過隨機化內存空間的分配,能夠避免讓一個進程的內存裏面的代碼,被推測出來,從而不容易被攻擊。