(2020.11.09 Mon)
簡單的說茧痕,內(nèi)存就是一個(gè)數(shù)據(jù)貨架枯跑,其最小的存儲(chǔ)單位大多是一個(gè)字節(jié)Byte。內(nèi)存用內(nèi)存地址(Memory address)來為每個(gè)字節(jié)的數(shù)據(jù)順序編號。內(nèi)存地址從0開始砸狞,每次增加1。線性增加的存儲(chǔ)器地址稱為線性地址(linear address)镀梭。用十六進(jìn)制來表示內(nèi)存地址刀森,如0x1A010CB0,其中的'0x'表示十六進(jìn)制报账,后面跟著的就是內(nèi)存地址的十六進(jìn)制數(shù)研底。
內(nèi)存地址的編號有上限。地址空間的范圍和地址總線(address bus)的位數(shù)直接有關(guān)透罢。CPU通過地址總線進(jìn)來向內(nèi)存地址說明想要存取數(shù)據(jù)的地址榜晦。32位CPU有32個(gè)針腳可以傳輸?shù)刂沸畔ⅰC總€(gè)針腳對應(yīng)了一位羽圃。該針腳的高電壓表示1乾胶,否則是0。內(nèi)存能把這32位電壓高低信息轉(zhuǎn)換成32位二進(jìn)制數(shù)朽寞,從而知道CPU想要的是哪個(gè)位置的數(shù)據(jù)识窿。32位地址空間就是從0x00000000到0xFFFFFFFF。
內(nèi)存的存儲(chǔ)單元采用了隨機(jī)讀取存儲(chǔ)器(Random Access Memory, RAM)脑融,其中的隨機(jī)讀取指的是存儲(chǔ)器的讀取時(shí)間和數(shù)據(jù)所在位置無關(guān)腕扶。相反,磁帶和錄像帶就不是隨機(jī)讀取吨掌。隨機(jī)讀取的特性是內(nèi)存成為主存儲(chǔ)器的關(guān)鍵因素半抱。
內(nèi)存的存儲(chǔ)空間,可以滿足內(nèi)核的運(yùn)行需求膜宋,還能支持運(yùn)行中的進(jìn)程窿侈,即其存儲(chǔ)能力和計(jì)算機(jī)運(yùn)行狀態(tài)的數(shù)據(jù)總量相當(dāng)。
虛擬內(nèi)存
在Linux下秋茫,進(jìn)程不能直接讀寫內(nèi)存中地址為0x1位置的數(shù)據(jù)史简,進(jìn)程中能訪問的地址只能是虛擬內(nèi)存地址(virtual memory address)。操作系統(tǒng)會(huì)把虛擬內(nèi)存地址翻譯成真實(shí)的內(nèi)存地址肛著。這種內(nèi)存管理方式圆兵,叫做虛擬內(nèi)存(virtual memory)。
每個(gè)進(jìn)程有自己的一套虛擬內(nèi)存地址枢贿,用來給自己的進(jìn)程空間標(biāo)號殉农。從功能上來,虛擬內(nèi)存地址和內(nèi)存一樣局荚,都是為數(shù)據(jù)提供索引超凳。進(jìn)程間的虛擬內(nèi)存地址相互獨(dú)立愈污,因此兩個(gè)進(jìn)程完全可能有一樣的虛擬內(nèi)存地址。
虛擬內(nèi)存和物理內(nèi)存有一定的對應(yīng)關(guān)系轮傍,對進(jìn)程某個(gè)虛擬內(nèi)存地址的操作暂雹,會(huì)被CPU翻譯成對某個(gè)具體內(nèi)存地址的操作。
應(yīng)用程序?qū)ξ锢韮?nèi)存地址一無所知创夜,只能和虛擬內(nèi)存溝通進(jìn)行數(shù)據(jù)讀寫杭跪。程序中表達(dá)的內(nèi)存地址,都是虛擬內(nèi)存地址驰吓。進(jìn)程對虛擬內(nèi)存的操作會(huì)被系統(tǒng)翻譯成對物理內(nèi)存地址的操作揍魂。該轉(zhuǎn)換/翻譯過程由操作系統(tǒng)負(fù)責(zé)。C語言中表達(dá)的內(nèi)存地址棚瘟,都是虛擬內(nèi)存地址。用下面指令打印變量地址
int v =0;
printf('%p', (void*)&v);
借助虛擬內(nèi)存喜最,系統(tǒng)可以保障進(jìn)程空間的獨(dú)立性偎蘸。只要操作系統(tǒng)把兩個(gè)進(jìn)程的進(jìn)程空間對應(yīng)到不同的內(nèi)存區(qū)域,進(jìn)程間就不可能互相篡改對方的數(shù)據(jù)瞬内,進(jìn)程出錯(cuò)的可能性就大大減少了迷雪。
有了虛擬內(nèi)存,內(nèi)存共享也變得簡單虫蝶。操作系統(tǒng)把同一物理內(nèi)存區(qū)對應(yīng)到多個(gè)進(jìn)程空間章咧,不需要任何數(shù)據(jù)復(fù)制,多個(gè)進(jìn)程就可以看到相同的數(shù)據(jù)能真。內(nèi)核共享庫的映射赁严,就是通過這種方式進(jìn)行的。每個(gè)進(jìn)程空間最初一部分的虛擬內(nèi)存地址粉铐,都對應(yīng)了物理內(nèi)存中預(yù)留給內(nèi)核的空間疼约,所以進(jìn)程即可共享同一套內(nèi)核數(shù)據(jù)。IPO中的共享內(nèi)存蝙泼,也有賴于虛擬內(nèi)存地址程剥。
內(nèi)存分頁P(yáng)aging
虛擬內(nèi)存和物理內(nèi)存的對應(yīng)關(guān)系,被保存在一個(gè)表中汤踏,為了翻譯速度足夠快织鲸,這個(gè)表就需要放在內(nèi)存中。為了減少這個(gè)對應(yīng)表的占用空間溪胶,Linux使用內(nèi)存分頁(paging)來管理虛擬內(nèi)存和物理內(nèi)存的對應(yīng)關(guān)系搂擦。
所謂分頁,就是以更大尺寸的單位頁(page)來管理內(nèi)存哗脖。Linux中通常頁的大小是4KB盾饮。用下面指令獲得當(dāng)前頁的大小
$getconf PAGE_SIZE
返回4096,即表示每個(gè)內(nèi)存也存放4096個(gè)字節(jié),i.e., 4KB丘损。物理內(nèi)存和虛擬內(nèi)存都被分割成頁普办。
無論是物理內(nèi)存還是虛擬內(nèi)存,一頁之內(nèi)的地址都是連續(xù)的徘钥。一個(gè)虛擬頁和物理頁對應(yīng)起來衔蹲,頁內(nèi)的數(shù)據(jù)就可以按順序一一對應(yīng)。兩種地址的末尾部分應(yīng)該完全相同呈础。舆驶,所以地址的最后12位對應(yīng)關(guān)系天然城里,這部分被成為偏移量(offset)而钞,表達(dá)了該字節(jié)在頁內(nèi)的位置沙廉。地址的前一部分是頁編號,操作系統(tǒng)只需記錄頁編號的對應(yīng)關(guān)系臼节,所以對應(yīng)關(guān)系的數(shù)字就縮減為原來的4096分之一撬陵。
多級分頁
內(nèi)存分頁管理制度的關(guān)鍵在于管理進(jìn)程空間頁和物理頁的對應(yīng)關(guān)系。操作系統(tǒng)把對應(yīng)關(guān)系記錄在分頁表(page table)中网缝。為保證查詢速度巨税,分頁表也會(huì)保存在內(nèi)存中 。分頁表實(shí)現(xiàn)的最簡單方式就是把所有對應(yīng)關(guān)系記錄到同一個(gè)線性列表中粉臊。
單一分頁表需要給每一個(gè)虛擬頁預(yù)留一條記錄的位置草添。對任一個(gè) 進(jìn)程,其進(jìn)程空間真正用到的地址都相當(dāng)有限扼仲。進(jìn)程中的堆棧很少會(huì)沾滿進(jìn)程空間远寸,如果使用連續(xù)分頁表,很多條目沒有真正用到屠凶。Linux的分頁表采取多層的數(shù)據(jù)結(jié)構(gòu)而晒,減少所需空間。
地址被分為頁編號和偏移量兩個(gè)部分阅畴,用單層的分頁表記錄頁編號部分的對應(yīng)關(guān)系倡怎。對于多層分頁表來說,進(jìn)一步分割頁編號為兩個(gè)或更多的部分贱枣,用兩層或更多層的分頁表來記錄對應(yīng)關(guān)系监署。
多層分頁表就好像完整的電話號碼分成區(qū)號,同一地區(qū)的電話號和對應(yīng)人名記錄在同一個(gè)本子上纽哥,再用一個(gè)上級本子記錄區(qū)號和各個(gè)小本子的對應(yīng)關(guān)系钠乏。若某個(gè)區(qū)號沒有被使用,則上級本子上把該區(qū)號標(biāo)記為空春塌。同樣晓避,一級分頁表中某個(gè)記錄為空簇捍,則該記錄開頭的虛擬地址段沒有使用,相應(yīng)的二級表就不需要存在了俏拱。多層分頁表占據(jù)的空間比單層分頁少了很多暑塑。
多層分頁表的另一個(gè)優(yōu)勢。單層分頁表必須存在于連續(xù)的內(nèi)存空間锅必,而多層分頁表的二級表事格,可以散布于內(nèi)存的不同位置,可以利用零碎空間來存儲(chǔ)分頁表搞隐。
Reference
1 Vamei驹愚,周昕梓著,樹莓派開始劣纲,玩轉(zhuǎn)Linux逢捺,中國工信出版社,電子工業(yè)出版社癞季,2018