點選藍字“程式員考拉”歡迎關注!

我們知道馮.諾伊曼體系結構中是把計算機劃分為輸入裝置,輸出裝置,存儲器,控制器,運算器.輸入裝置主要是鍵盤,滑鼠,輸出裝置主要是顯示器,列印機,控制器與運算器我們一般全稱為CPU.
存儲器主要指硬碟與記憶體.
為啥windows上的應用軟體都差不多一樣
我們開發軟體時不會直接面對硬體(隻有開發作業系統或驅動程式才直接面對硬體),作業系統封裝了硬體的細節資訊,應用軟體隻是使用作業系統提供的API去間接的操作硬體.以windows為例,我們發現運作在上面的軟體不管是啥程式設計語言開發的,用起來貌似都差不多.界面上基本上都是那個些控件,比如些菜單欄,工具欄,下拉清單,單選,多選按鈕之類的.幾乎都一個樣.原因很簡單,實際上不管啥程式設計語言,你自己寫的函數或者調用一些類庫,如果要使用那些硬體資源,最後都歸結于去調用windows的API.
是以作業系統是在硬體的基礎上抽象一層,隻提供給你一些API,然後各種程式設計語言又在API的基礎上再封裝一層,提供給你一些語言特性和類庫,庫函數給你用.
資料處理和資料顯示
看下windows的API(實際上可以簡單的看成一些C語言庫函數),我們發現絕大部分API函數都僅是實作處理資料和顯示資料這兩功能.
API中有很多針對滑鼠和鍵盤的函數,我們可以把這看成是輸入資料(資訊).消息機制相關的函數就是處理資料. 程序和線程也可以看成在記憶體中處理資料
然後API中視窗,子視窗控件,菜單,GDI,文字和字型,位圖那一堆堆的函數可以看成是顯示資料.把資料通過一些好看點的圖形界面顯示出來.
以前很多人都認為程式 = 資料結構 + 算法,在那會沒太注重使用者體驗,沒太注重GUI時是很恰當.現在應該說程式= 資料處理(資料結構和算法) + 資料顯示.
磁盤和記憶體原理
我們在程式中的資料處理主要是操作磁盤和記憶體,硬碟是磁盤裡面最常用的一種.以前有所謂的軟碟,也是磁盤的一種,原理和硬碟是一樣的.都是利用磁性物質的特性來儲存資訊.磁盤的原理就是利用電磁轉換,學實體時我們知道電可以使物質帶上磁性,而金屬在磁場運動時切割磁感線時會産生電流.磁盤上有很多微粒的磁粉.當寫通過磁頭寫資料時,磁頭中的電流會導緻磁粉極化,改變方向.讀資料時,導體磁頭經過磁粒的區域時會産生電流. 反正大概意思就是這樣.
而記憶體的原理就完全不同,記憶體是半導體制作的(CPU也是半導體做的),而半導體的特性就是我們平時常說的用開關的開和關來表示1,0.通過一些門電路的組合可用來表示數字和實作複雜的邏輯功能.而記憶體主要是用來臨時儲存資料.CPU就是處理一些邏輯關系.
半導體由于必須得通電,然後用電流的有無狀态來表示資訊,是以隻有通電的時候可以儲存資料,電一斷記憶體裡的半導體狀态就處未知狀态就啥用處也沒了.而磁盤斷電後磁性物質還會一直保持原樣.
我們知道通路磁盤時就通過磁頭去指到固定的地方然後讀取資料.不過記憶體就不一樣,不需要啥磁頭去讀取資料,它是有資料總線連接配接,我們是通過總線去讀取記憶體的資料.
實際上我們平時在程式中說要配置設定一塊記憶體(比如new 或malloc),或者操作記憶體(讀寫資料),實際上沒有真正的直接去操作記憶體,而隻是告訴作業系統我要做這些操作,然後作業系統再給你去直接操作記憶體.同樣我們操作硬碟時,實際上也是作業系統去讀寫硬碟.
在windows中有所謂的虛拟記憶體技術,實際上就是由于我們不會直接操作記憶體才會有這種可能.我們當需要多少記憶體時,比如4G,而實際記憶體才2G,但作業系統卻說可以有4G記憶體配置設定給你.它是把一部分硬碟也當作記憶體給你配置設定(至于把多少硬碟空間當作記憶體用這是可以手動配置大小的). 由于CPU讀取資料時隻會在記憶體中進行(不是必須非得這樣,因為如果直接讀取硬碟速度會很慢),是以當你使用了虛拟記憶體後資料會在記憶體和硬碟之間來回的交換.
等會再詳細講下虛拟記憶體...
簡單的曆史故事
1.單使用者使用記憶體
剛開始的時候電腦功能相當簡單,而且用電腦的也都是些高手專家,對硬體相當的了解.此時也沒有啥作業系統,(實際上可能顯示器也可以不需要),電腦磁盤上除了儲存一些供你調用的标準庫函數外啥都沒有.開機後CPU仍然靜靜的呆着不幹嘛,記憶體裡也是空的啥東東都沒有(現在我們用的電腦一開機,記憶體至少會加載作業系統代碼,是以永遠不可能是空的).
此時你要做啥操作就敲入些指令,然後就調用儲存的庫函數,自然是先把庫函數加載到記憶體,然後再在CPU上面跑,跑完了可能就是列印些啥資訊出來(此時還沒檔案的概念,磁盤上不會儲存你的結果資料).然後接下來CPU就休息去了,記憶體也空了.你不去調用庫函數時,電腦就傻傻的呆了啥都不幹.
2.批處理
像上面說的你敲入些指令時電腦才給你幹活,這樣效率太低了.于是做了些改進,首先是出現了作業系統的雛形,打開電腦後會加載一些作業系統的代碼常駐在記憶體中.此時出現了檔案,可以把很多待操作的指令事先寫好,然後儲存成一個個的檔案.然後電腦可以按順序給你從頭跑到尾,就就是你啟動一個操作後就可以走人了,然後計算機會全部給你按順序處理完,一次處理一大批嘛.是以叫批處理.
此時記憶體會被分為兩塊,一小塊給常駐記憶體的作業系統.其他的給使用者程式跑(記憶體和CPU隻能給單個使用者程式用)
3.儲存,切換
批處理隻能從頭到尾按順序跑,如果中間某個程式要跑太久太久,後面的就沒法跑了.一個程式一當跑起來你如果把它停了下次隻能重新再跑.于是有人琢磨着如果程式在記憶體中跑了一段時間還沒完,可以把記憶體中所有的資訊切換出來儲存到磁盤上.等下次再跑時可以不用從頭跑,而是再把磁盤上的資訊切換進記憶體接着上次的跑.
4.分時,多程序
前面說的三種情況都是一次隻能一個使用者程式在跑,此時有多少記憶體就用多少,不夠用就報錯呗,很簡單,沒涉及啥太複雜的技術.但是這樣顯然不能充分利用CPU,記憶體資源.因為記憶體變得越來越大,而一個程式可能隻用占一點點記憶體,這樣會空出一大片.而且CPU也是跑得賊快的.一眨眼就跑完了,隻有你進行那些IO操作時可能就慢的像蝸牛了.于是有人就琢磨着,應該可以讓幾個程式同時跑,一個程式對應一個程序,都同時在記憶體中占一片地盤.然後就通過分時輪流使用CPU.
此時作業系統做的事就多很多了啊.首先是要保證每個程序的獨立,不能讓一個程序輕易使用别的程序的記憶體了.然後有時還得實作程序間的通信,要防止死鎖之類的麻煩事.
程序加載進記憶體,等待獲得CPU,然後運作
記憶體配置設定
當出現了多程序的概念後.自然就需要想出很多花樣出合理的配置設定和利用記憶體這個寶貴的東東.作業系統記憶體管理中涉及到的術語可能看得人頭大.實際上用通俗點的話歸納下就做兩方面的事
1.合理的給每個程序配置設定一塊記憶體.當記憶體滿了時把一部分程序的記憶體切換出去放硬碟上.恰當的時候再從硬碟切換回記憶體
2.虛拟記憶體技術.實際上就是把硬碟也當記憶體用,隻不過要多做些切換罷了,在硬碟和記憶體之間切換.
記憶體位址
先不管記憶體怎麼分塊.我們首先會想到要用記憶體該怎麼去通路? 你可能說肯定是通過位址嘛.硬碟通路就是通過一個位址,然後用磁頭去讀寫嘛.
記憶體與硬碟間來回切換,會導緻記憶體位址變化
如果隻讓一個程序在記憶體中,開始跑時全部加載,然後跑完了再釋放記憶體.這樣像硬碟一樣直接通過記憶體的實際位址通路是沒太多問題的. 但如果是多程序共用記憶體,并且還會在硬碟和記憶體間切換.此時就會有問題.假如你的程式跑了一部分,然後全部切換到硬碟,那些儲存的資訊裡面有涉及到記憶體位址.等再切換到記憶體中時,你之前占的那記憶體可能被别的程序占用了,而系統分給你的是另外一塊記憶體.那樣的話你根據之前那些記憶體位址資訊再讀寫資料時就會出錯.咋整呢?
記憶體實體位址與邏輯位址
像在C,C++中我們一般用指針來指向記憶體位址.那你可能會問指針指的記憶體位址是記憶體的實體位址嗎 ?
如果是實際位址的話就出現上面說的問題了. 指針指向的實際上是一個邏輯位址.你可以這樣想,在一個程序内我們先把所有指針指定一些固定的值(也就是邏輯位址),比如從0開始遞增,然後實際上加載到記憶體中時(每次加載時的起始位址可能不一樣),再用邏輯位址做偏移量加上記憶體中的實際起始位址就是實際的記憶體位址了. 你可能會奇怪那麼每個程序的記憶體塊的起始位置放哪裡啊? 那個起始位址值是放在寄存器中.寄存器實際上可以看成一個速度更快的記憶體,在CPU呆一起,是以劃分子產品時我們一般分到CPU那一塊去了.
當然從邏輯位址映射到實體位址肯定不止一種方法的,如果用到了分頁技術,我們還可以把每個邏輯位址映射到實體位址中的不同頁面(page)
不過上面說的也不是太準确,我們隻能說我們用的絕大部分應用程式中涉及到的指針指向的記憶體位址是指向邏輯位址的.實際上也有些特殊的情況指針會指向記憶體實體位址,比如在嵌入式系統中,可能就一個程序用那記憶體,不用做啥切換,我們就可以用指針直接指向實際的實體記憶體位址.另外一般的桌面電腦中,記憶體中會劃分出一塊固定的記憶體來加載作業系統代碼.作業系統中一部分核心程式會常駐記憶體,一直呆一個固定的地方,不會被切換到硬碟上去.此時指針也可以直接指向實體記憶體.
虛拟位址空間
虛拟位址與邏輯位址關系
毫無疑問除了實體位址是真實存在的記憶體位址外.其他位址都是我們虛構出來的.我們知道作業系統把硬體封裝抽象了,我們程式設計時基本不會直接面對硬體.那我們可以把虛拟位址空間看成是抽象出來的記憶體.當每個程式運作時(在windows中,linux或其他系統我還不确定)系統會給程式配置設定一個虛拟位址空間,就相當于給你一個記憶體,你盡量去用就是,不用管其他的事了.當你申請一塊記憶體後會傳回記憶體位址,這個就是邏輯位址,邏輯位址就是指向虛拟位址空間中某塊位址
虛拟位址空間大小
在32位windows系統中,可用的虛拟位址空間是2的32次方,也就是4G. 你可能會奇怪這是怎麼算出來的啊.因為32位系統顧名思義是因為處理器一次隻能處理32位bit的資訊,這樣一來位址總線傳的記憶體位址最多隻能用32個bit來表示,是以隻能表示4G的記憶體位址了.不過我們會把其中的2G分給系統空間,隻留下2G給使用者空間.是以任何一個win32應用程式運作時系統會給配置設定2G的虛拟位址空間.不過你可以指定讓使用者空間超過2G,最大可增至3G,不過這樣系統空間就僅有1G了.
在64們的windows系統中,可用的虛拟位址空間是2的64次方,貌似是40多億G.但顯然我們用不了這麼多,用了也沒意義.是以我們隻用到這些虛拟位址空間的一部分.8T使用者空間, 248T系統空間.是以任何一個win64應用程式運作時系統會給配置設定8T的虛拟位址空間
我們知道當多程序在記憶體中共存時,如果記憶體足夠大都夠用,大家相安無事自然是最理想的事了.不過現實往往沒那麼美好.有些時候記憶體會滿了,不夠用了.此時必須将程序從記憶體移到硬碟中去.有空間時可能又會被移回記憶體來.專業點的說法叫 滾出(roll-out),滾進(roll-in).或者叫換出,換入.
在進行換出,換入時你可能會想到兩種方法
(1)以程序為機關換出換進,這是最簡單最容易想到的.但是顯然不夠靈活,因為一個程序所需的記憶體空間較大.是以這種交換技術現在用的不多
(2)隻交換程序的一部分. 通過頁式或段式記憶體管理先把程序的虛拟位址空間劃分為若幹頁面或段,這樣交換時就可就交換頁或段.
頁式存儲管理
所謂分頁就是把程序的虛拟位址空間劃分成大小均勻的一頁頁的(實際上就是一塊塊的,把程序切成幾小塊罷了),比如一頁是1K,然後把實體記憶體也劃分成一頁頁的.然後再把兩者映射起來.如下圖.哎發現有時說一堆話學不如一個圖頂用,下面的圖都是我們從别處拷來的,圖檔原位址http://www.doc88.com/p-982340562158.html
當然這個映射關系的資訊肯定要儲存在哪.都儲存在寄存器中.然後通過邏輯位址找實體位址的流程如下圖
段式存儲管理
分頁時,就是不管三七二十一,都一刀切,把程式切成均勻的一頁頁的.但我們知道程式實際運轉時是成為很多子產品的,比如一個函數可能是一個子產品.如果按程式的邏輯結構來分成更小的組成部分可能更合理.因為程式執行時也可以那樣分成一個個小的機關去執行的嘛.這裡我們把程式的更小機關叫作業
不過分段存儲管理其實跟分頁管理大的思想理念是一樣的,都是把程式分成更小的機關嘛,便于交換而已.隻不過分段不是均勻分成固定大小的頁,而是根據實際情況分在大小不均的段.此時由于段大小不一,是以除了知道每個段的開始位址還必須要有段表長度的資訊.
分頁與分段結合
分頁與分段自然是各有好處.分段如果每段太大了自然不太好,那還不如幹脆把整個程式交換出去得了,不用整得這麼麻煩
于是有人想着把這兩種方法結合起來,叫段頁式存儲管理.你要以在一段内再使用分頁技術
虛拟記憶體
我們以前講了在32的windows上,每個程式運作時都會配置設定2G的虛拟位址空間.就算你調大的話也最多就3G.從這句話裡我們可以延伸出這樣一些結論.
1.由于32的系統尋址空間隻有4G,是以你整個大于4G的記憶體完全是浪費資源.系統隻會用到其中的4G,多出來的根本不會去用.
2.你可能想着每個程序都是2G的虛拟位址空間,那一個程序加載進記憶體豈不是會把記憶體塞滿了啊.實際上不會的,因為一來嘛配置設定給你2G位址空間.你不一定用這麼多,可能隻用10M,這樣加載到記憶體的時候隻加載你實際用的.另外就是不會一次把所有程序需要的記憶體配置設定下來然後把程式加載進來,而隻是加載暫時需要的程式代碼或資料
3.因為每個程序位址空間最大也隻能整個3G出來.是以如果你的程序一跑時需要4G的位址空間才夠用.那你的電腦肯定沒法支援的.是以如果一些大型遊戲需要記憶體特别多,你32的系統不管怎麼整就玩不了.
另外你就肯定會有疑問. 過去個幾年我們買電腦時貌似記憶體很多都512M,1G就算多了.然後嘛基本是32的系統.那如果某個程式實際有用到1G記憶體咋整? 因為雖然說程式中配置設定時是用的虛拟位址空間,但如果你在那2G的虛拟位址空間中實際有用到1G,最後就要映射到實際記憶體中去的啊.而且你1G的記憶體位址可是不能出現重複.這樣那512記憶體肯定不夠用的.
後面就出現了個虛拟記憶體的概念.就是劃出一部分硬碟來當作記憶體用.當在虛拟記憶體空間中實際用到的記憶體大于實體記憶體時需要用到虛拟記憶體.在windows中可以在Advanced system settings那裡面去設定虛拟記憶體的大小.不管如果是32位的話虛拟記憶體加上實際記憶體肯定也不能大于4G,不然多出來的那部分也是沒有用處的.
假如還是上面的例子,512M記憶體,然後有1G的程序.你于是可以弄個1G的虛拟記憶體.然後映射的時候,先在實際的記憶體中映射,可能除開系統需要的一部分外就剩下300了.于是就先映射這300的記憶體,剩下的再去虛拟記憶體中映射.
反正你的用的時候隻要發個邏輯位址過去.剩下的事就不用管,作業系統有個MMU(memory management unit),裡面會有個頁表.反正你輸入邏輯位址它最終給你轉換成實體記憶體位址或硬碟位址.
至于具體細節比較麻煩的.大概思路是,程序一般會采取分頁技術,分成大小一樣的很多頁,每頁有編号.然後頁表裡面會把你每頁對應記憶體中具體的一塊記憶體.當超過那300的實際上就對應于虛拟記憶體中去了.(實際上就是硬碟上的一個swp檔案),肯定也有标志資訊表明這部分是在硬碟上. 于是當程序運作起來時,需要用到某個位址時就映射到虛拟記憶體中時就會出現所謂的缺頁,那頁不在記憶體中嘛,于是就需要去硬碟上把資料讀進記憶體來,如果記憶體滿了就需要置換出去一些頁. 是以你設定了虛拟記憶體,最後如果真的會有用到,那你會看到硬碟轉的很快,然後程式運作速度會變慢.硬碟的操作比記憶體慢很多。
原文連結:https://blog.csdn.net/lujiandong1/article/details/44569161