摘要:這個(gè)辦法,在現(xiàn)在計(jì)算機(jī)的內(nèi)存管理里面,就叫作內(nèi)存分頁(yè)和分段這樣分配一整段連續(xù)的空間給到程序相比分頁(yè)則是把整個(gè)物理內(nèi)存空間切成一段段固定尺寸的大小而對(duì)應(yīng)的程序所需要占用的虛擬內(nèi)存空間,也會(huì)同樣切成一段段固定尺寸的大小。
比爾·蓋茨在上世紀(jì)80年代說(shuō)的“640K ought to be enough for anyone”
也就是“640K內(nèi)存對(duì)哪個(gè)人來(lái)說(shuō)都?jí)蛴昧恕?/p>
那個(gè)年代,微軟開(kāi)發(fā)的還是DOS操作系統(tǒng),程序員們還在絞盡腦汁,想要用好這極為有限的640K內(nèi)存
而現(xiàn)在,我手頭的Mac Book Pro已經(jīng)是16G內(nèi)存了,上升了一萬(wàn)倍還不止。
那比爾·蓋茨這句話(huà)在當(dāng)時(shí)也是完全的無(wú)稽之談么?有沒(méi)有哪怕一點(diǎn)點(diǎn)的道理呢?這一講里,我就和你一起來(lái)看一看。
1 程序裝載的挑戰(zhàn)在運(yùn)行這些可執(zhí)行文件的時(shí)候,我們其實(shí)是通過(guò)一個(gè)裝載器,解析ELF或者PE格式的可執(zhí)行文件
裝載器會(huì)把對(duì)應(yīng)的指令和數(shù)據(jù)加載到內(nèi)存里面來(lái),讓CPU去執(zhí)行。
裝載到內(nèi)存,裝載器需要滿(mǎn)足兩個(gè)要求
可執(zhí)行程序加載后占用的內(nèi)存空間應(yīng)該是連續(xù)的
執(zhí)行指令的時(shí)候,程序計(jì)數(shù)器是順序地一條一條指令執(zhí)行。這意味著,這一條條指令需要連續(xù)地存儲(chǔ)在一起
需要同時(shí)加載很多個(gè)程序,并且不能讓程序自己規(guī)定在內(nèi)存中加載的位置
雖然編譯出來(lái)的指令里已經(jīng)有了對(duì)應(yīng)的各種各樣的內(nèi)存地址,但是實(shí)際加載的時(shí)候,我們其實(shí)沒(méi)有辦法確保,這個(gè)程序一定加載在哪一段內(nèi)存地址上
因?yàn)楝F(xiàn)在的計(jì)算機(jī)通常會(huì)同時(shí)運(yùn)行很多個(gè)程序,可能你想要的內(nèi)存地址已經(jīng)被其他加載了的程序占用
要滿(mǎn)足這兩個(gè)基本的要求,我們很容易想到一個(gè)辦法。那就是我們可以在內(nèi)存里面,找到一段連續(xù)的內(nèi)存空間,然后分配給裝載的程序,然后把這段連續(xù)的內(nèi)存空間地址,和整個(gè)程序指令里指定的內(nèi)存地址做一個(gè)映射。
指令里用到的內(nèi)存地址叫作虛擬內(nèi)存地址(Virtual Memory Address)
實(shí)際在內(nèi)存硬件里面的空間地址,我們叫物理內(nèi)存地址(Physical Memory Address)
程序里有指令和各種內(nèi)存地址,我們只需要關(guān)心虛擬內(nèi)存地址就行了
對(duì)于任何一個(gè)程序來(lái)說(shuō),它看到的都是同樣的內(nèi)存地址。我們維護(hù)一個(gè)虛擬內(nèi)存到物理內(nèi)存的映射表,這樣實(shí)際程序指令執(zhí)行的時(shí)候,會(huì)通過(guò)虛擬內(nèi)存地址,找到對(duì)應(yīng)的物理內(nèi)存地址,然后執(zhí)行。因?yàn)槭沁B續(xù)的內(nèi)存地址空間,所以我們只需要維護(hù)映射關(guān)系的起始地址和對(duì)應(yīng)的空間大小就可以了。
2 內(nèi)存分段這種找出一段連續(xù)的物理內(nèi)存和虛擬內(nèi)存地址進(jìn)行映射的方法,我們叫分段(Segmentation)。
這里的段,就是指系統(tǒng)分配出來(lái)的那個(gè)連續(xù)的內(nèi)存空間。
分段的辦法很好,解決了程序本身不需要關(guān)心具體的物理內(nèi)存地址的問(wèn)題,但它也有一些不足之處,第一個(gè)就是內(nèi)存碎片(Memory Fragmentation)
舉個(gè)例子
電腦有1GB的內(nèi)存
先啟動(dòng)一個(gè)圖形渲染程序,占用了512MB的內(nèi)存
接著啟動(dòng)一個(gè)Chrome瀏覽器,占用了128MB內(nèi)存
再啟動(dòng)一個(gè)PY程序,占用了256MB內(nèi)存
這個(gè)時(shí)候,我們關(guān)掉Chrome,于是空閑內(nèi)存還有1024 - 512 - 256 = 256MB
按理來(lái)說(shuō),我們有足夠的空間再去裝載一個(gè)200MB的程序。但是,這256MB的內(nèi)存空間不是連續(xù)的,而是被分成了兩段128MB的內(nèi)存
因此,實(shí)際情況是,我們的程序沒(méi)辦法加載進(jìn)來(lái)。
當(dāng)然了,有辦法解決 --- 內(nèi)存交換(Memory Swapping)
我們可以把Python程序占用的256MB內(nèi)存寫(xiě)到硬盤(pán),再?gòu)挠脖P(pán)上讀回來(lái)到內(nèi)存里面
不過(guò)讀回來(lái)的時(shí)候,我們不再把它加載到原來(lái)的位置,而是緊緊跟在那已經(jīng)被占用了的512MB內(nèi)存后面
這樣,我們就有了連續(xù)的256MB內(nèi)存空間,就可以去加載一個(gè)新的200MB的程序。如果你自己安裝過(guò)Linux操作系統(tǒng),你應(yīng)該遇到過(guò)分配一個(gè)swap硬盤(pán)分區(qū)的問(wèn)題
這塊分出來(lái)的磁盤(pán)空間,其實(shí)就是專(zhuān)門(mén)給Linux操作系統(tǒng)進(jìn)行內(nèi)存交換用的。
虛擬內(nèi)存、分段,再加上內(nèi)存交換
看起來(lái)似乎已經(jīng)解決了計(jì)算機(jī)同時(shí)裝載運(yùn)行很多個(gè)程序的問(wèn)題
不過(guò)三者的組合仍然會(huì)遇到一個(gè)性能瓶頸
硬盤(pán)的訪(fǎng)問(wèn)速度要比內(nèi)存慢很多
而每一次內(nèi)存交換,我們都需要把一大段連續(xù)的內(nèi)存數(shù)據(jù)寫(xiě)到硬盤(pán)上
所以,如果內(nèi)存交換的時(shí)候,交換的是一個(gè)很占內(nèi)存空間的程序,這樣整個(gè)機(jī)器都會(huì)顯得卡頓。
3 內(nèi)存分頁(yè)既然問(wèn)題出在內(nèi)存碎片和內(nèi)存交換的空間太大上,那么解決問(wèn)題的辦法就是,少出現(xiàn)一些內(nèi)存碎片。
另外,當(dāng)需要進(jìn)行內(nèi)存交換的時(shí)候,讓需要交換寫(xiě)入或者從磁盤(pán)裝載的數(shù)據(jù)更少一點(diǎn),這樣就可以解決這個(gè)問(wèn)題。
這個(gè)辦法,在現(xiàn)在計(jì)算機(jī)的內(nèi)存管理里面,就叫作內(nèi)存分頁(yè)(Paging)
**和分段這樣分配一整段連續(xù)的空間給到程序相比
分頁(yè)則是把整個(gè)物理內(nèi)存空間切成一段段固定尺寸的大小**
而對(duì)應(yīng)的程序所需要占用的虛擬內(nèi)存空間,也會(huì)同樣切成一段段固定尺寸的大小。
這樣一個(gè)連續(xù)并且尺寸固定的內(nèi)存空間,我們叫頁(yè)(Page)。
從虛擬內(nèi)存到物理內(nèi)存的映射,不再是拿整段連續(xù)的內(nèi)存的物理地址,而是按照一個(gè)個(gè)頁(yè)來(lái)的。
頁(yè)的尺寸一般遠(yuǎn)遠(yuǎn)小于整個(gè)程序的大小。
在Linux下,我們通常只設(shè)置成4KB。你可以通過(guò)命令看看你手頭的Linux系統(tǒng)設(shè)置的頁(yè)的大小。
由于內(nèi)存空間都是預(yù)先劃分好的,也就沒(méi)有不能使用的碎片,而只有被釋放出來(lái)的很多4KB的頁(yè)。
即使內(nèi)存空間不夠,需要讓現(xiàn)有的、正在運(yùn)行的其他程序
通過(guò)內(nèi)存交換釋放出一些內(nèi)存的頁(yè)出來(lái),一次性寫(xiě)入磁盤(pán)的也只有少數(shù)的一個(gè)頁(yè)或者幾個(gè)頁(yè),不會(huì)花太多時(shí)間,讓整個(gè)機(jī)器被內(nèi)存交換的過(guò)程給卡住。
分頁(yè)的方式使得加載程序的時(shí)候,不再需要一次性把程序加載到物理內(nèi)存中
可以在進(jìn)行虛擬內(nèi)存和物理內(nèi)存的頁(yè)之間的映射后,并不真的把頁(yè)加載到物理內(nèi)存里,而是只在程序運(yùn)行中,需要用到對(duì)應(yīng)虛擬內(nèi)存頁(yè)里面的指令和數(shù)據(jù)時(shí),再加載到物理內(nèi)存里面去。
實(shí)際上,我們的操作系統(tǒng),的確是這么做的
當(dāng)要讀取特定的頁(yè),卻發(fā)現(xiàn)數(shù)據(jù)并沒(méi)有加載到物理內(nèi)存里的時(shí)候,就會(huì)觸發(fā)一個(gè)來(lái)自于CPU的缺頁(yè)錯(cuò)誤(Page Fault)
操作系統(tǒng)會(huì)捕捉到這個(gè)錯(cuò)誤,然后將對(duì)應(yīng)的頁(yè),從存放在硬盤(pán)上的虛擬內(nèi)存里讀取出來(lái),加載到物理內(nèi)存里。這種方式,使得我們可以運(yùn)行那些遠(yuǎn)大于我們實(shí)際物理內(nèi)存的程序。同時(shí),這樣一來(lái),任何程序都不需要一次性加載完所有指令和數(shù)據(jù),只需要加載當(dāng)前需要用到就行了。
通過(guò)虛擬內(nèi)存、內(nèi)存交換和內(nèi)存分頁(yè)這三個(gè)技術(shù)的組合,我們最終得到了一個(gè)讓程序不需要考慮實(shí)際的物理內(nèi)存地址、大小和當(dāng)前分配空間的解決方案。
這些技術(shù)和方法,對(duì)于我們程序的編寫(xiě)、編譯和鏈接過(guò)程都是透明的。這也是我們?cè)谟?jì)算機(jī)的軟硬件開(kāi)發(fā)中常用的一種方法,就是加入一個(gè)間接層。
通過(guò)引入虛擬內(nèi)存、頁(yè)映射和內(nèi)存交換,我們的程序本身,就不再需要考慮對(duì)應(yīng)的真實(shí)的內(nèi)存地址、程序加載、內(nèi)存管理等問(wèn)題了。任何一個(gè)程序,都只需要把內(nèi)存當(dāng)成是一塊完整而連續(xù)的空間來(lái)直接使用。
4 總結(jié)電腦只要640K內(nèi)存就夠了嗎?很顯然,現(xiàn)在來(lái)看,比爾·蓋茨的這個(gè)判斷是不合理的,那為什么他會(huì)這么認(rèn)為呢?因?yàn)樗彩且粋€(gè)很優(yōu)秀的程序員?。?/p>
在虛擬內(nèi)存、內(nèi)存交換和內(nèi)存分頁(yè)這三者結(jié)合之下,你會(huì)發(fā)現(xiàn),其實(shí)要運(yùn)行一個(gè)程序,“必需”的內(nèi)存是很少的。CPU只需要執(zhí)行當(dāng)前的指令,極限情況下,內(nèi)存也只需要加載一頁(yè)就好了。再大的程序,也可以分成一頁(yè)。每次,只在需要用到對(duì)應(yīng)的數(shù)據(jù)和指令的時(shí)候,從硬盤(pán)上交換到內(nèi)存里面來(lái)就好了。以我們現(xiàn)在4K內(nèi)存一頁(yè)的大小,640K內(nèi)存也能放下足足160頁(yè)呢,也無(wú)怪乎在比爾·蓋茨會(huì)說(shuō)出“640K ought to be enough for anyone”這樣的話(huà)。
不過(guò)呢,硬盤(pán)的訪(fǎng)問(wèn)速度比內(nèi)存慢很多,所以我們現(xiàn)在的計(jì)算機(jī),沒(méi)有個(gè)幾G的內(nèi)存都不好意思和人打招呼。
那么,除了程序分頁(yè)裝載這種方式之外,我們還有其他優(yōu)化內(nèi)存使用的方式么?下一講,我們就一起來(lái)看看“動(dòng)態(tài)裝載”,學(xué)習(xí)一下讓兩個(gè)不同的應(yīng)用程序,共用一個(gè)共享程序庫(kù)的辦法。
5 推薦閱讀想要更深入地了解代碼裝載的詳細(xì)過(guò)程,推薦你閱讀《程序員的自我修養(yǎng)——鏈接、裝載和庫(kù)》的第1章和第6章。
6 思考在Java這樣使用虛擬機(jī)的編程語(yǔ)言里面,我們寫(xiě)的程序是怎么裝載到內(nèi)存里面來(lái)的呢?它也和我們講的一樣,是通過(guò)內(nèi)存分頁(yè)和內(nèi)存交換的方式加載到內(nèi)存里面來(lái)的么?
jvm已經(jīng)是上層應(yīng)用,無(wú)需考慮物理分頁(yè),一般更直接是考慮對(duì)象本身的空間大小,物理硬件管理統(tǒng)一由承載jvm的操縱系統(tǒng)去解決吧
參考深入淺出計(jì)算機(jī)組成原理
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76255.html
摘要:鏈接器會(huì)掃描所有輸入的目標(biāo)文件,然后把所有符號(hào)表里的信息收集起來(lái),構(gòu)成一個(gè)全局的符號(hào)表。這是一本難得的講解程序的鏈接裝載和運(yùn)行的好書(shū)。 showImg(https://image-static.segmentfault.com/396/693/396693929-5d558865c3a7e_articlex); 既然程序最終都被變成了一條條機(jī)器碼去執(zhí)行,那為什么同一個(gè)程序,在同一臺(tái)計(jì)算...
摘要:不同的進(jìn)程,調(diào)用同樣的,各自里面指向最終加載的動(dòng)態(tài)鏈接庫(kù)里面的虛擬內(nèi)存地址是不同的。實(shí)際上,在進(jìn)行程序開(kāi)發(fā),一直會(huì)用到各種各樣的動(dòng)態(tài)鏈接庫(kù)。通過(guò)動(dòng)態(tài)鏈接這個(gè)方式,可以說(shuō)徹底解決了這個(gè)問(wèn)題。參考深入淺出計(jì)算機(jī)組成原理 showImg(https://image-static.segmentfault.com/734/608/734608610-5d5846d292aa0_articlex...
摘要:計(jì)算機(jī)組成中的大量原理和設(shè)計(jì),都對(duì)應(yīng)著性能這個(gè)詞。時(shí)間的倒數(shù)性能計(jì)算機(jī)的性能,其實(shí)和體力勞動(dòng)很像,好比是我們要搬東西。對(duì)于計(jì)算機(jī)的性能,我們需要有個(gè)標(biāo)準(zhǔn)來(lái)衡量?;ǖ臅r(shí)間越少,自然性能就越好。 0 學(xué)習(xí)路線(xiàn)的知識(shí)點(diǎn)概括 showImg(https://segmentfault.com/img/remote/1460000020031616?w=3832&h=2540); 學(xué)習(xí)計(jì)算機(jī)組成原...
摘要:在中央處理器的控制部件中,包含的寄存器有指令寄存器和程序計(jì)數(shù)器。這條指令的第一個(gè)操作數(shù),代表累加寄存器在中央處理器中,累加器是一種寄存器,用來(lái)儲(chǔ)存計(jì)算產(chǎn)生的中間結(jié)果。第二個(gè)操作數(shù)則是進(jìn)制的的表示。 showImg(https://ask.qcloudimg.com/http-save/1752328/57mlmnq3i5.png); CPU執(zhí)行的也不只是一條指令,一般一個(gè)程序包含很多條...
摘要:馮諾依曼體系結(jié)構(gòu)示意圖總結(jié)馮諾依曼體系結(jié)構(gòu)確立了我們現(xiàn)在每天使用的計(jì)算機(jī)硬件的基礎(chǔ)架構(gòu)。因此,學(xué)習(xí)計(jì)算機(jī)組成原理,其實(shí)就是學(xué)習(xí)和拆解馮諾依曼體系結(jié)構(gòu)。 showImg(https://ask.qcloudimg.com/http-save/1752328/g6cdrb45jg.png); 1 計(jì)算機(jī)的基本硬件組成 早期,DIY一臺(tái)計(jì)算機(jī),要先有三大件 CPU 內(nèi)存 主板 1.1 C...
閱讀 2156·2023-04-25 14:56
閱讀 2479·2021-11-16 11:44
閱讀 2709·2021-09-22 15:00
閱讀 1912·2019-08-29 16:55
閱讀 2190·2019-08-29 14:04
閱讀 2315·2019-08-29 11:23
閱讀 3688·2019-08-26 10:46
閱讀 1917·2019-08-22 18:43