操作系統(tǒng)內(nèi)存管理基礎(chǔ)

1. 內(nèi)存地址映射

Android 整體架構(gòu)和實(shí)現(xiàn)機(jī)制如Binder、音頻系統(tǒng)钝满、GUI系統(tǒng)都與內(nèi)存管理息息相關(guān)憾赁。內(nèi)存管理主要涉及到以下幾個(gè)方面:

虛擬內(nèi)存

計(jì)算機(jī)早期构挤,內(nèi)存資源是有限的,有些電腦的內(nèi)存很小凯砍,可能無(wú)法將程序完整的加載到內(nèi)存中運(yùn)行箱硕。

于是想了一個(gè)辦法,將外部存儲(chǔ)器的部分空間作為內(nèi)存的擴(kuò)展悟衩,然后由系統(tǒng)按照一定的算法將暫時(shí)不用的數(shù)據(jù)放在磁盤(pán)中剧罩,用到時(shí)去磁盤(pán)中加載到內(nèi)存。

按需加載的思想局待,而且這一切步驟都由系統(tǒng)內(nèi)核自動(dòng)完成斑响,對(duì)用戶完全透明菱属。

主要涉及到三種地址空間概念:

  1. 邏輯地址

    也稱相對(duì)地址,是程序在編譯后產(chǎn)生的地址舰罚。分為兩部分纽门,段選擇子和偏移。這和硬件內(nèi)存的設(shè)計(jì)思路是相通的营罢,有片選信號(hào)和偏移組成赏陵。程序最終是運(yùn)行在物理內(nèi)存上的,對(duì)于有段頁(yè)式內(nèi)存管理的機(jī)器饲漾,由邏輯地址轉(zhuǎn)換到物理內(nèi)存還需要經(jīng)過(guò)兩個(gè)步驟蝙搔。

    【邏輯地址】---分段轉(zhuǎn)換機(jī)制--->【先線性地址】---- 分頁(yè)機(jī)制 ---> 【物理地址】

    然而實(shí)際上Linux只實(shí)現(xiàn)了分頁(yè)機(jī)制,沒(méi)有分段考传。

  2. 線性地址

    邏輯地址轉(zhuǎn)換后的產(chǎn)物吃型,邏輯地址的段選擇子中包含了段描述符,描述符記錄了線性地址的基地址僚楞∏谕恚基地址 + 線性偏移就得到了線性地址。

  3. 物理地址

    是真實(shí)的物理內(nèi)存地址泉褐,物理地址是以固定大小的內(nèi)存塊為基本的操作對(duì)象赐写,通常是4KB,稱之為頁(yè)膜赃。再說(shuō)回前面的挺邀,對(duì)于虛擬內(nèi)存,訪問(wèn)時(shí)跳座,沒(méi)有物理內(nèi)存中訪問(wèn)到的頁(yè)端铛,被認(rèn)為是缺失頁(yè),此時(shí)會(huì)觸發(fā)中斷躺坟,由操作系統(tǒng)去磁盤(pán)中置換相應(yīng)的頁(yè)到物理內(nèi)存中沦补。就此完成了虛擬內(nèi)存的功能。

內(nèi)存分配與回收

對(duì)于操作系統(tǒng)而言咪橙,內(nèi)存管理是其中重要組成部分夕膀。并且操作系統(tǒng)做的了,硬件無(wú)關(guān)性美侦,且能動(dòng)態(tài)分配和回收內(nèi)存产舞。對(duì)Android來(lái)說(shuō),針對(duì)Native層更多的要依靠開(kāi)發(fā)者的人工管理菠剩,當(dāng)然C++的智能指針是一個(gè)突破點(diǎn)易猫。Java層的內(nèi)存則是由JVM通過(guò)一整套系統(tǒng)的回收算法,統(tǒng)一管理具壮。但是Java代碼的編寫(xiě)依然需要嚴(yán)格的遵循使用規(guī)范准颓,否則也可能引起內(nèi)存泄漏等問(wèn)題哈蝇。

2. mmap(Memory Map)通信

mmap可將某個(gè)設(shè)備或文件映射到應(yīng)用進(jìn)程的內(nèi)存空間,實(shí)現(xiàn)進(jìn)程的直接操作攘已,節(jié)省了R/W的拷貝過(guò)程炮赦,提高了通信效率。比如Binder通信機(jī)制样勃,在驅(qū)動(dòng)層便是使用了mmap吠勘,將parcel對(duì)象映射到共同的地址,以完成進(jìn)程間通信峡眶。

具體使用方式可以參見(jiàn)man文檔

man mmap

3. 寫(xiě)時(shí)拷貝技術(shù)(Copy On Write)

這是Linux中非常關(guān)鍵的技術(shù)剧防,設(shè)計(jì)思想還是按需創(chuàng)建,多個(gè)對(duì)象起始時(shí)辫樱,共享某個(gè)資源峭拘,這時(shí)候這些對(duì)象不會(huì)擁有資源的拷貝,直到這個(gè)資源被某個(gè)對(duì)象修改搏熄。典型的使用場(chǎng)景是我們fork() 進(jìn)程時(shí)棚唆,如果不調(diào)用execve() 暇赤,都會(huì)共享父進(jìn)程的資源心例,調(diào)用之后子進(jìn)程才會(huì)擁有自己的資源空間。

4. 內(nèi)存回收機(jī)制 Android Memory Killer

Android 系統(tǒng)在應(yīng)用進(jìn)程資源并不會(huì)立即被清除鞋囊,這也就導(dǎo)致了一個(gè)問(wèn)題止后,內(nèi)存占用將成倍增加。怎么解決這個(gè)問(wèn)題溜腐?

我們知道Linux在內(nèi)核態(tài)實(shí)現(xiàn)自己的內(nèi)存監(jiān)控機(jī)制译株,OOMKiller。當(dāng)系統(tǒng)內(nèi)存占用達(dá)到臨界值時(shí)挺益,OOMKiller就按照優(yōu)先級(jí)從低到高按照優(yōu)先級(jí)順序殺掉進(jìn)程歉糜。

影響因素有:

  1. 進(jìn)程消耗的內(nèi)存 |
  2. CPU占用時(shí)間 | ------> oom_score //proc/<PID>/oom_score
  3. oom_adj |

Android在OOMKiller基礎(chǔ)上實(shí)現(xiàn)進(jìn)一步的細(xì)分,并專門(mén)開(kāi)發(fā)了一個(gè)驅(qū)動(dòng)命名為 Low Memory Killer ( /drivers/staging/android/:computer_mouse: Lowmemorykiller.c )

4.1 Low Memory Killer 驅(qū)動(dòng)加載過(guò)程

static struct shrinker lowmem_shrinker = {
    .scan_objects = lowmem_scan,
    .count_objects = lowmem_count,
    .seeks = DEFAULT_SEEKS * 16
};


static int __init lowmem_init(void)
{
    // 向內(nèi)核線程kswapd 注冊(cè)shrinker 監(jiān)聽(tīng)回調(diào)
    // 當(dāng)內(nèi)存頁(yè)低于一定閾值望众,就會(huì)回調(diào)lowmem_shrinker結(jié)構(gòu)體里對(duì)應(yīng)的函數(shù)
    register_shrinker(&lowmem_shrinker);
    return 0;
}

static void __exit lowmem_exit(void)
{
    unregister_shrinker(&lowmem_shrinker);
}
 //定義驅(qū)動(dòng)加載和卸載時(shí)候的處理函數(shù)   lowmem_init  
module_init(lowmem_init)  
module_exit(lowmem_exit)

驅(qū)動(dòng)加載時(shí)候匪补,注冊(cè)了處理函數(shù)lowmem_init() ,處理函數(shù)運(yùn)行時(shí)向內(nèi)核線程注冊(cè)lowmem_shrinker結(jié)構(gòu)體指針烂翰,我們可以看到 這個(gè)結(jié)構(gòu)體中包含了兩個(gè)函數(shù)指針夯缺,分別是lowmem_scanlowmem_count ,這兩個(gè)函數(shù)會(huì)在內(nèi)核發(fā)現(xiàn)系統(tǒng)中空閑的頁(yè)數(shù)低于閾值時(shí)候被回調(diào)甘耿。其中lowmem_scan 承載了驅(qū)動(dòng)的關(guān)鍵任務(wù)檢測(cè)回收踊兜。

4.2 lowmem_scan 檢測(cè)和回收邏輯

// 代碼比較長(zhǎng)  這里篩檢了主要邏輯
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
    // 待檢測(cè)的進(jìn)程
    struct task_struct *tsk;
    // 需要回收的進(jìn)程
    struct task_struct *selected = NULL;
    // 根據(jù)當(dāng)前狀態(tài)初始化必要的變量 
    int array_size = ARRAY_SIZE(lowmem_adj);
    int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
    int other_file = global_page_state(NR_FILE_PAGES) -
                        global_page_state(NR_SHMEM) -
                        total_swapcache_pages();

    //1. RCU(Read-Copy Update)上鎖
    rcu_read_lock();
    //2.遍歷所有進(jìn)程
    for_each_process(tsk) {
        struct task_struct *p;
        short oom_score_adj;

        if (tsk->flags & PF_KTHREAD)
            continue;

        p = find_lock_task_mm(tsk);
        if (!p)
            continue;
        //3.獲取p的oom_score_adj 如果比最小閾值  還小, 那么暫時(shí)還不用殺佳恬;繼續(xù)循環(huán)
        oom_score_adj = p->signal->oom_score_adj;
        if (oom_score_adj < min_score_adj) {
            task_unlock(p);
            continue;
        }
        tasksize = get_mm_rss(p->mm);
        task_unlock(p);
        //4.RSS <= 0  實(shí)際物理內(nèi)存<=0; 不占內(nèi)存 也暫時(shí)不殺
        if (tasksize <= 0)
            continue;
      
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)
                continue;
        }
        //5.以上情況都沒(méi)留住  那么鎖定目標(biāo) 
        selected = p;
        selected_tasksize = tasksize;
        selected_oom_score_adj = oom_score_adj;
        
    }
    
    if (selected) {
        set_tsk_thread_flag(selected, TIF_MEMDIE);
        //6.發(fā)送 -9 SIGNAL 消息殺掉進(jìn)程 
        send_sig(SIGKILL, selected, 0);
    }
    //7.RCU(Read-Copy Update)解鎖
    rcu_read_unlock();
    return rem;
}

核心就是一個(gè)遍歷進(jìn)程的循環(huán)捏境,將進(jìn)程的內(nèi)存占用狀態(tài)和oom_score_adj分值進(jìn)行相應(yīng)的比較于游,當(dāng)前系統(tǒng)狀態(tài)下,分?jǐn)?shù)夠高就會(huì)被殺掉垫言。

4.3 adj的維護(hù)

oom_score_adj這個(gè)值我們看到是從進(jìn)程內(nèi)部取的曙砂,那么adj誰(shuí)維護(hù)的呢?

在ActivityManagerService中有一個(gè)對(duì)象

// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
OomAdjuster mOomAdjuster;

看名字是不是很有感骏掀? 沒(méi)錯(cuò)就是這個(gè)類中維護(hù)了adj的狀態(tài)鸠澈,具體這里不分析了有興趣可以取對(duì)應(yīng)目錄查看。

名稱 取值 解釋
UNKNOWN_ADJ 16 一般指將要會(huì)緩存進(jìn)程截驮,無(wú)法獲取確定值
CACHED_APP_MAX_ADJ 15 不可見(jiàn)進(jìn)程的adj最大值
CACHED_APP_MIN_ADJ 9 不可見(jiàn)進(jìn)程的adj最小值
SERVICE_B_AD 8 B List中的Service(較老的笑陈、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一個(gè)App的進(jìn)程(往往通過(guò)按返回鍵)
HOME_APP_ADJ 6 Home進(jìn)程
SERVICE_ADJ 5 服務(wù)進(jìn)程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后臺(tái)的重量級(jí)進(jìn)程葵袭,system/rootdir/init.rc文件中設(shè)置
BACKUP_APP_ADJ 3 備份進(jìn)程
PERCEPTIBLE_APP_ADJ 2 可感知進(jìn)程涵妥,比如后臺(tái)音樂(lè)播放
VISIBLE_APP_ADJ 1 可見(jiàn)進(jìn)程(Visible process)
FOREGROUND_APP_ADJ 0 前臺(tái)進(jìn)程(Foreground process
PERSISTENT_SERVICE_ADJ -11 關(guān)聯(lián)著系統(tǒng)或persistent進(jìn)程
PERSISTENT_PROC_ADJ -12 系統(tǒng)persistent進(jìn)程,比如telephony
SYSTEM_ADJ -16 系統(tǒng)進(jìn)程
NATIVE_ADJ -17 native進(jìn)程(不被系統(tǒng)管理)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坡锡,一起剝皮案震驚了整個(gè)濱河市蓬网,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹉勒,老刑警劉巖帆锋,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異禽额,居然都是意外死亡锯厢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)脯倒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)实辑,“玉大人,你說(shuō)我怎么就攤上這事藻丢〖羟耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵悠反,是天一觀的道長(zhǎng)残黑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)问慎,這世上最難降的妖魔是什么萍摊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮如叼,結(jié)果婚禮上冰木,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好踊沸,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布歇终。 她就那樣靜靜地躺著,像睡著了一般逼龟。 火紅的嫁衣襯著肌膚如雪评凝。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,337評(píng)論 1 310
  • 那天腺律,我揣著相機(jī)與錄音奕短,去河邊找鬼。 笑死匀钧,一個(gè)胖子當(dāng)著我的面吹牛翎碑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播之斯,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼日杈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了佑刷?” 一聲冷哼從身側(cè)響起莉擒,我...
    開(kāi)封第一講書(shū)人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘫絮,沒(méi)想到半個(gè)月后涨冀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡檀何,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年蝇裤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片频鉴。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恋拍,靈堂內(nèi)的尸體忽然破棺而出垛孔,到底是詐尸還是另有隱情,我是刑警寧澤施敢,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布周荐,位于F島的核電站,受9級(jí)特大地震影響僵娃,放射性物質(zhì)發(fā)生泄漏概作。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一默怨、第九天 我趴在偏房一處隱蔽的房頂上張望讯榕。 院中可真熱鬧,春花似錦、人聲如沸愚屁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)霎槐。三九已至送浊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丘跌,已是汗流浹背袭景。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闭树,地道東北人浴讯。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蔼啦,于是被迫代替她去往敵國(guó)和親榆纽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359