精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux內(nèi)存的分配管理與內(nèi)存回收基本框架

Linux閱碼場(chǎng) ? 來(lái)源:Linux閱碼場(chǎng) ? 作者:Linux閱碼場(chǎng) ? 2022-06-01 16:02 ? 次閱讀

1.序言

內(nèi)存對(duì)計(jì)算機(jī)系統(tǒng)來(lái)說(shuō)是一項(xiàng)非常重要的資源,直接影響著系統(tǒng)運(yùn)行的性能。最初的時(shí)候,系統(tǒng)是直接運(yùn)行在物理內(nèi)存上的,這存在著很多的問(wèn)題,尤其是安全問(wèn)題。后來(lái)出現(xiàn)了虛擬內(nèi)存,內(nèi)核和進(jìn)程都運(yùn)行在虛擬內(nèi)存上,進(jìn)程與進(jìn)程之間有了空間隔離,增加了安全性。進(jìn)程與內(nèi)核之間有特權(quán)級(jí)的區(qū)別,進(jìn)程運(yùn)行在非特權(quán)級(jí),內(nèi)核運(yùn)行在特權(quán)級(jí),進(jìn)程不能訪問(wèn)內(nèi)核空間,只能通過(guò)系統(tǒng)調(diào)用和內(nèi)核進(jìn)行交互,內(nèi)核會(huì)對(duì)進(jìn)程進(jìn)行嚴(yán)格的權(quán)限檢查和參數(shù)檢查,使得系統(tǒng)更加安全。通過(guò)虛擬內(nèi)存訪問(wèn)物理內(nèi)存,每次都需要解析頁(yè)表,這大大降低了內(nèi)存訪問(wèn)的性能,為此CPUMMU里面加入了TLB用來(lái)緩存頁(yè)表解析的結(jié)果,這樣由于程序的時(shí)間局部性和空間局部性,能極大的提高內(nèi)存訪問(wèn)的速度。雖然和直接訪問(wèn)物理內(nèi)存相比,仍然存在著一些性能損耗,但是損耗已經(jīng)降到很低了。因此虛擬內(nèi)存機(jī)制在系統(tǒng)安全和性能之間達(dá)到了最大的平衡。雖然如此,但是虛擬內(nèi)存機(jī)制也使得計(jì)算機(jī)的內(nèi)存系統(tǒng)變得異常復(fù)雜,給我們的編程帶來(lái)了巨大的挑戰(zhàn)。內(nèi)存問(wèn)題,在很多軟件公司里面,都是一個(gè)非常重要非常讓人頭疼的問(wèn)題,今天我們從OOM的角度來(lái)幫大家提高一點(diǎn)內(nèi)存方面的知識(shí),雖然不能說(shuō)幫助人們來(lái)完全解決內(nèi)存問(wèn)題,但是也能從一個(gè)側(cè)面來(lái)提高大家分析內(nèi)存問(wèn)題相關(guān)的能力。

2.內(nèi)存的分配管理

我們已經(jīng)知道了物理內(nèi)存、虛擬內(nèi)存、用戶空間、內(nèi)核空間之間的區(qū)別,下面我們?cè)賮?lái)深入的了解一下這方面的知識(shí)。系統(tǒng)剛啟動(dòng)的時(shí)候是運(yùn)行在物理內(nèi)存之上的,然后系統(tǒng)建立了一段足夠自己繼續(xù)運(yùn)行的恒等映射的頁(yè)表,也就是把物理地址映射到相同地址的虛擬地址上。等到系統(tǒng)再進(jìn)一步初始化之后,就會(huì)建立完整的頁(yè)表來(lái)映射物理內(nèi)存,并把內(nèi)核映射在虛擬地址空間的高部位,對(duì)于32位系統(tǒng)來(lái)說(shuō)是3G之上的內(nèi)存空間,對(duì)于64系統(tǒng)來(lái)說(shuō),是映射到比較接近虛擬地址頂端的地方。內(nèi)核初始化之后就會(huì)啟動(dòng)init進(jìn)程,從而啟動(dòng)整個(gè)用戶空間的所有進(jìn)程。內(nèi)核空間和用戶空間的內(nèi)存管理方式的差別是非常大的,首先內(nèi)核是不會(huì)缺頁(yè)也不會(huì)換頁(yè)的,不會(huì)缺頁(yè)是指內(nèi)核的物理內(nèi)存在啟動(dòng)時(shí)就直接映射好了,使用時(shí)直接分配就行了,分配好虛擬內(nèi)存的同時(shí)物理內(nèi)存也分配好了。不會(huì)換頁(yè)是指,當(dāng)系統(tǒng)內(nèi)存不足時(shí)內(nèi)核自身使用的物理內(nèi)存不會(huì)被swap出去。與此相反,用戶空間的內(nèi)存分配是先分配虛擬內(nèi)存,此時(shí)并不會(huì)直接分配物理內(nèi)存,而是延遲到程序運(yùn)行時(shí)訪問(wèn)到哪里的內(nèi)存,如果這個(gè)內(nèi)存還沒(méi)有對(duì)應(yīng)的物理內(nèi)存,MMU就會(huì)報(bào)缺頁(yè)異常從而陷入內(nèi)核,執(zhí)行內(nèi)核的缺頁(yè)異常handler給分配物理內(nèi)存,并建立頁(yè)表映射,然后再回到用戶空間剛才的那個(gè)指令處繼續(xù)執(zhí)行。當(dāng)系統(tǒng)內(nèi)存不足時(shí),用戶空間使用的物理內(nèi)存會(huì)被swap到磁盤,從而回收物理內(nèi)存。之后如果進(jìn)程再訪問(wèn)這段內(nèi)存又會(huì)再發(fā)生缺頁(yè)異常從swap處把內(nèi)存內(nèi)容加載回來(lái)。

3.進(jìn)程的內(nèi)存空間布局

明白了上面這些,我們?cè)賮?lái)看看進(jìn)程的用戶空間內(nèi)存布局。我們都知道進(jìn)程的內(nèi)存空間是由代碼區(qū)、數(shù)據(jù)區(qū)、堆區(qū)、棧區(qū)組成。我們先來(lái)看下面的圖,我們以32位進(jìn)程為例進(jìn)行講解,64位的數(shù)值太大不好畫的,但是原理都是一樣的。

3dc0a69e-e165-11ec-ba43-dac502259ad0.png

進(jìn)程啟動(dòng)之后的內(nèi)存布局如上圖所示,程序file的代碼段被映射到text區(qū),數(shù)據(jù)段映射到data區(qū),內(nèi)核還會(huì)幫進(jìn)程建立堆內(nèi)存區(qū)映射和棧內(nèi)存區(qū)映射,堆一般緊挨著data區(qū)的末尾往上增長(zhǎng),棧區(qū)在3G下面一點(diǎn)點(diǎn)往下增長(zhǎng)。數(shù)據(jù)區(qū)和代碼區(qū)是在進(jìn)程啟動(dòng)時(shí)由內(nèi)核之間分配好的,之后大小就不會(huì)再改變,heap區(qū)是隨著程序運(yùn)行中不斷的malloc/free而增長(zhǎng)或者縮小的,stack區(qū)是隨時(shí)程序運(yùn)行的局部變量分配釋放而變化的,局部變量的分配釋放是自動(dòng)的,因此這三個(gè)區(qū)域也分別被叫做靜態(tài)內(nèi)存、動(dòng)態(tài)內(nèi)存、自動(dòng)內(nèi)存。由此我們可以看出,我們不必對(duì)靜態(tài)內(nèi)存、自動(dòng)內(nèi)存太操心,我們最應(yīng)該關(guān)系的是動(dòng)態(tài)內(nèi)存。我們可以brk系統(tǒng)調(diào)用擴(kuò)大heap區(qū)域來(lái)增加堆內(nèi)存,然后再自己管理使用堆內(nèi)存,但是這樣做顯然很麻煩。因此C庫(kù)為我們準(zhǔn)備了相關(guān)的APImallocfree,來(lái)分配和釋放堆內(nèi)存,這樣就方便到了。 C庫(kù)里面最早的malloc實(shí)現(xiàn)叫做dlmalloc,在計(jì)算機(jī)早期還是單CPU時(shí)代的時(shí)候非常流行,效率也非常高,但是隨著SMPCPU時(shí)代的到來(lái),dlmalloc的缺點(diǎn)也越來(lái)越明顯,尤其是多線程同時(shí)調(diào)用malloc的時(shí)候,鎖沖突越來(lái)越嚴(yán)重,嚴(yán)重影響了性能。后來(lái)業(yè)界相繼出現(xiàn)了ptmallocjemallocscudo等優(yōu)秀的malloc庫(kù)。 PtmallocGlibc的默認(rèn)malloc實(shí)現(xiàn),jemalloc庫(kù)是首先實(shí)現(xiàn)在FreeBSDmalloc庫(kù),后廣泛應(yīng)用于FireFoxRedisNetty等眾多產(chǎn)品中,也長(zhǎng)期是安卓的默認(rèn)malloc庫(kù)實(shí)現(xiàn)。目前安卓已經(jīng)把malloc庫(kù)替換為scudo了,據(jù)說(shuō)scudo在安全和性能方面都很不錯(cuò)。 程序簡(jiǎn)單的時(shí)候還好說(shuō),但是對(duì)于很多產(chǎn)品級(jí)的軟件來(lái)說(shuō),其邏輯結(jié)構(gòu)都非常復(fù)雜,進(jìn)而導(dǎo)致其內(nèi)存管理方面也很復(fù)雜,很容易出現(xiàn)棧溢出、野指針、內(nèi)存泄漏等問(wèn)題。我們有著很多方法和規(guī)則來(lái)規(guī)避這些問(wèn)題,比如誰(shuí)申請(qǐng)誰(shuí)釋放,引用計(jì)數(shù),智能指針等,但是仍然不能完全解決這些問(wèn)題。尤其是內(nèi)存泄漏,在很多公司里面都是令人頭疼的頑疾,對(duì)于內(nèi)存泄漏也存在著很多工具,但是都無(wú)法完美的解決這個(gè)問(wèn)題。我們今天要說(shuō)的不是內(nèi)存泄漏,而是由于內(nèi)存泄漏或者內(nèi)存使用不合理而導(dǎo)致的OOM問(wèn)題。

4.內(nèi)存回收基本框架

在講OOM之前,我們先來(lái)了解一下內(nèi)核內(nèi)存回收的總體框架。內(nèi)存作為系統(tǒng)最寶貴的資源,總是不夠用的,經(jīng)常需要進(jìn)行回收。內(nèi)存回收可分為兩種方式,同步回收和異步回收,同步回收是在分配內(nèi)存時(shí)發(fā)現(xiàn)內(nèi)存不足直接調(diào)用函數(shù)進(jìn)行回收,異步回收是喚醒專門的回收線程kswapd進(jìn)行回收。我們先看一下它們的總體架構(gòu)圖,然后再一一說(shuō)明。

3ddbaac0-e165-11ec-ba43-dac502259ad0.png

同步回收的話是在alloc_pages時(shí)發(fā)現(xiàn)內(nèi)存不足就直接進(jìn)行回收,首先嘗試的是內(nèi)存規(guī)整,也就是內(nèi)存碎片整理,比如說(shuō)系統(tǒng)當(dāng)前有10個(gè)不連續(xù)的空閑page,但是你要分配兩個(gè)連續(xù)的page,顯然是無(wú)法分配的,此時(shí)就要進(jìn)行內(nèi)存規(guī)整,通過(guò)移動(dòng)movable page,使空閑page盡量連在一起,這樣能有可能分配出多個(gè)連續(xù)的page了。如果內(nèi)存規(guī)整之后還是無(wú)法分配到內(nèi)存,此時(shí)就會(huì)進(jìn)行頁(yè)幀回收了。用戶空間的物理內(nèi)存可以分為兩種類型,文件頁(yè)和匿名頁(yè),文件頁(yè)是text data段對(duì)應(yīng)的頁(yè)幀,它們都有文件做后備存儲(chǔ),匿名是棧和堆對(duì)應(yīng)的內(nèi)存頁(yè),它們沒(méi)有對(duì)應(yīng)的文件,一般用swap分區(qū)或者swap文件做它們的后備存儲(chǔ)。系統(tǒng)會(huì)首先考慮干凈的文件頁(yè)進(jìn)行回收,因?yàn)榛厥账鼈冎灰苯觼G棄內(nèi)容就可以了,需要的時(shí)候再直接從文件里讀取回來(lái),這樣不會(huì)有數(shù)據(jù)丟失。如果沒(méi)有干凈的文件頁(yè)或者干凈的文件頁(yè)不太多,此時(shí)就要從dirty 文件頁(yè)和匿名頁(yè)進(jìn)行回收了,因?yàn)樗鼈兌家M(jìn)行IO操作,所以會(huì)非常的慢。如果頁(yè)幀回收也回收不到內(nèi)存的話,內(nèi)核只能使出最后一招了,OOM Killer,直接殺進(jìn)程進(jìn)行內(nèi)存回收,雖然這招好像不太文雅,但是也是沒(méi)有辦法,因?yàn)椴贿@樣做的話,系統(tǒng)沒(méi)有多余的內(nèi)存就沒(méi)法繼續(xù)運(yùn)行,系統(tǒng)就會(huì)卡死,用戶就會(huì)重啟系統(tǒng),結(jié)果更糟,所以殺進(jìn)程也是最后的無(wú)奈之舉。一般能走到這一步都是因?yàn)檫M(jìn)程有長(zhǎng)期或者嚴(yán)重的內(nèi)存泄漏導(dǎo)致的。 異步回收線程kswapd是被周期性的喚醒來(lái)執(zhí)行回收任務(wù)的,當(dāng)然同步回收的時(shí)候也會(huì)順便喚醒它來(lái)一起回收內(nèi)存。有一點(diǎn)需要注意的是kswapd線程不是per CPU的,而是per node的,是一個(gè)NUMA節(jié)點(diǎn)一個(gè)線程,這是因?yàn)閮?nèi)存的分配是per node不是 per CPU的,大部分內(nèi)存分配都是優(yōu)先從本node分配或者只能從本node分配,因此哪個(gè)node的內(nèi)存不足了就喚醒哪個(gè)nodekswapd線程就行內(nèi)存回收工作。對(duì)于家庭電腦手機(jī)來(lái)說(shuō)都是一個(gè)node,所以一般就只有一個(gè)kswapd線程。Kswapd完成回收工作之后,它會(huì)喚醒kcompactd線程進(jìn)行內(nèi)存規(guī)整,對(duì)的,內(nèi)存規(guī)整也可以異步執(zhí)行。

5.OOM基本原理

在講內(nèi)核的OOM Killer之前,我們先來(lái)說(shuō)一下OOM基本概念。OOMout of memory,就是內(nèi)存用完了耗盡了的意思。OOM分為虛擬內(nèi)存OOM和物理內(nèi)存OOM,兩者是不一樣的。虛擬內(nèi)存OOM發(fā)生在用戶空間,因?yàn)橛脩艨臻g分配的就是虛擬內(nèi)存,不能分配物理內(nèi)存,程序在運(yùn)行的時(shí)候觸發(fā)缺頁(yè)異常從而需要分配物理內(nèi)存,內(nèi)核自身在運(yùn)行的時(shí)候也需要分配物理內(nèi)存,如果此時(shí)物理內(nèi)存不足了,就會(huì)發(fā)生物理內(nèi)存OOM。用戶空間虛擬內(nèi)存OOM表現(xiàn)為mallocmmap等內(nèi)存分配接口返回失敗,錯(cuò)誤碼為ENOMEM。大家也許會(huì)想,虛擬內(nèi)存會(huì)OOM嗎,虛擬內(nèi)存那么大,對(duì)于32位進(jìn)程來(lái)說(shuō)就有3G,對(duì)于64位進(jìn)程來(lái)說(shuō)至少也得有上百G,應(yīng)有盡有,而且很多教科書上都說(shuō)的是虛擬內(nèi)存可以隨意分配,不受物理內(nèi)存的限制,事實(shí)上真的是這樣嗎,讓我們來(lái)看一看。

5.1、虛擬內(nèi)存OOM

虛擬內(nèi)存我們是不是可以隨意分配,虛擬空間有多大我們就能分配多少?事實(shí)不是這樣的。UNIX世界有個(gè)著名的哲學(xué)原理,提供機(jī)制而不是策略,對(duì)于這個(gè)問(wèn)題,Linux也提供了機(jī)制,我們可以通過(guò) /proc/sys/vm/overcommit_memory文件來(lái)選擇策略。我們有三種選擇,我們可以往這個(gè)文件里面寫入012來(lái)選擇不同的策略,這三個(gè)值對(duì)應(yīng)的宏是:
  • #define OVERCOMMIT_GUESS 0

  • #define OVERCOMMIT_ALWAYS 1

  • #define OVERCOMMIT_NEVER 2

通過(guò)宏名我們也可以大概猜出來(lái)是啥意思,下面我們一一解析一下,先從最簡(jiǎn)單的開(kāi)始,OVERCOMMIT_ALWAYS,從名字就可以看出來(lái),只要虛擬內(nèi)存空間還有富余,你malloc多少內(nèi)存就給你多少虛擬內(nèi)存,不管它物理內(nèi)存到底還夠不夠用。OVERCOMMIT_GUESS,名為GUESS,實(shí)在不好guess的,通過(guò)看代碼發(fā)現(xiàn),這個(gè)模式允許你最多分配的虛擬內(nèi)存不能超過(guò)系統(tǒng)總的物理內(nèi)存(這里說(shuō)的總物理內(nèi)存是物理內(nèi)存加swap的總和,因?yàn)?/span>swap在一定意義上也相當(dāng)于是增加了物理內(nèi)存),也就是說(shuō)一個(gè)進(jìn)程分配的總虛擬內(nèi)存可以和系統(tǒng)的總物理內(nèi)存相同,還是夠可以的。OVERCOMMIT_NEVER,這個(gè)就比較苛刻了,它像一位勤儉持家的媽媽,總是只給你勉強(qiáng)夠用的零花錢,從來(lái)不多給一分。我們來(lái)看一下它的計(jì)算過(guò)程,它先計(jì)算一個(gè)基準(zhǔn)值,默認(rèn)等于50%的物理內(nèi)存加上swap大小,然后再減去系統(tǒng)管理保留的內(nèi)存,再減去用戶管理保留的內(nèi)存,如果系統(tǒng)所有已分配的虛擬內(nèi)存大于這個(gè)值,就返回分配失敗。具體情況大家可以去看代碼:
linux-src/mm/util.c:__vm_enough_memory。
我們?cè)賮?lái)看一個(gè)這個(gè)三個(gè)宏的公共部分OVERCOMMIT,過(guò)度承諾,這個(gè)詞想表達(dá)什么含義呢,過(guò)程承諾always never guess,我們可以看出來(lái),過(guò)程承諾指的是,系統(tǒng)允許分配給你的虛擬內(nèi)存是對(duì)你的承諾,后面當(dāng)你具體用訪問(wèn)內(nèi)存的時(shí)候,是要給你分配物理內(nèi)存來(lái)實(shí)現(xiàn)對(duì)你的承諾的,那么這個(gè)承諾到底能不能實(shí)現(xiàn)呢,如果不能實(shí)現(xiàn)會(huì)怎么樣呢?

5.2、物理內(nèi)存OOM

出來(lái)混遲早是要還的,分配出去的虛擬內(nèi)存遲早是要兌現(xiàn)物理內(nèi)存的。內(nèi)核運(yùn)行時(shí)會(huì)分配物理內(nèi)存,程序運(yùn)行時(shí)也會(huì)通過(guò)缺頁(yè)異常去分配物理。如果此時(shí)沒(méi)有足夠的物理內(nèi)存,內(nèi)核會(huì)通過(guò)各種手段來(lái)收集物理內(nèi)存,比如內(nèi)存規(guī)整、回收緩存、swap等,如果這些手段都用盡了,還是沒(méi)有收集到足夠的物理內(nèi)存,那么就只能使出最后一招了,OOM Killer,通過(guò)殺死進(jìn)程來(lái)回收內(nèi)存。代碼實(shí)現(xiàn)在linux-src/mm/oom_kill.c:out_of_memory,觸發(fā)點(diǎn)在linux-src/mm/page_alloc.c:__alloc_pages_may_oom,當(dāng)使用各種方法都回收不到不到內(nèi)存時(shí)會(huì)調(diào)用out_of_memory函數(shù)。

out_of_memory函數(shù)的實(shí)現(xiàn)還是有點(diǎn)復(fù)雜,我們把各種檢測(cè)代碼和輔助代碼都去除之后,高度簡(jiǎn)化之后的函數(shù)如下:
bool out_of_memory(struct oom_control *oc){    select_bad_process(oc);    oom_kill_process(oc, "Out of memory");}
這樣就看邏輯就很簡(jiǎn)單了,
  • 1先選擇一個(gè)要?dú)⑺赖倪M(jìn)程

  • 2殺死它,就是這么簡(jiǎn)單。

oom_kill_process函數(shù)的目的很簡(jiǎn)單,但是實(shí)現(xiàn)過(guò)程也有點(diǎn)復(fù)雜,這里就不展開(kāi)分析了,大家可以自行去看一下代碼。我們重點(diǎn)分析一下select_bad_process函數(shù)的邏輯,select_bad_process主要是依靠oom_score來(lái)進(jìn)行進(jìn)程選擇的。我們先來(lái)看一下和每一個(gè)進(jìn)程相關(guān)聯(lián)的三個(gè)文件。 /proc//oom_score系統(tǒng)計(jì)算出來(lái)的oom_score值,只讀文件,取值范圍0 –- 10000代表never kill1000代表aways kill,值越大,進(jìn)程被選中的概率越大。 /proc//oom_score_adj 讓用戶空間調(diào)節(jié)oom_score之值的接口,root可讀寫,取值范圍 -1000 --- 1000默認(rèn)為0若為 -1000,則oom_score加上此值一定小于等于0,從而變成never kill進(jìn)程。OS可以把一些關(guān)鍵的系統(tǒng)進(jìn)程的oom_score_adj設(shè)為-1000,從而避免被oom kill /proc//oom_adj 舊的接口文件,為兼容而保留,root可讀寫,取值范圍 -16 — 15,會(huì)被線性映射到oom_score_adj,特殊值 -17代表 OOM_DISABLE,大家盡量不用再用此接口。

下面我們來(lái)分析一下select_bad_process函數(shù)的實(shí)現(xiàn):

static void select_bad_process(struct oom_control *oc){  oc->chosen_points = LONG_MIN;  struct task_struct *p;
  rcu_read_lock();  for_each_process(p)    if (oom_evaluate_task(p, oc))      break;  rcu_read_unlock();}

函數(shù)首先把chosen_points初始化為最小的Long值,這個(gè)值是用來(lái)比較所有的oom_score值,最后誰(shuí)的值最大就選中哪個(gè)進(jìn)程。然后函數(shù)已經(jīng)遍歷所有進(jìn)程,計(jì)算其oom_score,并更新chosen_points和被選中的task,有點(diǎn)類似于選擇排序。我們繼續(xù)看oom_evaluate_task函數(shù)是如何評(píng)估每個(gè)進(jìn)程的函數(shù)。
static int oom_evaluate_task(struct task_struct *task, void *arg){  struct oom_control *oc = arg;  long points;  if (oom_unkillable_task(task))    goto next;  /* p may not have freeable memory in nodemask */  if (!is_memcg_oom(oc) && !oom_cpuset_eligible(task, oc))    goto next;  if (oom_task_origin(task)) {    points = LONG_MAX;    goto select;  }  points = oom_badness(task, oc->totalpages);  if (points == LONG_MIN || points < oc->chosen_points)    goto next;select:  if (oc->chosen)    put_task_struct(oc->chosen);  get_task_struct(task);  oc->chosen = task;  oc->chosen_points = points;next:  return 0;abort:  if (oc->chosen)    put_task_struct(oc->chosen);  oc->chosen = (void *)-1UL;  return 1;}

此函數(shù)首先會(huì)跳軌所有不適合kill的進(jìn)程,如init進(jìn)程、內(nèi)核線程、OOM_DISABLE進(jìn)程等。然后通過(guò)select_bad_process算出此進(jìn)程的得分points 也就是oom_score,并和上一次的勝出進(jìn)程進(jìn)行比較,如果小的會(huì)話就會(huì)goto next 返回,如果大的話就會(huì)更新oc->chosen的task 和 chosen_points也就是目前最高的oom_score。那么oom_badness是如何計(jì)算的呢?
long oom_badness(struct task_struct *p, unsigned long totalpages){  long points;  long adj;  if (oom_unkillable_task(p))    return LONG_MIN;  p = find_lock_task_mm(p);  if (!p)    return LONG_MIN;  adj = (long)p->signal->oom_score_adj;  if (adj == OOM_SCORE_ADJ_MIN ||      test_bit(MMF_OOM_SKIP, &p->mm->flags) ||      in_vfork(p)) {    task_unlock(p);    return LONG_MIN;  }  points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +    mm_pgtables_bytes(p->mm) / PAGE_SIZE;  task_unlock(p);  adj *= totalpages / 1000;  points += adj;  return points;}

oom_badness首先把unkiller的進(jìn)程也就是init進(jìn)程內(nèi)核線程直接返回LONG_MIN,這樣他們就不會(huì)被選中而殺死了,這里看好像和前面的檢測(cè)冗余了,但是實(shí)際上這個(gè)函數(shù)還被/proc//oom_scoreshow函數(shù)調(diào)用用來(lái)顯示數(shù)值,所以還是有必要的,這里也說(shuō)明了一點(diǎn),oom_score的值是不保留的,每次都是即時(shí)計(jì)算。然后又把oom_score_adj-1000的進(jìn)程直接也返回LONG_MIN,這樣用戶空間專門設(shè)置的進(jìn)程就不會(huì)被kill了。最后就是計(jì)算oom_score了,計(jì)算方法比較簡(jiǎn)單,就是此進(jìn)程使用的RSS駐留內(nèi)存、頁(yè)表、swap之和越大,也就是此進(jìn)程所用的總內(nèi)存越大,oom_score的值就越大,邏輯簡(jiǎn)單直接,誰(shuí)用的物理內(nèi)存最多就殺誰(shuí),這樣就能夠回收更多的物理內(nèi)存,而且使用內(nèi)存最多的進(jìn)程很可能是內(nèi)存泄漏了,所以此算法雖然很簡(jiǎn)單,但是也很合理。

可能很多會(huì)覺(jué)得這里講的不對(duì),和自己在網(wǎng)上的看到的邏輯不一樣,那是因?yàn)榫W(wǎng)上有很多講oom_score算法的文章都是基于2.6版本的內(nèi)核講的,那個(gè)算法比較復(fù)雜,會(huì)考慮進(jìn)程的nice值,nice值小的,oom_score會(huì)相應(yīng)的降低,也會(huì)考慮進(jìn)程的運(yùn)行時(shí)間,運(yùn)行時(shí)間越長(zhǎng),oom_score值也會(huì)相應(yīng)的降低,因?yàn)楫?dāng)時(shí)認(rèn)為進(jìn)程運(yùn)行的時(shí)間長(zhǎng)消耗內(nèi)存多是合理的。但是這個(gè)算法會(huì)讓那些緩慢內(nèi)存泄漏的進(jìn)程逃脫制裁。因此后來(lái)這個(gè)算法就改成現(xiàn)在這樣的了,只考慮誰(shuí)用的內(nèi)存多就殺誰(shuí),簡(jiǎn)潔高效。

5.3、安卓LMK簡(jiǎn)介

除了OOM KillerAndroid上還開(kāi)發(fā)了low memory killer機(jī)制,我們?cè)诖艘埠?jiǎn)單介紹一下。LMK在系統(tǒng)內(nèi)存較低時(shí)就開(kāi)始?xì)⑦M(jìn)程,而不是等到內(nèi)存不足時(shí)再殺。LMK復(fù)用了OOMKiller /proc//oom_score_adj 文件接口,但是沒(méi)有使用/proc//oom_scoreLMK僅根據(jù)oom_score_adj值的大小選擇殺進(jìn)程,而不會(huì)考慮進(jìn)程本身占用內(nèi)存的大小。apk進(jìn)程的oom_score_adj的值由AMS根據(jù)apk的生命周期和其他一些因素進(jìn)行設(shè)置,會(huì)動(dòng)態(tài)變。apk進(jìn)程的oom_score_adj都大于等于0native進(jìn)程的oom_score_adj的值由rc文件設(shè)置或者繼承自父進(jìn)程,一般都是靜態(tài)的,不會(huì)變化,其值一般都小于0。很多重要的系統(tǒng)進(jìn)程的oom_score_adj值為 -1000,在oom killer的情況下也免殺。LMK默認(rèn)只管理oom_score_adj大于等于0的進(jìn)程,所以只能殺死apk進(jìn)程。 LMK的優(yōu)點(diǎn)是,1.它在系統(tǒng)內(nèi)存開(kāi)始緊張時(shí)就開(kāi)始?xì)⑦M(jìn)程,而不是拖到最后一刻一點(diǎn)內(nèi)存都沒(méi)有了才去殺進(jìn)程,2.安卓framework對(duì)apk的運(yùn)行狀態(tài)很了解,知道哪個(gè)進(jìn)程重要不重要,哪個(gè)進(jìn)程處于什么狀態(tài),能更針對(duì)性的選擇殺哪些進(jìn)程。LMKOOM Killer 共同構(gòu)成了系統(tǒng)內(nèi)存不足的兩道防線,LMK在前,內(nèi)存有些不足時(shí)就殺進(jìn)程,OOM killer在后,作為最后一道屏障,作最后的努力去回收內(nèi)存。

6.總結(jié)

Linux內(nèi)存管理是一門龐大的學(xué)問(wèn),內(nèi)存回收作為其中的一部分也是十分復(fù)雜的,我們今天給大家大概介紹了內(nèi)核的內(nèi)存回收概覽,并詳細(xì)的介紹了OOM Killer機(jī)制,也算是拋磚引玉讓大家對(duì)內(nèi)存回收有個(gè)初步的認(rèn)識(shí)。另外如果你在工作中遇到你的進(jìn)程莫名其妙掛掉了,如果你能在內(nèi)核log中找到OOM Killerlog的話(搜索 out of memory 關(guān)鍵字并過(guò)濾你的進(jìn)程名),那么你就可以快速的斷定你的是因?yàn)橄到y(tǒng)內(nèi)存不足了,而且你的進(jìn)程占用物理內(nèi)存最多,所以被殺了,此時(shí)你就有很大的理由懷疑自己的進(jìn)程內(nèi)存泄漏了,就可以開(kāi)始進(jìn)行內(nèi)存相關(guān)問(wèn)題的排查了。

原文標(biāo)題:Linux OOM 基本原理解析

文章出處:【微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11226

    瀏覽量

    208920
  • 計(jì)算機(jī)
    +關(guān)注

    關(guān)注

    19

    文章

    7419

    瀏覽量

    87712
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2999

    瀏覽量

    73882

原文標(biāo)題:Linux OOM 基本原理解析

文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux內(nèi)存管理是什么,Linux內(nèi)存管理詳解

    Linux內(nèi)存管理 Linux內(nèi)存管理是一個(gè)非常復(fù)雜的過(guò)程,主要分成兩個(gè)大的部分:內(nèi)核的
    的頭像 發(fā)表于 05-11 17:54 ?5980次閱讀
    <b class='flag-5'>Linux</b>的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>是什么,<b class='flag-5'>Linux</b>的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>詳解

    Linux內(nèi)存管理之頁(yè)面回收

    請(qǐng)求調(diào)頁(yè)機(jī)制,只要用戶態(tài)進(jìn)程繼續(xù)執(zhí)行,他們就能獲得頁(yè)框,然而,請(qǐng)求調(diào)頁(yè)沒(méi)有辦法強(qiáng)制進(jìn)程釋放不再使用的頁(yè)框。因此,遲早所有空閑內(nèi)存將被分配給進(jìn)程和高速緩存,Linux內(nèi)核的頁(yè)面回收算法(
    發(fā)表于 05-19 14:09 ?1063次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>之頁(yè)面<b class='flag-5'>回收</b>

    關(guān)于Linux內(nèi)存管理的詳細(xì)介紹

    Linux內(nèi)存管理是指對(duì)系統(tǒng)內(nèi)存分配、釋放、映射、管理、交換、壓縮等一系列操作的
    發(fā)表于 03-06 09:28 ?1060次閱讀

    Linux內(nèi)核的內(nèi)存管理詳解

    內(nèi)存管理的主要工作就是對(duì)物理內(nèi)存進(jìn)行組織,然后對(duì)物理內(nèi)存分配回收。但是
    發(fā)表于 08-31 14:46 ?740次閱讀
    <b class='flag-5'>Linux</b>內(nèi)核的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>詳解

    Linux內(nèi)核內(nèi)存管理架構(gòu)解析

    內(nèi)存管理子系統(tǒng)可能是linux內(nèi)核中最為復(fù)雜的一個(gè)子系統(tǒng),其支持的功能需求眾多,如頁(yè)面映射、頁(yè)面分配、頁(yè)面回收、頁(yè)面交換、冷熱頁(yè)面、緊急頁(yè)面
    的頭像 發(fā)表于 01-04 09:24 ?635次閱讀
    <b class='flag-5'>Linux</b>內(nèi)核<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>架構(gòu)解析

    Linux內(nèi)核內(nèi)存管理之ZONE內(nèi)存分配

    內(nèi)核中使用ZONE分配器滿足內(nèi)存分配請(qǐng)求。該分配器必須具有足夠的空閑頁(yè)幀,以便滿足各種內(nèi)存大小請(qǐng)求。
    的頭像 發(fā)表于 02-21 09:29 ?865次閱讀

    Linux內(nèi)存系統(tǒng): Linux 內(nèi)存分配算法

    內(nèi)存管理算法:對(duì)討厭自己管理內(nèi)存的人來(lái)說(shuō)是天賜的禮物。1、內(nèi)存碎片1) 基本原理· 產(chǎn)生原因:內(nèi)存
    發(fā)表于 08-24 07:44

    Linux內(nèi)存管理中的Slab分配機(jī)制

    早期Linux內(nèi)存分配機(jī)制采用伙伴算法, 當(dāng)請(qǐng)求分配內(nèi)存大小為幾十個(gè)字節(jié)或幾百個(gè)字節(jié)時(shí)會(huì)產(chǎn)生內(nèi)存
    發(fā)表于 04-24 10:49 ?11次下載

    linux內(nèi)存管理機(jī)制淺析

    本內(nèi)容介紹了arm linux內(nèi)存管理機(jī)制,詳細(xì)說(shuō)明了linux內(nèi)核內(nèi)存管理,
    發(fā)表于 12-19 14:09 ?73次下載
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>機(jī)制淺析

    LINUX源代碼分析-內(nèi)存管理

    操作系統(tǒng)管理系統(tǒng)所有的物理空間, 現(xiàn)代大多數(shù)操作系統(tǒng)都采取多級(jí)管理, 即頁(yè)面級(jí)分配與內(nèi)核內(nèi)存分配。就LI
    發(fā)表于 12-19 16:38 ?102次下載
    <b class='flag-5'>LINUX</b>源代碼分析-<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>

    linux內(nèi)存管理

    linux內(nèi)存管理
    發(fā)表于 10-24 11:12 ?3次下載
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>

    基于Linux內(nèi)存管理與Android內(nèi)存分配機(jī)制

    Android采取了一種有別于Linux的進(jìn)程管理策略,有別于Linux的在進(jìn)程活動(dòng)停止后就結(jié)束該進(jìn)程,Android把這些進(jìn)程都保留在內(nèi)存中,直到系統(tǒng)需要更多
    的頭像 發(fā)表于 03-30 14:52 ?6210次閱讀

    STM32內(nèi)存管理

    內(nèi)存管理詳解1、介紹內(nèi)存管理,是指軟件運(yùn)行時(shí)對(duì)計(jì)算機(jī)內(nèi)存資源的分配和使用的技術(shù)。其最主要的目的是
    發(fā)表于 12-24 19:37 ?13次下載
    STM32<b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>

    Linux內(nèi)核引導(dǎo)內(nèi)存分配器的原理

    Linux內(nèi)核引導(dǎo)內(nèi)存分配器使用的是伙伴系統(tǒng)算法。這種算法是一種用于動(dòng)態(tài)內(nèi)存分配的高效算法,它將內(nèi)存
    發(fā)表于 04-03 14:52 ?393次閱讀

    Linux 內(nèi)存管理總結(jié)

    一、Linux內(nèi)存管理概述 Linux內(nèi)存管理是指對(duì)系統(tǒng)內(nèi)存
    的頭像 發(fā)表于 11-10 14:58 ?499次閱讀
    <b class='flag-5'>Linux</b> <b class='flag-5'>內(nèi)存</b><b class='flag-5'>管理</b>總結(jié)