操作系统与内存管理
操作系統內存管理
文章目錄
- 操作系統內存管理
- 1. 虛擬地址空間
- 2. 內存地址空間含義及分配
- 3. 虛擬內存誕生的前世與今生?
- 3.1 內存管理的好處
- 3.2 **內存管理實現總體策略**
- 4. 不同進程如何劃分內存地址空間?
- 5 內存分配與回收
- 5.1 buffer和cache
- 5.2 malloc背后發生了什么?
- 5.3 第一次訪問剛分配的內存時,會發生什么?
- 5.4 內存緩沖區 Swap
- 5.5 內存碎片處理
1. 虛擬地址空間
虛擬內存管理
malloc
page-fault 缺頁錯誤
物理內存空間
page-table 頁表結構
mmap 內存映射
swap 內存交換區
2. 內存地址空間含義及分配
- 內存地址空間 ≠ 物理內存空間
Virtual address space ≠ Physical memory space
門牌號 ≠ 實際房屋
門牌號數量 ≥ 實際房屋數量 - 32bit vs 64bit
2^32 = 4GB
2^64 = 16EB = 16,384 PB = 1.7e17 TB
處理器支持:
ARMv7 (32bit), ARMv8 (64bit)
x86, amd64 (x86_64)
操作系統支持
編譯器支持 - 具體內存劃分如下圖所示
物理內存 Physical Memory
DRAM: 泛指內存
DIMM: 特指一條內存
虛擬內存地址 ? 物理內存地址
虛擬內存地址 Virtual Memory Address (VMA)
物理內存地址 Physical Memory Address (PMA)
保護模式下的代碼(user/kernel)都使用VMA
CPU自動完成 VMA ? PMA 轉換
分配粒度:a memory page = 4KB
Page Table: 記錄VMA --> PMA映射的數據結構
Page Table 保存在 Kernel Address Space,由操作系統維護
每個進程有一個獨立的 Page Table,用戶空間 VMA -->PMA 映射各不相同
所有進程共享同一內核空間中VMA–>PMA映射
TLB用于加速 VMA --> PMA 轉換
3. 虛擬內存誕生的前世與今生?
MMU誕生之前:
在傳統的批處理系統如DOS系統,應用程序與操作系統在內存中的布局大致如下圖:
- 應用程序直接訪問物理內存,操作系統占用一部分內存區。
- 操作系統的職責是“加載”應用程序,“運行”或“卸載”應用程序。
如果我們一直是單任務處理,則不會有任何問題,也或者應用程序所需的內存總是非常小,則這種架構是不會有任何問題的。然而隨著計算機科學技術的發展,所需解決的問題越來越復雜,單任務批處理已不能滿足需求了。而且應用程序需要的內存量也越來越大。而且伴隨著多任務同時處理的需求,這種技術架構已然不能滿足需求了,早先的多任務處理系統是怎么運作的呢?
程序員將應用程序分段加載執行,但是分段是一個苦力活。而且死板枯燥。此時聰明的計算機科學家想到了好辦法,提出來虛擬內存的思想。程序所需的內存可以遠超物理內存的大小,將當前需要執行的留在內存中,而不需要執行的部分留在磁盤中,這樣同時就可以滿足多應用程序同時駐留內存能并發執行了。
從總體上而言,需要實現哪些大的策略呢?
- 所有的應用程序能同時駐留內存,并由操作系統調度并發執行。需要提供機制管理I/O重疊,CPU資源競爭訪問。
- 虛實內存映射及交換管理,可以將真實的物理內存,有可變或固定的分區,分頁或者分段與虛擬內存建立交換映射關系,并且有效的管理這種映射,實現交換管理。
這樣,衍生而來的一些實現上的更具體的需求:
- 競爭訪問保護管理需求:需要嚴格的訪問保護,動態管理哪些內存頁/段/區,為哪些應用程序所用。這屬于資源的競爭訪問管理需求。
- 高效的翻譯轉換管理需求:需要實現快速高效的映射翻譯轉換,否則系統的運行效率將會低下。
- 高效的虛實內存交換需求:需要在實際的虛擬內存與物理內存進行內存頁/段交換過程中快速高效。
總之,在這樣的背景下,MMU應運而生,也由此可見,任何一項技術的發展壯大,都必然是需求驅動的。這是技術本身發展的客觀規律。
3.1 內存管理的好處
- 為編程提供方便統一的內存空間抽象,在應用開發而言,好似都完全擁有各自獨立的用戶內存空間的訪問權限,這樣隱藏了底層實現細節,提供了統一可移植用戶抽象。
- 以最小的開銷換取性能最大化,利用MMU管理內存肯定不如直接對內存進行訪問效率高,為什么需要用這樣的機制進行內存管理,是因為并發進程每個進程都擁有完整且相互獨立的內存空間。那么實際上內存是昂貴的,即使內存成本遠比從前便宜,但是應用進程對內存的尋求仍然無法在實際硬件中,設計足夠大的內存實現直接訪問,即使能滿足,CPU利用地址總線直接尋址空間也是有限的。
3.2 內存管理實現總體策略
從操作系統角度來看,虛擬內存的基本抽象由操作系統實現完成:
- 處理器內存空間不必與真實的所連接的物理內存空間一致。
- 當應用程序請求訪問內存時,操作系統將虛擬內存地址翻譯成物理內存地址,然后完成訪問。
從應用程序角度來看,應用程序(往往是進程)所使用的地址是虛擬內存地址,從概念上就如下示意圖所示,MMU在操作系統的控制下負責將虛擬內存實際翻譯成物理內存。
從而這樣的機制,虛擬內存使得應用程序不用將其全部內容都一次性駐留在內存中執行:
-
節省內存:很多應用程序都不必讓其全部內容一次性加載駐留在內存中,那么這樣的好處是顯而易見,即使硬件系統配置多大的內存,內存在系統中仍然是最為珍貴的資源。所以這種技術節省內存的好處是顯而易見的。
-
使得應用程序以及操作系統更具靈活性。
-
- 操作系統根據應用程序的動態運行時行為靈活的分配內存給應用程序。
- 使得應用程序可以使用比實際物理內存多或少的內存空間。
細究 MMU(Memory Management Unit)內存管理單元 請參考:關于MMU那些事兒
4. 不同進程如何劃分內存地址空間?
每個進程都獨占整個用戶態內存地址空間:
進程在用戶態時,只能訪問用戶空間內存;只有進入內核態后,才可以訪問內核空間內存。雖然每個進程的地址空間都包含了內核空間,但這些內核空間,其實關聯的都是相同的物理內存,也就是共享動態鏈接庫、共享內存等。當進程切換到內核態后,就可以很方便地訪問內核空間內存。
并不是所有的虛擬內存都會分配物理內存,只有那些實際使用的虛擬內存才分配物理內存,并且分配后的物理內存,是通過內存映射來管理的。內存映射,其實就是將虛擬內存地址映射到物理內存地址。為了完成內存映射,內核為每個進程都維護了一張頁表,記錄虛擬地址與物理地址的映射關系。
不同進程存在于不
頁表實際上存儲在 CPU 的內存管理單元 MMU 中,這樣,正常情況下,處理器就可以直接通過硬件,找出要訪問的內存。而當進程訪問的虛擬地址在頁表中查不到時,系統會產生一個缺頁異常,進入內核空間分配物理內存、更新進程頁表,最后再返回用戶空間,恢復進程的運行。
**CPU 上下文切換中的TLB(**Translation Lookaside Buffer,轉譯后備緩沖器)是 MMU 中頁表的高速緩存。由于進程的虛擬地址空間是獨立的,而 TLB 的訪問速度又比 MMU 快得多,所以,通過減少進程的上下文切換,減少 TLB 的刷新次數,就可以提高 TLB 緩存的使用率,進而提高 CPU 的內存訪問性能。
MMU(Memory Management Unit)內存管理單元: 規定了一個內存映射的最小單位,也就是頁,通常是 4 KB 大小。這樣,每一次內存映射,都需要關聯 4 KB 或者 4KB 整數倍的內存空間。
4 KB大小的頁,會導致整個頁表會變得非常大,比如32位系統4GB/4KB=100多萬個頁表項。為了解決頁表項過多的問題,Linux 提供了兩種機制,也就是多級頁表和大頁(HugePage)。
多級頁表就是把內存分成區塊來管理,將原來的映射關系改成區塊索引和區塊內的偏移。由于虛擬內存空間通常只用了很少一部分,那么,多級頁表就只保存這些使用中的區塊,這樣就可以大大地減少頁表的項數。Linux 用四級頁表來管理內存頁,虛擬地址被分為 5 個部分,前 4 個表項用于選擇頁,而最后一個索引表示頁內偏移。
大頁,就是比普通頁更大的內存塊,常見的大小有 2MB 和 1GB。大頁通常用在使用大量內存的進程上,比如 Oracle、DPDK 等。
通過這些機制,在頁表的映射下,進程就可以通過虛擬地址來訪問物理內存了。
5 內存分配與回收
malloc() 是 C 標準庫提供的內存分配函數,對應到系統調用上,有兩種實現方式,即 brk() 和 mmap()。
對小塊內存(小于 128K),C 標準庫使用 brk() 來分配,也就是通過移動堆頂的位置來分配內存。這些內存釋放后并不會立刻歸還系統,而是被緩存起來,這樣就可以重復使用。
對大塊內存(大于 128K),則直接使用內存映射 mmap() 來分配,也就是在文件映射段找一塊空閑內存分配出去。
這兩種方式的優缺點:
brk()方式的緩存,可以減少缺頁異常的發生,提高內存訪問效率。不過,由于這些內存沒有歸還系統,在內存工作繁忙時,頻繁的內存分配和釋放會造成內存碎片。
mmap()方式分配的內存,會在釋放時直接歸還系統,所以每次 mmap 都會發生缺頁異常。在內存工作繁忙時,頻繁的內存分配會導致大量的缺頁異常,使內核的管理負擔增大。這也是 malloc 只對大塊內存使用 mmap 的原因。
需要注意的是:當這兩種調用發生后,其實并沒有真正分配內存。這些內存,都只在首次訪問時才分配,也就是通過缺頁異常進入內核中,再由內核來分配內存。
整體來說,Linux 使用伙伴系統來管理內存分配。前面我們提到過,這些內存在 MMU 中以頁為單位進行管理,伙伴系統也一樣,以頁為單位來管理內存,并且會通過相鄰頁的合并,減少內存碎片化(比如 brk 方式造成的內存碎片)。
但在實際系統運行中,會有大量比頁還小的對象,如不到1K,如果為它們也分配單獨的頁,會浪費大量的內存,那該怎么分配內存呢?
在用戶空間,malloc 通過 brk() 分配的內存,在釋放時并不立即歸還系統,而是緩存起來重復利用。
在內核空間,Linux 則通過 slab 分配器來管理小內存。你可以把 slab 看成構建在伙伴系統上的一個緩存,主要作用就是分配并釋放內核中的小對象。
內存回收:對內存來說,如果只分配而不釋放,就會造成內存泄漏,甚至會耗盡系統內存。所以,在應用程序用完內存后,還需要調用 free() 或 unmap() ,來釋放這些不用的內存。當然,系統也不會任由某個進程用完所有內存。在發現內存緊張時,系統就會通過一系列機制來回收內存,比如下面這三種方式:
(1)回收緩存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的內存頁面。
(2)回收不常訪問的內存,把不常用的內存通過交換分區(Swap)直接寫到磁盤中。Swap 其實就是把一塊磁盤空間當成內存來用。它可以把進程暫時不用的數據存儲到磁盤中(這個過程稱為換出),當進程訪問這些內存時,再從磁盤讀取這些數據到內存中(這個過程稱為換入)。Swap 把系統的可用內存變大了,但通常只在內存不足時,才會發生 Swap 交換,并且由于磁盤讀寫的速度遠比內存慢,Swap 會導致嚴重的內存性能問題。
(3)殺死進程,內存緊張時系統還會通過 OOM(Out of Memory,內核的一種保護機制),直接殺掉占用大量內存的進程.。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 殺死。
5.1 buffer和cache
free命令中buffer和cache都表示緩存,但用途不一樣
1、 Buffer,是內核緩沖區用到的內存,對應的是/proc/meminfo中的Buffer值
2、 Cache,是內核頁緩存和Slab用到的內存,對應的是/proc/meminfo中的Cache和SReclaimable之和
簡單來說,Buffer 是對磁盤數據的緩存,而 Cache 是文件數據的緩存,它們既會用在讀請求中,也會用在寫請求中。
cache(緩存)從CPU角度考慮,是為了提高cpu和內存之間的數據交換速度而設計的,例如平常見到的一級緩存、二級緩存、三級緩存。 cpu在執行程序所用的指令和讀數據都是針對內存的,也就是從內存中取得的。由于內存讀寫速度慢,為了提高cpu和內存之間數據交換的速度,在cpu和內存之間增加了cache,它的速度比內存快,但是造價高,又由于在cpu內不能集成太多集成電路,所以一般cache比較小,以后intel等公司為了進一步提高速度,又增加了二級cache,甚至三級cache,它是根據程序的局部性原理而設計的,就是cpu執行的指令和****訪問的數據往往在集中的某一塊,所以把這塊內容放入cache后,cpu就不用在訪問內存了,這就提高了訪問速度。當然若cache中沒有cpu所需要的內容,還是要訪問內存的。
從內存讀取與磁盤讀取角度考慮,cache可以理解為操作系統為了更高的讀取效率,更多的使用內存來緩存可能被再次訪問的數據。
**緩沖(buffers)**是為了提高內存和硬盤(或其他I/O設備)之間的數據交換的速度而設計的。把分散的寫操作集中進行,減少磁盤碎片和硬盤的反復尋道,從而提高系統性能。linux有一個守護進程定期清空緩沖內容(即寫入磁盤),也可以通過sync命令手動清空緩沖。
簡單來說,buffer是即將要被寫入磁盤的,而cache是被從磁盤中讀出來的。 buffer是由各種進程分配的,被用在如輸入隊列等方面。一個簡單的例子如某個進程要求有多個字段讀入,在所有字段被讀入完整之前,進程把先前讀入的字段放在buffer中保存。
cache經常被用在磁盤的I/O請求上,如果有多個進程都要訪問某個文件,于是該文件便被做成cache以方便下次被訪問,這樣可提高系統性能。
5.2 malloc背后發生了什么?
為什么可以運行?
因為malloc分配的是虛擬內存地址空間
分配小于 MMAP_THRESHOLD (128KB),從 Heap 分配
分配大于 MMAP_THRESHOLD (128KB),從 Memory Mapping Segment 分配
物理內存在第一次訪問的時候才分配(demand paging)
每次分配一個物理內存頁(4KB)
內存結構如下圖所示:
5.3 第一次訪問剛分配的內存時,會發生什么?
Page Fault 缺頁錯誤
- 第一次訪問一個新malloc出來的地址時,該Virtual Memory Address (VMA) 還未有與其對應的物理內存頁
- CPU中的Memory Management Unit (MMU)單元會觸發一個缺頁錯誤的硬件中斷
- 操作系統內核收到該中斷后會去檢查該VA是否處在一個合法的VMA區間
- 如果VMA不合法,則拋出段錯誤 (Segmentation fault) ,然后殺掉該進程,如果合法,則在空余的物理內存池中尋找一個內存頁,通過修改頁表 (Page table) 來完成 VMA到物理內存的對應關系
如何判斷 VMA 是否合法?
- malloc() 會調用 brk() 或 mmap() 來向kernel注冊合法的虛擬內存地址空間 Virtual Memory Area (VM Area),詳細鍵 5.0開頭。
這些VM Area由一棵紅黑樹來維護
5.4 內存緩沖區 Swap
-
當物理內存耗盡時,操作系統會將一部分訪問不頻繁的數據從內存搬移到硬盤中
-
-硬盤上用于保存這些數據的區域稱為“內存緩沖區”(Swap)
-
這理論上可以讓應用程序可以使用的內存和硬盤一樣大
-
但是使用硬盤緩沖區會導致訪問速度減慢100倍(20GB/s vs 200MB/s)
top 實例分析
VIRT (Virtual): 所聲明的虛擬內存空間(包括分配未使用)
RES (Resident): 所使用的物理內存空間(不包括swap)
SHR (Shared): 和其他進程共享的內存空間
判斷一個進程占用內存空間大小,關鍵看RES
5.5 內存碎片處理
-
內部碎片
-
已分配給某進程,但不能被該進程利用的物理內存空間
-
內部:物理內存頁的內部
-
內存分配盡可能采用同樣大小(或整數倍大小)
-
-
外部碎片
- 未分配給任一進程,但由于太小了無法分配給申請內存空間的進程的物理內存空間
- 通常發生在需要分配一段連續的物理內存空間時(例如 kmalloc,DMA)
- 外部碎片導致內存不足的情況相對罕見
內存碎片整理工具有用嗎?
簡單回答:沒用
原因:內存碎片整理工具無法知道每一塊內存被誰引用,直接移動會導致程序指針失效,程序崩潰
市面上的內存碎片整理工具干了啥?
-
step 1:申請一大塊虛擬內存空間
-
step 2:訪問這塊虛擬內存空間,觸發page fault
-
step 3:操作系統為該虛擬內存空間分配物理內存,進而將其他進程內存中的數據寫到swap
-
step 4:碎片整理工具釋放剛申請的內存
-
step 5:新啟動的程序即時擁有可使用的內存
作用:
- 減少了新啟動程序觸發swap的耗時
- 一定程度上緩解了內存泄漏
副作用:讓已經在運行的程序頻繁觸發swap
- Managed environments 自帶內存碎片整理功能
- JVM / C# / Go / Rust
總結
- 上一篇: 【Tools】Markdown数学符号公
- 下一篇: 求想对你说歌词!