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ì)用戶完全透明菱属。
主要涉及到三種地址空間概念:
-
邏輯地址
也稱相對(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)有分段考传。
-
線性地址
邏輯地址轉(zhuǎn)換后的產(chǎn)物吃型,邏輯地址的段選擇子中包含了段描述符,描述符記錄了線性地址的基地址僚楞∏谕恚基地址 + 線性偏移就得到了線性地址。
-
物理地址
是真實(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)程歉糜。
影響因素有:
- 進(jìn)程消耗的內(nèi)存 |
- CPU占用時(shí)間 | ------> oom_score //proc/<PID>/oom_score
- 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_scan
和 lowmem_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)管理) |