每個程式設計師都該了解一點 Linux 內存管理知識

2021-02-13 MacTalk

題圖:by globe_people from Instagram

上周寫了篇文章,介紹了一個 Java 內存溢出的案例,創下了今年閱讀量最低記錄(不算圖片),為了挽回顏面,我決定再寫一篇,還是從案例入手,介紹 Linux 內存管理機制和一個內存溢出殺手的存在感。

這是以前工作中的一個真實案例:運維人員突然發現有個在線服務失效了。一般遇到這種情況,運維和相關的研發人員都會查看在線的日誌服務,必要的話還會登錄到生產環境的伺服器上去一看究竟。這次略有不同。

通過 ping 可以知道這臺伺服器還「活著」,但是無法通過 ssh 登錄,網絡鏈路確認了沒有問題,在線系統日誌也查不出什麼端倪。排除了所有的可能性之後,我們只能認為,這臺伺服器不僅幹掉了我們部署的服務,而且幹掉了 sshd 進程。

由於這個服務沒有那麼敏感,所以運維人員選擇了直接重啟伺服器(一臺虛擬機),系統啟動後一切都恢復了正常,殺手隱藏在伺服器的 shadow 中,就像一切都沒有發生過。登上伺服器查看系統日誌 /var/log/messages,可以看到類似這樣的信息:

Out of memory: Kill process 862 (xxproid) score 11 or sacrifice child

這條消息表明,某個 pid 是 862 的進程被 OOM Killer 殺掉了,再往下找,還可以找到 sshd 的進程名。在內存極度緊張的時候, sshd 這種不怎麼佔內存的進程也無法倖免。覆巢之下,焉有完卵。

OOM Killer 的全稱叫 Out Of Memory Killer,今天簡單給大家介紹下,它是 linux 內核的內存保護機制。當應用程式大量請求內存導致內存不足的時候,通常會觸發 OOM Killer,OOM Killer 會根據相對簡單粗暴的算法殺掉某個進程以解燃眉之急。如果殺掉一個進程還不足以解決問題,Killer 會按照它對進程的評分,逐次幹掉,分數越高,被幹掉的機率越大,直到內存可以正常使用。

為什麼會出現這種情況呢?這要從 Linux 的內存管理策略說起。

作業系統裡內存管理的主要作用是,進程請求內存的時候為其分配可用內存,進程釋放後回收內存,並監控內存的使用狀況。最簡單的內存管理方式是所有運行的進程對所有內存具備訪問權限,這種方式下,進程必須包含對所需硬體操作的全部代碼,能找到對應的內存地址,然後將自己的數據載入內存。以這種方式進行開發,內存的利用率上不去不說,每個程式設計師差不多都會被內存管理編程搞到吐血,所以這件事得交給作業系統來做。

現代作業系統需要程序能夠共享內存,並且內存的限制對開發者透明,有些程序佔用了內存空間,但不一定是一直使用的,這樣可以把這部分內存數據序列化到磁碟上,需要的時候再加載到內存裡,這樣內存資源永遠會給最需要的程序使用。有了需求,自然會有解決辦法,程式設計師們為了拯救自己,可以做出各種匪夷所思的技術,他們發明了虛擬內存(Virtual Memory)。虛擬內存技術支持程序訪問比物理內存大得多的內存空間,也使得多個程序共享內存更加高效。物理內存由 RAM 晶片提供,虛擬內存則依靠透明的使用磁碟空間,使程序運行起來好像有了更大的內存空間。雖然是個錯覺,但是程序們都很開心,程式設計師們也很開心,但幸福的時光總是短暫的,過度分配內存(over-commit memory)的方式也會帶來麻煩。

當大部分程序都處於「工作狀態」的時候(例如高並發),大量的進程會向作業系統申請內存,這就導致了內存的需求加起來在某個瞬間超過了物理內存,怎麼處理這種情況呢?難不倒程式設計師,他們又為此設計了一位殺手,起名內存溢出殺手 —— OOM Killer。當內存溢出的時候,OOM Killer 會冷靜的挑一些進程出來,殘忍殺害,以保證整個系統正常運行。這時候的內存管理就像銀行擠兌,人們把現金存入銀行,收取一定的利息,平時只有少數人去銀行取現,銀行會拿人們存的錢去做更有價值的投資。但是,如果大部分人都去銀行取現,銀行是沒有那麼多現金的。擠兌,往往會導致銀行倒閉,畢竟銀行沒辦法請殺手幹掉那麼多的擠兌群眾。現在的 P2P 理財也是一個道理,投資者都去變現,無論是多麼良性的資產,一樣玩完。

OOM Killer 的調用鏈路大致是這樣的,分配內存,發現內存不足,這時候系統會調用 OOM Killer 的 out_of_memory 方法,通過 select_bad_process 找到評分最高的進程,幹掉。

每個進程的分數存儲在 /proc/$pid/oom_score,這個評分是程序通過各種因子計算出來的,比如進程起始佔用的虛擬內存,進程的子進程(如果有的話)對內存的使用情況,對 cpu 的使用時間,程序運行時間,進程間交互等。大的方向就是,你用內存越狠越頻繁,評分越高,當發生內存溢出的時候越容易被殺掉。

具體的代碼和算法可以參考 mm/oom_kill.c,注釋寫的很清楚。
https://github.com/torvalds/linux/blob/master/mm/oom_kill.c

這時候的另一個矛盾是,大部分重要的應用程式,都會頻繁的大量使用內存,如何避免重要的進程被少掉呢?OOM Killer 提供了一個分數調節機制。修改 /proc/$pid/oom_score_adj 的調整分數,可以降低該進程被 kill 的機率。由於 score 的分值範圍在 [-17, 15],本著分值越大約容易被殺死的原則,為一個進程設置 adj 為 -17,基本上可以保證該進程是安全的。

如果查看分數排名最高的進程呢?可以用下面這個腳本:

關於 Linux 內存管理更詳細的內容,可以參考這篇文章(或者直接去讀英文官方文檔)
http://learning-kernel.readthedocs.io/en/latest/mem-management.html

當然了,解決內存不足問題的好辦法之一是增加物理內存,內存多了,很多問題就不是問題了。隨著硬體的發展,之前遇到的很多軟體問題和程式設計師需要窮盡聰明才智解決的問題,都慢慢消失了,這也是一種生活。

最後一期互相:騰訊 2018雲 + 未來峰會將於 5 月 23 號在廣州召開,騰訊小馬哥會出席這個會議,據說張小龍也在現場。我和鳥哥等人會去參加個開發者專場,歡迎你一同前往。

如何把握雲計算紅利?來聽業界大咖乾貨(騰訊互選,點擊查看詳情)

相關焦點

  • 嵌入式Linux內存管理的一些知識點總結
    這個內存管理的知識點還真的需要我們專門的去理解一下,今天大家一起來學習學習嵌入式Linux內存管理的知識。1.不涉及linux內核的彙編知識,僅C語言層面解析1.回答:彙編主要處理的是寄存器地址(包括內容)的計算,進行一部分的地址轉換工作(當然,它是重要的);C語言處理了極大部分的系統內存管理工作。
  • 別再說你不懂 Linux 內存管理了
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • Linux內核中的內存管理
    本文對Linux內存管理使用到的一些數據結構和函數作了簡要描述,而不深入到它們的內部。對這些數據結構和函數有了一個總體上的了解後,再針對各項分別作深入了解的時候,也許會簡單一些。Linux內存訪問限制(僅針對32位系統)默認情況下Linux的內核空間映射到4G虛擬地址的最高1G(即0xC0000000 - 0xFFFFFFF)。
  • 郭健:Linux內存管理系統參數配置之OOM(內存耗盡)
    如果這個名詞對你毫無壓力,你可以直接進入第三章,這一章是描述具體的參數的,除了描述具體的參數,我們引用了一些具體的內核代碼,本文的代碼來自4.0內核,如果有興趣,可以結合代碼閱讀,為了縮減篇幅,文章中的代碼都是刪減版本的。按照慣例,最後一章是參考文獻,本文的參考文獻都是來自linux內核的Documentation目錄,該目錄下有大量的文檔可以參考,每一篇都值得細細品味。
  • 萬字長文,別再說你不懂Linux內存管理了(合輯),30 張圖給你安排的明明白白
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • 別再說你不懂Linux內存管理了,30 張圖給你安排的明明白白
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • 10 張圖解再談 Linux 物理內存和虛擬內存
    物理內存管理在Linux系統中通過分段和分頁機制,把物理內存劃分 4K 大小的內存頁 Page(也稱作頁框Page Frame),物理內存的分配和回收都是基於內存頁進行,把物理內存分頁管理的好處大大的。
  • 別再說你不懂Linux內存管理了,30 張圖給你安排的明明白白
    虛擬地址即使是現代作業系統中,內存依然是計算機中很寶貴的資源,看看你電腦幾個T固態硬碟,再看看內存大小就知道了。為了充分利用和管理系統內存資源,Linux採用虛擬內存管理技術,利用虛擬內存技術讓每個進程都有4GB 互不幹涉的虛擬地址空間。
  • 系統內存/進程內存知識掃盲
    Linux的內存管理和相關概念要比Windows複雜一些,而且還有一些區別,有悖於平時的理解,而做性能測試的同學都知道,很多時候性能測試的伺服器都是linux
  • 為何Cortex-M處理器運行不了linux
    值得注意的是,Cortex-M下的處理器沒有內存管理單元MMU。二、內存管理單元MMU內存管理單元簡稱MMU,它負責虛擬地址到物理地址的映射,並提供硬體機制的內存訪問權限檢查。在多用戶、多進程的作業系統中,MMU使得各個用戶進程都有獨立的地址空間。
  • JVM內存分析,程式設計師進階的必經之路
    話不多說,開始今天的學習:JVM也就是Java虛擬機,它的內存結構這塊知識點。你說它重要吧,編寫代碼基本用不到它;你說它不重要吧,程式設計師想要進階又必須對底層有一定的了解。接下來我們就詳細地了解下Java虛擬機。
  • 程式設計師需要了解的硬核知識之作業系統和應用
    我在《程式設計師需要了解的硬核知識之CPU》 這篇文章中提到了彙編語言,這裡簡單再提一下。彙編語言是一種低級語言,也被稱為符號語言。彙編語言是第二代計算機語言,在彙編語言中,用助記符代替機器指令的操作碼,用地址符號或標號代替指令或操作數的地址。
  • Linux平臺中調試C/C++內存洩漏方法 (騰訊和MTK面試的時候問到的)
    自從 70 年代末期以來,C/C++ 程式設計師就一直討論此類錯誤,但其影響在 2007 年仍然很大。與許多其他類型的常見錯誤不同,內存錯誤通常具有隱蔽性,即它們很難再現,症狀通常不能在相應的原始碼中找到。例如,無論何時何地發生內存洩 漏,都可能表現為應用程式完全無法接受,同時內存洩漏不是顯而易見[1]。存在內存錯誤的 C 和 C++ 程序會導致各種問題。
  • Linux如何調試內存洩漏
    內存洩漏並非指內存在物理上的消失,而是應用程式分配某段內存後,由於設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。我們平時開發過程中不可避免的會遇到內存洩漏問題,你是如何排查的呢?估計你是使用下面這幾個工具吧?
  • Swift高階之內存管理
    image內存管理在任何程式語言中都是核心概念。儘管有很多教程解釋了Swift自動引用計數的基本原理,但我發現沒有一個可以從編譯器的角度對其進行解釋。在本文中,我們將學習iOS內存管理,引用計數和對象生命周期等基礎知識之外的內容。讓我們從基礎開始,逐步進入ARC和Swift Runtime的內部,首先思考以下問題:內存是什麼?
  • Linux下找出吃內存的方法總結
    linux下查詢進程佔用的內存方法總結,假設現在有一個「php-cgi」的進程 ,進程id為「25282」。
  • Linux堆內存管理深入分析(二)
    >)可以包含多個heaps,所以為了便於管理,就給每個heap分配一個heap header。 5.1 隱式鍊表技術前文說過,任何堆內存管理器都是以chunk為單位進行堆內存管理的,而這就需要一些數據結構來標誌各個塊的邊界,以及區分已分配塊和空閒塊。
  • 從串口驅動到Linux驅動模型,想轉Linux的必會!
    以便讀者能對OS原理有更深入的了解和更具體的掌握。在具體分析之前。我們必須對串口。驅動。和Linux作業系統有一定的了解。這一階段我們有三個問題需要解決:1.什麼是Linux作業系統。2.什麼是Linux設備驅動。3.關於串口的種種。要了解這些概念。如下我介紹了一點這方面的知識。不過遺憾的是對一些概念有著不可避免的向前引用。
  • Android 內存管理詳解
    Android經典好文推薦,通過閱讀本文,您將收穫以下知識點:一、Android 垃圾回收機制(GC)二、共享內存三、內存的申請與回收四、內存限制五、不同App切換時的內存管理Android Runtime(ART)和Dalvik虛擬機使用 分頁 和 內存映射 來管理內存。
  • Linux內存初始化(上)
    有了armv8架構訪問內存的理解,我們來看下linux在內存這塊的初始化就更容易理解了。創建啟動頁表:在彙編代碼階段的head.S文件中,負責創建映射關係的函數是create_page_tables。fixmap區之early ioremap:對於一些硬體需要在內存管理系統起來之前就要工作的,我們就可以使用這種機制來映射內存給這些硬體driver使用。各個模塊在使用完early ioremap的地址後,需要儘快把這段映射的虛擬地址釋放掉,這樣才能反覆被其他模塊繼續申請使用。