linux memory overcommit機制--------筆記

前言:

overcommit 機制介紹:

一個問題引發的對overcommit的思考:

問題背景:

問題:

問題分析:

問題的原因:

解決方案:


前言:

linux的虛擬內存支持overcommit(過度使用)

本文就fork子進程時"fork: Cannot allocate memory" 錯誤展開分析,解釋其原因,並給出解決方法.

overcommit 機制介紹:

linux memory overcommit策略一共分爲三種(可以通過/proc/sys/vm/overcommit_memory修改策略):

參考linux kernel document:overcommit-accounting

參考內核mm/util.c __vm_enough_memory()

(1)啓發式策略(overcommit_memory==0):

如果有足夠可用的物理內存供使用,則內存分配成功,否則失敗.

可用物理內存可以通過對/proc/meminfo的統計信息做一下計算得到:"free + buffer + cached - shm + swap + slab_recalimable - zone_total_reserved(各zone預留內存總和,一般是固定的)"

__vm_enough_memory通過比較請求的內存數量與當前可用的物理內存來決定是否允許請求。

(2)允許過度使用策略(overcommit_memory==1):

無論分配多少內存都會成功,這樣的好處是可以使用所有的物理內存,但是有可能引發oom。(爲了驗證你可以動手試一下,譬如當前有50M可用的物理內存,malloc即使100M也是成功返回的,但是寫這段內存的時候,超過50M就是oom)

(3)不允許過度使用策略(overcommit_memory==2):

系統的所有虛擬內存加起來不得超過 “總物理內存 + CommitLimt”(CommitLimt=總物理內存*overcommit_ratio%)。overcommit_ratio可以在/proc/sys/vm裏設定,默認是50,也就是CommitLimt默認是0.5倍的總物理內存。當前所有進程一共使用的虛擬內存Committed_AS和CommitLimt可通過/proc/meminfo查看。(爲了驗證你也可以試一下,看一下當你的Committed_AS超過“總物理內存 + CommitLimt”時,這時候你在終端上敲個命令就會提示你fork:alloc memory fail)

 

一個問題引發的對overcommit的思考:

問題背景:

我們在寫程序的時候可能會涉及到多進程,譬如主進程fork一個子進程,或者更一般的我們會用到system和popen這些系統調用來執行我們的command。實際上system和popen也是通過fork子進程來完成自己的工作的。

system會先fork一個子進程,然後watpid(這裏fork動作和waitpid動作都是在主進程中執行的)。子進程通過exec替換其上下文空間爲shell進程,shell進程再fork一個子進程,然後waitpid。這個子進程同樣的通過exec替換自己的上下文空間爲要執行的command,執行command。popen的動作跟system一致,不同的是主子進程間有管道來進行通訊,譬如我可以獲得command執行的輸出結果。

問題:

由於使用system我們可能會遇到這樣一個令人匪夷所思的問題:

fork: alloc memory fail

問題分析:

爲了搞清楚這一個問題的原因,我舉一個例子,通過這個例子來解釋清楚爲什麼會出現這一問題。

例子:

系統當前有50M可用的物理內存(可用的物理內存同上一節的解釋),啓動一個進程:

  1. 分配1M大小的全局數組(這1M出現在了heap段)
  2. 申請2段15M大小的內存(malloc大於128k時採用mmap分配,出現在mmap段)
  3. 向這30M內存執行寫操作(不執行這一步可fork成功)
  4. 執行fork
  5. 子進程sleep 10s,退出
  6. 父進程收到子進程退出消息後, sleep 10s
  7. 父進程釋放內存. 退出

對於這個進程的內存空間的分佈可以查看進程的maps(/proc/pid/maps)

我分配了15*2+1=31M的內存,但是應該還有19M內存可用,爲什麼fork會提示alloc memory fail呢????

                                                                         meminfo

 

                                                                            maps

 

                                                                            overcommit

爲了弄明白原由,我們需要先知道在fork的時候子進程會copy父進程的一些虛擬內存區域,通過我們的實驗分析可以知道它需要copy數據段,head段,部分mmap段(譬如這裏我們malloc的30M內存)。在copy每一個內存區域的時候都會按照當前的overcommit策略來斷定是否允許執行copy。overcommit的實現關鍵邏輯在"__vm_enough_memory(...)"函數。我們上面已經介紹了overcommit的三種策略,默認情況下采用啓發式overcommit策略,這裏不再闡述了。

在執行fork時,從系統調用開始_do_fork--->copy_process--->copy_mm--->dup_mm--->dup_mmap--->對父進程mm_struct中的每個vm_area執行security_vm_enough_memory_mm--->__vm_enough_memory,根據overcommit策略判斷是否允許申請,如果允許事情則接着執行copy_page_range對頁目錄和頁表進行拷貝

(同樣的malloc一段內存的時候也會執行overcommit檢查)

對於上面的例子:

(1) 通過在執行fork前,cat得到meminfo信息,計算得到當前可用物理內存 7500+2648+8816-144+1624-1068=19376Kbytes
這一結果與overcommit圖中free的4861pages(19444Kbytes)基本接近(運行過程中內存存在變動,還有爲數不多的其他幾個進程在運行)。

(2) maps圖中,第二行是data段(未初始化時大小爲1個page),第三行是heap段(1M大小的全局數組分佈在這個段內),第四行是一段mmap段(malloc超過128Kbytes時通過mmap分配,大小是30M+2pages,分量次申請的數據存在同一mmap段;當然還有其他mmap段,譬如文件的映射段,貢獻庫的映射段,這些都不需要拷貝),這三段是在fork時需要拷貝的vm_area,fork時拷貝這三段也是處於cow的考慮吧(寫時拷貝)

(3)overcommit圖中(我在驅動中加的log),第一行是data段(可以通過size和start_address與maps圖所展示的信息斷定,下同),第二行是1M的heap段,第三行是32M+2pages的一段mmap段

問題的原因:

通過overcommit圖可以得知在fork時依據overcommit的決段邏輯(默認的啓發式),30M+2pages的mmap段的大小(7682pages)超過了當前可用的物理內存大小19444Kbytes(4861pages).導致"alloc memory fail"

這樣對"malloc的30M內存不執行寫操作,fork成功"的原因就顯而易見了,因爲它沒有去分配頁框,物理內存不會因此減小30M+2pages,所以overcommit檢查的時候能夠通過,fork得以成功執行.(需要拷貝的最大的vm_area 30M+2pages小於當前可用的物理內存19444Kbytes+30M+ 2pages)。

解決方案:

可以使用vfork替代fork,vfork共享父進程的內存空間,不會進行copy,對資源的消耗更少,所以不會出現上述問題。而且vfork的kernel機制保證了vfork後子進程先運行(fork之後父子進程誰先運行時不確定的,當然你可以通過/proc/sys/kernel/sched_child_runs_first來使子進程先運行)。

要注意在vfork後子進程共享父進程的內存空間,不要因爲子進程的一些動作而影響父進程的運行。其一般的用途是vfork之後即調用exec來替換子進程的上下文,從而執行我們的另外一個程序,避免了fork對父進程的沒必要的拷貝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章