最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

玩轉(zhuǎn)Linux虛擬內(nèi)存管理,一文給你搞定!

2022-06-09 20:10 作者:補給站Linux內(nèi)核  | 我要投稿
  • Linux的虛擬內(nèi)存管理有幾個關鍵概念:

每個進程有獨立的虛擬地址空間,進程訪問的虛擬地址空間并不是真正的物理地址 虛擬地址可通過每個進程上頁表與物理地址進行映射,獲得真正的物理地址 如果虛擬地址所對應的物理地址不在物理內(nèi)存中,則產(chǎn)生缺頁中斷,真正分配物理地址,同時更新進程的頁表;如果此時物理內(nèi)存已經(jīng)耗盡,則根據(jù)內(nèi)存替換算法淘汰部分頁面至物理磁盤中。

一. Linux虛擬地址空間如何分布?32位和64位有何不同?

  • Linux使用虛擬地址空間,大大增加了進程的尋址空間,由低地址到高地址分別是:

  1. 只讀段:該部分空間只能讀,不能寫,包括代碼段,rodata段(C常量字符和#define定義的常量)

  2. 數(shù)據(jù)段:保存全局變量、靜態(tài)空間變量

  3. 堆:就是平時所說的動態(tài)內(nèi)存,malloc/new大部分都源于此。其中堆頂?shù)奈恢每梢酝ㄟ^brk和sbrk進行動態(tài)調(diào)整

  4. 文件映射區(qū)域:如動態(tài)庫,共享內(nèi)存等映射物理空間的內(nèi)存,一般是mmap函數(shù)所分配的虛擬地址空間

  5. 棧:用于維護函數(shù)調(diào)用的上下文空間,一般為8M,可以通過ulimit -s查看

  6. 內(nèi)核虛擬空間:用戶代碼不可見的區(qū)域,由內(nèi)核管理

  • 下圖是32位OS典型的虛擬地址空間分布:

  • 32位系統(tǒng)有4G的地址空間,其中0X08048000~0Xbfffffff是用戶空間,0Xc0000000~0Xffffffff是內(nèi)核空間,包含內(nèi)核代碼和數(shù)據(jù)、與進程相關的數(shù)據(jù)結(jié)構(gòu)(如頁表,內(nèi)核棧)等。另外%esp執(zhí)行棧頂,往低地址方向變化;brk/sbrk函數(shù)控制堆頂往高地址方向變化。

  • 可以通過以下代碼驗證進程的地址空間分布,其中sbrk(0)函數(shù)用于返回棧頂指針。

32位系統(tǒng)的結(jié)果如下,與上圖的劃分保持一致,并且棧頂指針在malloc和free一個127K的存儲空間時都發(fā)生了變化(增大和縮小)

  • 但是64位系統(tǒng)的結(jié)果怎樣呢?64位系統(tǒng)是否擁有2^64的地址空間呢?

  • 64位的結(jié)果如下:

  • 從結(jié)果可知,與上圖的分布并不一致。而事實上,64位系統(tǒng)的虛擬地址空間劃分發(fā)生了變化:

地址空間大小不是2^32,也不是2^64,而一般是2^48。因為并不需要2^64這么大的尋址空間,過大空間只會導致資源的浪費。64位Linux一般使用48位來表示虛擬地址空間,40位表示物理地址,可通過/proc/cpuinfo來查看:address sizes : 40 bits physical, 48 bits virtual 其中,0x0000000000000000~0x00007fffffffffff表示用戶空間,0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF表示內(nèi)核空間,共提供 256TB(2^48) 的尋址空間。這兩個區(qū)間的特點是,第47位與48~63位相同,若這些位為0表示用戶空間,否則表示內(nèi)核空間 用戶空間由低地址到高地址仍然是只讀段,數(shù)據(jù)段,堆,文件映射區(qū)域和棧

【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??



  • malloc是glibc中的內(nèi)存分配函數(shù),也是最常用的動態(tài)內(nèi)存分配函數(shù),其內(nèi)存必須通過free進行釋放,否則導致內(nèi)存泄漏。

  • 關于malloc獲得虛擬空間的表現(xiàn),與glibc的版本有關,但大體邏輯上:

1.若分配內(nèi)存小于128K,調(diào)用sbrk(),將堆頂指針向高地址移動,獲得新的虛擬空間; 2.若分配內(nèi)存大于128K,調(diào)用mmap(),在文件映射區(qū)域中分配匿名虛擬空間; 3.這里討論的是簡單情況,如果涉及并發(fā)則可能會復雜一些,不過先不討論。 其中sbrk()就是修改棧頂指針位置,而mmap可用于生成文件的映射以及修改匿名頁面的內(nèi)存,這里指的是匿名頁面。

  • 而這個128K,是glibc的默認配置,可通過函數(shù)mallopt來設置,可通過以下例子來說明:

  • 這個例子很簡單,通過 malloc 申請多個不同大小的動態(tài)內(nèi)存,同時通過接口 print_info 打印變量大小和地址等相關信息,其中 sbrk(0) 可返回堆頂指針位置。另外,粗體部分是將 MMAP 分配的臨界點由 128k 轉(zhuǎn)為 64k ,再打印變量地址的不同。

  • 下面是 Linux 64 位機器的執(zhí)行結(jié)果(后文所有例子都是通過 64 位機器上的測試結(jié)果):

三.malloc分配多大的內(nèi)存,就占用多大的物理內(nèi)存空間嗎?

  • malloc分配的內(nèi)存是虛擬地址空間,而虛擬地址空間和物理地址空間使用進程頁表進行映射,那么分配了空間就是占用物理內(nèi)存空間了嗎?

  • 首先,進程使用了多少內(nèi)存可通過ps -aux命令查看,其中關鍵的兩個信息(第五,六列)為:

  1. VSZ,virtual memory size,表示進程總共使用的虛擬地址空間大小,包括進程地址空間的代碼段,數(shù)據(jù)段,堆,文件映射區(qū)域,棧,內(nèi)核空間等所有虛擬地址使用的總和,單位為K。

  2. RSS,resident set size,表示進程實際使用的物理空間大小,RSS總小于VSZ。

  • 可通過一個例子說明這個問題 :

  • 該代碼擴展了上一個例子print_info能力,處理打印變量信息,同時通過 ps aux 命令獲得當前進程的 VSZ 和 RSS 值。并且程序 malloc 一塊內(nèi)存后,會 memset 內(nèi)存的若干 k 內(nèi)容。

  • 執(zhí)行結(jié)果為:

由以上結(jié)果可知:

  1. VSZ并不是每次malloc后都增長,是與上一節(jié)說的堆頂沒發(fā)生變化有關,因為可重用堆頂內(nèi)剩余的空間,這樣malloc是很輕量和快速的;

  2. 如果VSZ發(fā)生變化,基本與分配內(nèi)存量相當,因為VSZ是計算虛擬地址空間總大??;

  3. RSS的增量很少,是因為malloc分配的內(nèi)存并不就馬上分配實際的存儲空間,只有第一次使用,如第一個memset后才分配;

  4. 由于每個物理頁面大小是4K,不管memset其中的1K,還是5K,7K,實際占用物理內(nèi)存總是4K的倍數(shù)。所以RSS的增量總是4K的倍數(shù);

  5. 因此,不是malloc后馬上占用實際內(nèi)存,而是第一次使用時發(fā)現(xiàn)虛存對應的物理頁面未分配,產(chǎn)生缺頁中斷,才真正分配物理頁面,同時更新進程頁表的映射關系。這也是Linux虛擬內(nèi)存管理的核心概念之一。


四.如何查看進程虛擬地址空間的使用情況

  • 進程地址空間被分成了代碼段,數(shù)據(jù)段,堆,文件映射區(qū)域,棧等區(qū)域,怎么查詢這些虛擬地址空間的使用情況呢?

  • Linux提供了pmap命令來查看這些信息,通常使用pmap -d pid(高版本可提供pmap -x pid)查詢,如下所示:

  • 從這個結(jié)果可以看到進程虛擬地址空間的使用情況,包括起始地址、大小、實際使用內(nèi)存、臟頁大小、權限、偏移、設備和映射文件等,pmap命令就是基于下面兩文件進行解析的:

/proc/pid/maps /proc/pid/smaps

  • 并且對于上述每個內(nèi)存塊區(qū)間,內(nèi)核會使用一個vm_area_struct結(jié)構(gòu)來維護,同時提供頁面建立與物理內(nèi)存的映射關系,如下圖所示:


五.free的內(nèi)存真的釋放了嗎(還給OS)?

  • 前面的示例都有一個嚴重的問題,就是分配的內(nèi)存都沒有釋放,即導致內(nèi)存泄漏,原則上所有malloc/new分配的內(nèi)存,都需要free/delete來釋放,但是free了的內(nèi)存真的釋放了嗎?

  • 要說清楚這個問題,可通過下面例子來說明:


  1. 初始狀態(tài):如圖 (1) 所示,系統(tǒng)已分配 ABCD 四塊內(nèi)存,其中 ABD 在堆內(nèi)分配, C 使用 mmap 分配。為簡單起見,圖中忽略了如共享庫等文件映射區(qū)域的地址空間。

  2. E=malloc(100K):分配 100k 內(nèi)存,小于 128k ,從堆內(nèi)分配,堆內(nèi)剩余空間不足,擴展堆頂 (brk) 指針。

  3. free(A):釋放 A 的內(nèi)存,在 glibc 中,僅僅是標記為可用,形成一個內(nèi)存空洞 ( 碎片 ) ,并沒有真正釋放。如果此時需要分配 40k 以內(nèi)的空間,可重用此空間,剩余空間形成新的小碎片。


4. free(C):C 空間大于 128K ,使用 mmap 分配,如果釋放 C ,會調(diào)用 munmap 系統(tǒng)調(diào)用來釋放,并會真正 ? ? ? 釋放該空間,還給 OS ,如圖 (4) 所示。


5. free(D):與釋放 A 類似,釋放 D 同樣會導致一個空洞,獲得空閑空間,但并不會還給 OS 。此時,空閑總空間為 100K ,但由于虛擬地址不連續(xù),無法合并,空閑空間無法滿足大于 60k 的分配請求。


6. free(E):釋放 E ,由于與 D 連續(xù),兩者將進行合并,得到 160k 連續(xù)空閑空間。同時 E 是最靠近堆頂?shù)目臻g,glibc 的 free 實現(xiàn)中,只要堆頂附近釋放總空間(包括合并的空間)超過 128k ,即會調(diào)用 sbrk(-SIZE) 來回溯堆頂指針,將原堆頂空間還給 OS ,如圖 (6) 所示。而堆內(nèi)的空閑空間還是不會歸還 OS 的。


由此可見:

  1. malloc使用mmap分配的內(nèi)存(大于128K),free會調(diào)用unmap系統(tǒng)調(diào)用馬上還給OS,實現(xiàn)真正釋放。

  2. 堆內(nèi)的內(nèi)存,只有釋放堆頂?shù)目臻g,同時堆頂總連續(xù)空間大于128K才使用sbrk(-SIZE)回收內(nèi)存,真正歸還OS。

  3. 堆內(nèi)的空閑空間,是不會歸還給OS的。

六.程序代碼中malloc的內(nèi)存都有相應的free,就不會出現(xiàn)內(nèi)存泄漏了嗎?

  • 狹義上的內(nèi)存泄漏是指malloc的內(nèi)存,沒有free,導致內(nèi)存泄漏,直到程序結(jié)束。而廣義上的內(nèi)存泄漏是進程使用內(nèi)存量不斷增加,或大大超出了系統(tǒng)原設計的上限。

  • 上一節(jié)說到,free 了的內(nèi)存并不會馬上歸還 OS ,并且堆內(nèi)的空洞(碎片)更是很難真正釋放,除非空洞成為了新的堆頂 。所以,如上一例子情況 (5) ,釋放了 40k 和 60k 兩片內(nèi)存,但如果此時需要申請大于 60k (如 70k ),沒有可用碎片,必須向 OS 申請,實際使用內(nèi)存仍然增大。

  • 因此,隨著系統(tǒng)頻繁的malloc和free,尤其是對于小塊內(nèi)存,堆內(nèi)將產(chǎn)生越來越多不可用的碎片,導致“內(nèi)存泄漏”。而這種“泄露”現(xiàn)象使用 valgrind 是無法檢測出來的。

  • 下圖是 MySQL 存在大量分區(qū)表時的內(nèi)存使用情況 (RSS 和 VSZ) ,疑似“內(nèi)存泄露”。


  • 因此,當我們寫程序時,不能完全依賴于glibc的malloc和free的實現(xiàn)。更好方式是建立進程的內(nèi)存池,即一次分配(malloc)大塊內(nèi)存,小內(nèi)存從內(nèi)存池中獲得,當進程結(jié)束或該塊內(nèi)存不可用時,一次釋放(free),可大大減少碎片的產(chǎn)生。


七.既然堆內(nèi)內(nèi)存不能直接釋放,為什么不全部使用mmap來分配

  • 由于堆內(nèi)碎片不能直接釋放,而問題5中說到的mmap分配的內(nèi)存可以通過unmap進行free,實現(xiàn)真正釋放。既然堆內(nèi)碎片不能直接釋放,導致疑似“內(nèi)存泄漏”問題,為什么malloc不全部使用mmap來實現(xiàn)呢?而僅僅對于大于128K的大塊內(nèi)存才使用mmap?

  • 其實,進程向OS申請和釋放緊致空間的接口 sbrk/mmap/unmap都是系統(tǒng)調(diào)用,頻繁的系統(tǒng)調(diào)用都比較消耗系統(tǒng)資源。并且,mmap申請的內(nèi)存被unmap后,重新申請會產(chǎn)生更多的缺頁中斷。例如mmap分配 1M 空間,第一次調(diào)用產(chǎn)生了大量缺頁中斷(1M/4K次),當unmap后再次分配 1M 空間,會再次產(chǎn)生大量缺頁中斷。缺頁中斷屬于內(nèi)核行為,會導致內(nèi)核態(tài) CPU 消耗較大。另外,使用 mmap 分配小內(nèi)存,會導致地址空間的分片更多,內(nèi)核的管理負擔更大。

  • 而堆是一個連續(xù)空間,并且堆內(nèi)碎片由于沒有歸還給OS,如果可重用碎片,在此訪問該內(nèi)存很可能不需要任何系統(tǒng)調(diào)用和缺頁中斷,這將大大降低CPU的消耗。

  • 因此,glibc的malloc實現(xiàn)中,充分考慮了sbrk和mmap行為上的差異和優(yōu)缺點,默認分配大塊內(nèi)存(128K)才使用mmap獲得地址空間,也可通過 mallopt(M_MMAP_THRESHOLD,SIZE)來修改這個臨界值。


八.如何查看進程的缺頁中斷信息?

  • 可通過以下命令查看缺頁中斷信息:

  • 其中, majflt 代表 major fault ,指大錯誤, minflt 代表 minor fault ,指小錯誤。這兩個數(shù)值表示一個進程自啟動以來所發(fā)生的缺頁中斷的次數(shù)。其中 majflt 與 minflt 的不同是, majflt 表示需要讀寫磁盤,可能是內(nèi)存對應頁面在磁盤中需要 load 到物理內(nèi)存中,也可能是此時物理內(nèi)存不足,需要淘汰部分物理頁面至磁盤中。

  • 例如,下面是 mysqld 的一個例子。

  • 如果進程的內(nèi)核態(tài) CPU 使用過多,其中一個原因就可能是單位時間的缺頁中斷次數(shù)多個,可通過以上命令來查看。

如果 MAJFLT 過大,很可能是內(nèi)存不足。 如果 MINFLT 過大,很可能是頻繁分配 / 釋放大塊內(nèi)存 (128k) , malloc 使用 mmap 來分配。對于這種情況,可通過 mallopt(M_MMAP_THRESHOLD, SIZE)增大臨界值,或程序?qū)崿F(xiàn)內(nèi)存池。

九.如何查看堆內(nèi)內(nèi)存的碎片情況?

  • glibc 提供了以下結(jié)構(gòu)和接口來查看堆內(nèi)內(nèi)存和 mmap 的使用情況:

可通過以下例子來驗證 mallinfo 和 malloc_stats 輸出結(jié)果:

  • 該例子第一個循環(huán)為指針數(shù)組每個成員分配索引位置 (KB) 大小的內(nèi)存塊,并通過 128 為分界分別對 heap 和 mmap 內(nèi)存分配情況進行計數(shù);第二個循環(huán)是 free 索引下標為奇數(shù)的項,同時更新計數(shù)情況。通過程序的計數(shù)與 mallinfo/malloc_stats 接口得到結(jié)果進行對比,并通過 print_info 打印到終端。

  • 下面是一個執(zhí)行結(jié)果:

  • 由上可知,程序統(tǒng)計和 mallinfo 得到的信息基本吻合,其中 heap_free_total 表示堆內(nèi)已釋放的內(nèi)存碎片總和。

  • 如果想知道堆內(nèi)片究竟有多碎 ,可通過 mallinfo 結(jié)構(gòu)中的 fsmblks 、 smblks 、 ordblks 值得到,這些值表示不同大小區(qū)間的碎片總個數(shù),這些區(qū)間分別是 0~80 字節(jié), 80~512 字節(jié), 512~128k 。如果 fsmblks 、 smblks 的值過大,那碎片問題可能比較嚴重了。

  • 不過, mallinfo 結(jié)構(gòu)有一個很致命的問題,就是其成員定義全部都是 int ,在 64 位環(huán)境中,其結(jié)構(gòu)中的 uordblks/fordblks/arena/usmblks 很容易就會導致溢出,應該是歷史遺留問題,使用時要注意!

十. 除了 glibc 的 malloc/free ,還有其他第三方實現(xiàn)嗎?

  • 其實,很多人開始詬病 glibc 內(nèi)存管理的實現(xiàn),就是在高并發(fā)性能低下和內(nèi)存碎片化問題都比較嚴重,因此,陸續(xù)出現(xiàn)一些第三方工具來替換 glibc 的實現(xiàn),最著名的當屬 google 的 tcmalloc 和 facebook 的 jemalloc 。


玩轉(zhuǎn)Linux虛擬內(nèi)存管理,一文給你搞定!的評論 (共 條)

分享到微博請遵守國家法律
鹤峰县| 鄂州市| 谢通门县| 库伦旗| 五台县| 顺平县| 彩票| 绥江县| 封开县| 汉源县| 浠水县| 泗阳县| 东山县| 景宁| 马关县| 安吉县| 双城市| 上林县| 青阳县| 玉树县| 日照市| 苏尼特左旗| 华安县| 禹城市| 任丘市| 洪湖市| 高雄县| 武宣县| 澜沧| 襄城县| 武定县| 屯留县| 乳山市| 河南省| 仁怀市| 威宁| 浙江省| 永清县| 邵阳市| 改则县| 甘孜|