顯存映射
化學顯存合稱為尋址,動態隨機存取顯存(DRAM)。只有核心才可以直接存取數學顯存。
Linux核心為每位行程提供了一個獨立的虛擬位址空間,但是這個位址空間是連續的。這樣,進程就可以很方便地存取顯存,更精確地說是存取虛擬顯存。虛擬位址空間的內部又被分成核心空間和使用者空間兩份。
#進程在用戶態時,只能存取用戶空間顯存;只有步入內核態後,才可以存取內核空間顯存。其實每位行程的位址空間都包含了核心空間,但這種核心空間,雖然關聯的都是相同的化學顯存,也就是共享動態連結函式庫、共享顯存等。當行程切換到核心態後,就可以很方便地存取核心空間顯存。
並不是所有的虛擬顯存就會分配化學顯存,只有這些實際使用的虛擬顯存才分配化學顯存,但是分配後的化學顯存,是透過顯存映射來管理的。顯存映射,雖然就是將虛擬顯存位址映射到化學顯存位址。為了完成顯存映射,核心為每位進程都維護了一張頁表,記錄虛擬位址與化學位址的映射關係。
#頁表實際上儲存在CPU的顯存管理單元MMU中,這樣,正常情況下,處理器就可以直接通過硬件,找出要存取的顯存。而當進程存取的虛擬位址在頁表中查不到時,系統會形成一個缺頁異常,步入內核空間分配化學顯存、更新進程頁表,最後再返回用戶空間,恢復進程的運作。
CPU上下文切換中的TLB(TranslationLookasideBuffer,轉譯後備緩衝器)是MMU中頁表的快取。因為進程的虛擬位址空間是獨立的linux是什麼系統,而TLB的存取速率又比MMU快得多,所以,透過降低進程的上下文切換,降低TLB的刷新次數,就可以提升TLB快取的使用率,從而提升CPU的顯存存取效能。
MMU規定了一個顯存映射的最小單位,也就是頁,一般是4KB大小。這樣,每一次顯存映射,都必須關聯4KB或則4KB整數倍的顯存空間。
4KB大小的頁,會造成整個頁表會顯得十分大,例如32位元系統4GB/4KB=100多萬個頁表項。為了解決頁表項過多的問題,Linux提供了兩種機制,也就是多層頁表和大頁(HugePage)。
#多層頁表就是把顯存分成區塊來管理,將原先的映射關係改成區塊索引和區塊內的偏斜。因為虛擬顯存空間一般只用了極少一部分,這麼,多級頁表就只保存那些使用中的區塊,這樣就可以大大地減低頁表的項數。 Linux以四級頁表管理顯存頁,虛擬位址分為5個部份,前4個表項用於選擇頁,而最後一個索引表示頁內偏斜。
#大頁,就是比普通頁更大的顯存區塊,常見的大小有2MB和1GB。大頁一般用在使用大量顯存的程序上,例如Oracle、DPDK等。
透過這個機制,在頁表的對應下,流程就可以透過虛擬位址來存取數學顯存了。
虛擬顯存空間分佈
最上方的是核心空間,下方的是用戶空間顯存,用戶空間又被分成多個不同的段
#使用者空間顯存,從低到高分別是5種不同的顯存段
1、唯讀段,包括程式碼和常數等
#2、資料段,包括全景變數等
3、堆,包含動態分配的顯存,從低位址開始向下下降
#4、檔案映射段,包括動態函式庫,共享顯存等,從高位址開始向上下降
5、棧,包括局部變數和函數呼叫的上下文等,堆疊的大小是固定的,通常是8M
這5個顯存段中,堆和檔案對映的顯存是動態分配的,例如使用C標準函式庫的malloc或mmap(),就可以分別在堆和文件映射段動態分配顯存。 64位元系統的顯存分佈也是類似的,只是顯存空間要大的多
顯存分配與回收
malloc()是C標準函式庫提供的顯存分配函數,對應到系統呼叫上,有兩種實作方法,即brk()和mmap()。
#對小塊顯存(大於128K),C標準函式庫使用brk()來分配,也就是透過聯通堆頂的位置來分配顯存。這種顯存釋放後並不會立即歸還系統,而是被快取上去,這樣就可以重複使用。
#對大塊顯存(小於128K),則直接使用顯存映射mmap()來分配,也就是在檔案映射段找一塊空閒顯存分配出去。
這兩種方法的異同點:
brk()方法的緩存,可降低缺頁異常的發生,提升顯存存取效率。不過,因為這種顯存沒有歸還系統,在顯存工作忙碌時,頻繁的顯存分配和釋放會導致顯存碎片。
mmap()方法分配的顯存,會在釋放時直接歸還系統,所以每次mmap就會發生缺頁異常。在顯存工作忙碌時,頻繁的顯存分配會造成大量的缺頁異常,使核心的管理負擔減少。這也是malloc只對大塊顯存使用mmap的誘因。
須要注意的是:當這兩種呼叫發生後,雖然並沒有真正分配顯存。這種顯存,都只在首次存取時才分配,也就是透過缺頁異常步入核心中,再由核心來分配顯存。
整體來說,Linux使用夥伴系統來管理記憶體分配。上面我們談到過,這種顯存在MMU中以頁為單位進行管理,夥伴系統也一樣linux漏洞掃描,以頁為單位來管理顯存,而且會透過相鄰頁的合併,降低顯存碎片化(例如brk法導致的顯存碎片)。
但在實際系統運作中,會有大量比頁還小的對象,如不到1K,倘若為它們也分配單獨的頁,會浪費大量的顯存,那該如何分配顯存呢?
在使用者空間linux 使用者分配空間,malloc透過brk()分配的顯存,在釋放時並非立刻歸還系統,而是快取上去重複借助。
在核心空間,Linux則透過slab分配器來管理小顯存。你可以把slab看成建立在夥伴系統上的一個緩存,主要作用就是分配並釋放核心中的小物件。
#顯存回收:對顯存來說,假如只分配而不釋放,還會導致顯存洩露,甚至會用盡系統顯存。所以,在應用程式用完顯存後,還必須呼叫free()或unmap(),來釋放那些不用的顯存。其實,系統也不會任由某個行程用完所有顯存。在發覺顯存緊張時,系統也會透過一系列機制回收顯存,例如下邊這三種形式:
(1)回收緩存,例如使用LRU(LeastRecentlyUsed)演算法,回收近來使用最少的顯存頁面。
(2)回收不常存取的顯存,把不常用的顯存透過交換分區(Swap)直接講到c盤中。 Swap雖然就是把一塊c盤空間當作顯存來用。它可以把進程暫時不用的資料儲存到c盤中(這個過程稱為換出),當進程存取那些顯存時,再從c盤讀取這種資料到顯存中(這個過程稱為換入)。 Swap把系統的可用顯存變大了,但一般只在顯存不足時,就會發生Swap交換,而且因為c盤讀寫的速率遠比顯存慢,Swap會造成嚴重的顯存效能問題。
(3)殺害進程,顯存緊張時系統就會通過OOM(OutofMemory,核心的一種保護機制),直接殺掉佔用大量顯存的進程。 OOM監控進程的顯存使用情況,但使用oom_score為每位行程的顯示使用情況進行評分:
一個行程消耗的顯存越大,oom_score就越大;
一個行程運行佔用的CPU越多,oom_score就越小。
這樣,進程的oom_score越大,代表消耗的顯存越多,就越容易被OOM殺害,進而可以更好地保護系統。
其實,為了實際工作的須要,管理員可以通過/proc檔案系統,自動設定進程的oom_adj,因而調整進程的oom_score。 oom_adj的範圍是[-17,15],數值越大,表示進程越容易被OOM殺害;數值越小,表示進程越不容易被OOM殺害,其中-17表示嚴禁OOM。如用下邊的命令,你就可以把sshd進程的oom_adj調小為-16,這樣,sshd進程就不容易被OOM殺害。
echo-16>/proc/$(pidofsshd)/oom_adj
##buffer和cache
free指令中buffer和cache都表示緩存,但用途不一樣
1、Buffer,是核心緩衝區用到的顯存,對應的是/proc/meminfo中的Buffer值
2、Cache,是核心頁快取和Slab用到的顯存,對應的是/proc/meminfo中的Cache和SReclaimable總和
簡單來說,Buffer是對c碟資料的緩存,而Cache是檔案資料的緩存,它們既會用在讀取懇求中,也會用在寫懇求中。
cache(快取)從CPU角度考慮,是為了提升cpu和顯存之間的資料交換速率而設計的,例如平常看到的一級快取、二級快取、三級快取。 cpu在執行程式所使用的指令和讀取資料都是針對顯存的,也就是從顯存中取得的。因為顯存讀寫速率慢,為了提升cpu與顯存之間資料交換的速率,在cpu與顯存之間降低了cache,它的速率比顯存快linux 使用者分配空間,而且造價高,又因為在cpu內不能整合太多積體電路,所以通常cache比較小,之後intel等公司為了進一步提升速率,又降低了二級cache,甚至五級cache,它是按照程式的局部性原理而設計的,就是cpu執行的指令和存取的資料常常在集中的某一塊,所以把這塊內容裝入cache後,cpu就不用在存取顯存了,這就提升了存取速率。其實若cache中沒有cpu所須要的內容,還是要存取顯存的。
從顯存讀取與c盤讀取角度考慮,cache可以理解為作業系統為了更高的讀取效率,更多的使用顯存來快取可能被再度存取的資料。
緩衝(buffers)是為了提升顯存和硬盤(或其他I/O裝置)之間的資料交換的速率而設計的。把分散的寫入操作集中進行,降低c盤碎片和硬盤的重複尋道,因而提升系統效能。 linux有一個守護程式定期清空緩衝內容(即寫入c碟),也可以透過sync指令自動清空緩衝。
簡單來說,buffer是即即將被寫入c盤的,而cache是被從c盤中讀下來的。 buffer是由各類別進程分配的,被用在如輸入佇列等方面。一個簡單的反例如某個進程要求有多個數組讀入,在所有數組被讀入完整之前,進程把原本讀入的數組置於buffer中保存。
cache常常被用在c盤的I/O懇求上,假如有多個進程都要訪問某個文件,於是該文件便被弄成cache以便捷上次被訪問,這樣可提升系統性能。
以上是深入理解 Linux 核心:虛擬位址空間與實體記憶體的映射關係的詳細內容。更多資訊請關注PHP中文網其他相關文章!