細(xì)讀《深入理解 Android 內(nèi)核設(shè)計(jì)思想》(二)內(nèi)存管理

對(duì)冗余挑揀重點(diǎn)崔兴,對(duì)重點(diǎn)深入補(bǔ)充,輸出結(jié)構(gòu)清晰的精簡(jiǎn)版

1. 操作系統(tǒng)內(nèi)存管理基礎(chǔ)
虛擬內(nèi)存
內(nèi)存分配與回收
mmap
Copy on Write
2. Android 內(nèi)存管理
Low Memory Killer
Ashmem 驅(qū)動(dòng)
MemoryFile 原理
3. 總結(jié)

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

不論什么操作系統(tǒng),內(nèi)存管理都是絕對(duì)的重點(diǎn)和難點(diǎn)敲茄。內(nèi)存管理旨在為系統(tǒng)中所有 Task 提供穩(wěn)定可靠的內(nèi)存分配位谋、釋放和保護(hù)機(jī)制。你可能會(huì)疑問堰燎,學(xué)習(xí) Android 系統(tǒng)有必要了解 Linux Kernel 的內(nèi)存管理機(jī)制嗎掏父?

是的!不論是 Android 的音頻系統(tǒng)秆剪、GUI 系統(tǒng)赊淑,還是 Binder 的實(shí)現(xiàn)機(jī)理等,都是和內(nèi)存管理息息相關(guān)的仅讽。

虛擬內(nèi)存

虛擬內(nèi)存就是當(dāng)內(nèi)存資源不足時(shí)陶缺,借用硬盤中的一部分的空間,充當(dāng)內(nèi)存使用洁灵。系統(tǒng)會(huì)挑選優(yōu)先級(jí)低的內(nèi)存數(shù)據(jù)放入硬盤饱岸,后續(xù)若要用到硬盤中的數(shù)據(jù),系統(tǒng)會(huì)產(chǎn)生一次缺頁中斷徽千,然后把數(shù)據(jù)交換回內(nèi)存中苫费。

要理解虛擬內(nèi)存機(jī)制,就要理解三種地址空間双抽,分別是邏輯地址百框、線性地址和物理地址:

1.邏輯地址(Logical Address)
邏輯地址是程序編譯后產(chǎn)生的地址,也稱為相對(duì)地址牍汹,由兩部分組成:

  • 段選擇子(Segment Selector):描述邏輯地址所處的段
  • Offset:描述所在段內(nèi)的偏移值

2.線性地址(Linear Address)
線性地址是由邏輯地址經(jīng)過分段機(jī)制轉(zhuǎn)換后得到的铐维。

大致轉(zhuǎn)換過程為:通過段選擇子確定段的基地址,然后結(jié)合 Offset 得到線性地址慎菲。

3.物理地址(Physical Address)
物理地址就是指機(jī)器真實(shí)的物理內(nèi)存地址嫁蛇,任何操作系統(tǒng),最終都要通過物理地址來訪問內(nèi)存钧嘶。

若系統(tǒng)開啟了分頁機(jī)制棠众,則在得到線性地址后需要通過分頁機(jī)制轉(zhuǎn)換后琳疏,才能得到物理地址有决。

簡(jiǎn)單來說,由邏輯地址得到物理地址過程如下:

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

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

內(nèi)存的分配與回收是操作系統(tǒng)的重要組成部分空盼,需要解決的核心問題包括:

  • 操作系統(tǒng)應(yīng)保證應(yīng)用程序的硬件無關(guān)性书幕,硬件差異不能體現(xiàn)在應(yīng)用程序上
  • 內(nèi)存劃分的區(qū)域、分配粒度揽趾、最小單位台汇,管理區(qū)分已使用和未使用的內(nèi)存,回收等等
  • 優(yōu)化內(nèi)存碎片,考慮整體機(jī)制的高效性

mmap

mmap(Memory Map) 可以將某個(gè)設(shè)備或文件映射到應(yīng)用進(jìn)程的內(nèi)存空間中苟呐,這樣應(yīng)用程序訪問這塊內(nèi)存痒芝,相當(dāng)于直接對(duì)設(shè)備/文件讀寫,不再需要 read牵素、write 等 IO 操作严衬。

mmap 函數(shù)如下:

//映射成功返回0,否則返回錯(cuò)誤碼
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
  • addr:指文件/設(shè)備應(yīng)該映射到進(jìn)程空間的哪個(gè)起始地址
  • len:指被映射到進(jìn)程空間的內(nèi)存塊大小
  • prot:指定被映射內(nèi)存的訪問權(quán)限笆呆,包括 PROT_READ(可讀)请琳、PROT_WRITE(可寫) 等
  • flags:指定程序?qū)?nèi)存塊所做改變?cè)斐傻挠绊懀?MAP_SHARED(保存到文件) 等
  • fd:被映射到進(jìn)程空間的文件描述符
  • offset:指定從文件的哪一部分開始映射

源碼見 http://androidxref.com/9.0.0_r3/xref/bionic/libc/bionic/mmap.cpp 赠幕。mmap 可用于跨進(jìn)程通信俄精,Linux Kernel 和 Android 中就頻繁的用到了這個(gè)函數(shù),比如 Android 的 Binder 驅(qū)動(dòng)榕堰,下面分析 MemoryFile 原理時(shí)還會(huì)提到這個(gè)函數(shù)竖慧。

Copy on Write

Copy on Write(寫時(shí)拷貝) 是指如果有多個(gè)調(diào)用者要請(qǐng)求同一資源,他們會(huì)獲取到相同的指向這一資源的指針局冰,直到某個(gè)調(diào)用者需修改資源時(shí)测蘑,系統(tǒng)才會(huì)復(fù)制一份副本給該調(diào)用者,而其他調(diào)用者仍使用最初的資源康二。

如果調(diào)用者不需要修改資源碳胳,就不會(huì)建立副本,多個(gè)調(diào)用者共享讀取同一份資源沫勿。

Linux 的 fork() 函數(shù)就是 Copy on Write 的挨约,實(shí)際開銷很小,主要是給子進(jìn)程創(chuàng)建進(jìn)程描述符等产雹,并且推遲甚至免除了數(shù)據(jù)拷貝操作诫惭。比如 fork() 后子進(jìn)程需立即調(diào)用 exec() 裝載新程序到進(jìn)程的內(nèi)存空間,即不需要父進(jìn)程的任何數(shù)據(jù)蔓挖,這種情況 Copy on Write 技術(shù)就避免了不必要的數(shù)據(jù)拷貝夕土,從而提升了運(yùn)行速度。

Android 內(nèi)存管理

Low Memory Killer

Linux Kernel 有自己的內(nèi)存監(jiān)控機(jī)制瘟判,即 OOMKiller怨绣。當(dāng)系統(tǒng)的可用內(nèi)存達(dá)到臨界值時(shí),OOMKiller 就會(huì)按照優(yōu)先級(jí)從低到高殺掉進(jìn)程拷获。優(yōu)先級(jí)該如何衡量呢篮撑?OOMKiller 會(huì)綜合進(jìn)程當(dāng)前消耗內(nèi)存、進(jìn)程占用 CPU 時(shí)間匆瓜、進(jìn)程類型等因素赢笨,對(duì)進(jìn)程實(shí)時(shí)評(píng)分未蝌。分值存儲(chǔ)在 /proc/{PID}/oom_score 中,可通過 cat 命令查看茧妒。分值越低的進(jìn)程萧吠,優(yōu)先級(jí)越高,被殺死的概率越小桐筏。

基于 Linux 內(nèi)核 OOMKiller 的核心思想怎憋,Android 系統(tǒng)拓展出了自己的內(nèi)存監(jiān)控體系,相比 Linux 達(dá)到臨界值才觸發(fā)九昧,Android 實(shí)現(xiàn)了不同梯級(jí)的 Killer绊袋。Android 系統(tǒng)為此開發(fā)了專門的驅(qū)動(dòng),名為 Low Memory Killer铸鹰,源碼在內(nèi)核的 /drivers/staging/android/Lowmemorykiller.c 中癌别。

Lowmemorykiller.c 中有如下定義:

static int lowmem_adj[6] = {0, 1, 6, 12};
static int lowmem_adj_size = 4; //頁大小
static size_t lowmem_minfree[6] = { //元素使用時(shí)以 lowmem_adj_size 為單位
    3 * 512, //6MB
    2 * 1024, //8MB
    4 * 1024, //16MB
    16 * 1024,//64MB
};

lowmem_minfree 定義了可用內(nèi)存容量對(duì)應(yīng)的不同梯級(jí)蹋笼。lowmem_adj 與 lowmem_minfree 中的梯級(jí)一一對(duì)應(yīng)展姐,表示處于某梯級(jí)時(shí)需要被處理的 adj 值。adj 值用來描述進(jìn)程的優(yōu)先級(jí)剖毯,取值范圍為 -17~15圾笨,數(shù)字越小表示進(jìn)程優(yōu)先級(jí)越高,被殺死的概率越小逊谋。

比如當(dāng)可用內(nèi)存低于 64MB 時(shí)擂达,即 lowmem_minfree 第 4 梯級(jí),對(duì)應(yīng)于 lowmem_adj 的 12胶滋,那就會(huì)清理掉優(yōu)先級(jí)低于 12(即 adj>12)的進(jìn)程板鬓。

上面這兩個(gè)數(shù)組中梯級(jí)的定義只是系統(tǒng)的預(yù)定義值,Android 系統(tǒng)還提供了相應(yīng)的文件供我們修改這兩組值究恤,路徑為:

/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree

可以在 init.rc(系統(tǒng)啟動(dòng)時(shí)由 init 進(jìn)程解析的一個(gè)腳本) 中俭令,這樣修改:

write /sys/module/lowmemorykiller/parameters/adj        0, 8
write /sys/module/lowmemorykiller/parameters/minfree 1024, 4096

另外 ActivityManagerService 中有一個(gè) updateOomLevels 方法也是通過修改這兩個(gè)文件來實(shí)現(xiàn)的,AMS 在運(yùn)行時(shí)會(huì)根據(jù)當(dāng)前的系統(tǒng)配置自動(dòng)調(diào)整 adj 和 minfree部宿,以盡可能適配不同的硬件設(shè)備抄腔。

了解了 Low Memory Killer 的梯級(jí)規(guī)則后,來看下 Android 進(jìn)程的 adj 值含義:

ADJ 說明
HIDDEN_APP_MAX_AD = 15 只運(yùn)行了不可見 Activity 的進(jìn)程
HIDDEN_APP_MIN_ADJ = 9 只運(yùn)行了不可見 Activity 的進(jìn)程
SERVICE_B_ADJ = 8 B list of Service
PREVIOUS_APP_ADJ = 7 用戶的上一個(gè)產(chǎn)生交互的進(jìn)程
HOME_APP_ADJ = 6 Launcher 進(jìn)程
SERVICE_ADJ = 5 當(dāng)前運(yùn)行了 application service 的進(jìn)程
BACKUP_APP_ADJ = 4 用于承載 backup 相關(guān)操作的進(jìn)程
HEAVY_WEIGHT_APP_ADJ = 3 重量級(jí)應(yīng)用程序進(jìn)程
PERCEPTIBLE_APP_ADJ = 2 能被用戶感覺但不可見理张,如后臺(tái)運(yùn)行的音樂播放器
VISIBLE_APP_ADJ = 1 有前臺(tái)可見的 Activity
FOREGROUND_APP_ADJ = 0 當(dāng)前正在前臺(tái)運(yùn)行與用戶交互的進(jìn)程
PERSISTENT_PROC_ADJ = -12 Persistent 性質(zhì)的進(jìn)程赫蛇,如 telephony
SYSTEM_ADJ = -16 系統(tǒng)進(jìn)程

除了表格中系統(tǒng)的評(píng)定標(biāo)準(zhǔn),有沒有辦法改變某一進(jìn)程的 adj 值呢涯穷?和修改上面的 adj棍掐、minfree 梯級(jí)類似藏雏,進(jìn)程的 adj 值也可以通過寫文件的方式來修改拷况,路徑為 /proc/{PID}/oom_adj作煌,比如 init.rc 中:

write /proc/1/oom_adj -16

另外還可以在 AndroidManifest.xml 中給 application 添加 "android:persistent=true" 屬性。

Ashmem 驅(qū)動(dòng)

Anonymous Shared Memory 匿名共享內(nèi)存是 Android 特有的內(nèi)存共享機(jī)制赚瘦,它可以將指定的物理內(nèi)存分別映射到各個(gè)進(jìn)程自己的虛擬地址空間中粟誓,從而便捷的實(shí)現(xiàn)進(jìn)程間內(nèi)存共享,Ashmem 的實(shí)現(xiàn)依賴 Ashmem 設(shè)備節(jié)點(diǎn)起意。

怎么理解設(shè)備節(jié)點(diǎn)呢鹰服?Linux 抽象了對(duì)硬件的處理,所有的硬件設(shè)備都可以當(dāng)作普通文件一樣來看待揽咕,設(shè)備節(jié)點(diǎn)文件是設(shè)備驅(qū)動(dòng)的邏輯文件悲酷,其中對(duì)設(shè)備的描述包括文件操作函數(shù)集合,應(yīng)用程序可以通過這些函數(shù)來訪問硬件設(shè)備亲善。

除了磁盤等真正的硬件設(shè)備设易,還可以通過內(nèi)存抽象,使用設(shè)備節(jié)點(diǎn)文件的方式來描述一個(gè)"設(shè)備"并使用它蛹头,Ashmem顿肺、Binder 驅(qū)動(dòng)都是屬于這種內(nèi)存抽象的"設(shè)備"。

介紹 Ashmem 設(shè)備節(jié)點(diǎn)前渣蜗,先了解下 ueventd 進(jìn)程屠尊。ueventd 就是 Android 中負(fù)責(zé)創(chuàng)建和管理設(shè)備節(jié)點(diǎn)的進(jìn)程,創(chuàng)建設(shè)備節(jié)點(diǎn)文件有兩種方式:
1.靜態(tài)節(jié)點(diǎn)文件:以預(yù)先定義的設(shè)備信息為基礎(chǔ)耕拷,當(dāng) ueventd 進(jìn)程啟動(dòng)后讼昆,統(tǒng)一創(chuàng)建設(shè)備節(jié)點(diǎn)文件
2.動(dòng)態(tài)節(jié)點(diǎn)文件:即在系統(tǒng)運(yùn)行中,當(dāng)有設(shè)備插入 USB 端口時(shí)骚烧,ueventd 進(jìn)程就會(huì)接收到這一事件控淡,為插入的設(shè)備動(dòng)態(tài)創(chuàng)建設(shè)備節(jié)點(diǎn)文件

Ashmem 設(shè)備節(jié)點(diǎn)就屬于靜態(tài)節(jié)點(diǎn)文件,創(chuàng)建過程如下:
1.Android 系統(tǒng)啟動(dòng)止潘,解析 init.rc掺炭,啟動(dòng) ueventd 進(jìn)程
2.ueventd 進(jìn)程會(huì)去解析 ueventd.rc,讀取 ashmem 設(shè)備節(jié)點(diǎn)信息到系統(tǒng)中

其中 ueventd.rc 文件格式如下:

/dev/null                 0666   root       root
/dev/zero                 0666   root       root
/dev/random               0666   root       root
/dev/ashmem               0666   root       root
/dev/binder               0666   root       root

可以看到包括 binder凭戴、ashmem 在內(nèi)的一系列設(shè)備節(jié)點(diǎn)信息都會(huì)在這里讀取到系統(tǒng)中涧狮。

隨后 ashmem 會(huì)調(diào)用 ashmem.c 文件的 ashmem_init 進(jìn)行初始化:

static int _init ashmem_init(void){
    int ret;
    ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",sizeof(struct ashmem_area),0,0,NULL); 
    ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",sizeof(struct ashmem_range),0,0,NULL);
    ret = misc_register(&ashmem_misc);
    ...
    return 0;
}

通過 kmem_cache_create() 函數(shù)創(chuàng)建了兩個(gè) cache,后面申請(qǐng)內(nèi)存時(shí)需要用到么夫。對(duì)于 kmem_cache_create() 函數(shù)者冤,書中提及 Slab、Slub档痪、Slob 三種機(jī)制涉枫,這里不再延伸,僅理解: kmem_cache_create() 并沒有真正的分配內(nèi)存腐螟,后續(xù)還要調(diào)用 kmem_cache_alloc() 愿汰。

由于 ashmem 屬于 misc 雜項(xiàng)設(shè)備困后,所以調(diào)用 misc_register(&ashmem_misc) 進(jìn)行設(shè)備注冊(cè)。ashmem_misc 就是 Ashmem 的設(shè)備描述衬廷,定義如下:

static struct miscdevice ashmem_misc = {
    .minor = MISC_DYNAMIC_MINOR, //自動(dòng)分配次設(shè)備號(hào)
    .name = "ashmem", //設(shè)備節(jié)點(diǎn)的名稱
    .fops = &ashmem_fops, //文件操作集合
};

.fops 就是上面提到的"文件操作函數(shù)集合"摇予,即 Ashmem 設(shè)備的操作函數(shù)集,如下

static struct file_operations ashmem_fops = {
    .owner = THIS_MODULE,
    .open = ashmem_open,
    .release = ashmem_release,
    .read = ashmem_read,
    .llseek = ashmem_llseek,
    .mmap = ashmem_mmap,
    .unlocked_ioctl = ashmem_ioctl,
    .compat_ioctl = ashmem_ioctl,
};

其中 ashmem_open吗跋、ashmem_mmap 及 ashmem_ioctl 函數(shù)比較重要侧戴,依次來看:

1.ashmem_open

static int ashmem_open(struct inode *inode, struct file *file){
    struct ashmem_area *asma;
    ...
    asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
    ...
    file->private_data = asma;
    ...
    return 0; //申請(qǐng)成功
}

ashmem_open 主要做了兩個(gè)工作跌宛,1.調(diào)用 kmem_cache_zalloc 方法從 ashmem_area_cachep 分配了一塊內(nèi)存酗宋,這個(gè)方法和 cache 上面都提到過;2.將 ashmem_area 記錄在 file 中 疆拘。

2.ashmem_mmap

static int ashmem_mmap(struct file *file, struct vm_area_struct *vma){
    struct ashmem_area *asma = file->private_data;
    ...
    mutex_lock(&ashmem_mutex);
    ...
    if(!asma->file){
        shmem_file_setup(name, asma->size, vma->vm_flags);
    }
    ...
    shmem_set_file(vma, asma->file);
    ...
    mutex_unlock(&ashmem_mutex);
}

首先拿到在 ashmem_open 函數(shù)中創(chuàng)建的 ashmem_area本缠,然后判斷如果 asma->file 為空,說明這是第一個(gè)訪問該共享內(nèi)存的進(jìn)程入问,調(diào)用 shmem_file_setup() 函數(shù)在 tmpfs 中創(chuàng)建一個(gè)臨時(shí)文件丹锹,用于進(jìn)程間的內(nèi)存共享;如果 asma->file 不為空芬失,直接調(diào)用 shmem_set_file 進(jìn)行內(nèi)存映射楣黍。

3.ashmem_ioctl

static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
    struct ashmem_area * asma = file->private_data;
    switch(cmd){
    case ASHMEM_SET_NAME://設(shè)置名稱
    set_name(asma, (void __user *) arg);
    break;
    case ASHMEM_GET_NAME://獲取名稱
    get_name(asma, (void __user *) arg);
    break;
    case ASHMEM_SET_NAME://設(shè)置大小
    if(!asma->file){
        asma->size = (size_t) arg;
    }
    break;
    ...
    }
}

ashmem_ioctl 即根據(jù) ioctl 命令做相應(yīng)的操作,設(shè)置或獲取 size棱烂、名稱等租漂。

MemoryFile 原理

書中通過 MemoryDealer 講解了 Ashmem 示例,觸類旁通颊糜,我來分析一下 Ashmem 的另一個(gè)應(yīng)用示例:MemoryFile哩治。MemoryFile 是 Java 層對(duì) Ashmem 的一個(gè)封裝,使用方法大致如下:

進(jìn)程 A 中申請(qǐng)一塊共享內(nèi)存寫入數(shù)據(jù)衬鱼,并準(zhǔn)備好文件描述符:

MemoryFile memoryFile = new MemoryFile(name, size);
memoryFile.getOutputStream().write(data);
Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(des);

進(jìn)程 B 中通過 binder 拿到 A 進(jìn)程中準(zhǔn)備好的文件描述符业筏,然后直接讀取數(shù)據(jù):

FileDescriptor descriptor = pfd.getFileDescriptor();
FileInputStream fileInputStream = new FileInputStream(descriptor);
fileInputStream.read(data);

使用起來和文件讀寫一樣很簡(jiǎn)單,如果不了解 Ashmem 機(jī)制鸟赫,也就只能停留在僅會(huì)使用的淺顯層面了∷馀郑現(xiàn)在有了 Ashmem 驅(qū)動(dòng)知識(shí)的鋪墊,來看 MemoryFile 是怎么從 Java API 調(diào)用到 Ashmem 驅(qū)動(dòng)函數(shù)的抛蚤,先來看 MemoryFile 的構(gòu)造函數(shù):

public MemoryFile(String name, int length) throws IOException {
    try {
        mSharedMemory = SharedMemory.create(name, length);
        mMapping = mSharedMemory.mapReadWrite();
    } catch (ErrnoException ex) {
        ex.rethrowAsIOException();
    }
}

可以看到構(gòu)造 MemoryFile 時(shí)通過 SharedMemory create 方法申請(qǐng)了一塊匿名共享內(nèi)存台谢,SharedMemory create 方法中調(diào)用了 nCreate native 方法:

private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;

對(duì)應(yīng)的 native 實(shí)現(xiàn)在 android_os_SharedMemory.cpp 中,源碼見 http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/jni/android_os_SharedMemory.cpp 岁经,具體 native 實(shí)現(xiàn)如下:

static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) {
    const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;
    int fd = ashmem_create_region(name, size); //創(chuàng)建匿名共享內(nèi)存
    ...
    return jniCreateFileDescriptor(env, fd);
}

ashmem_create_region 方法的對(duì)應(yīng)實(shí)現(xiàn)在 ashmem-dev.cpp 中朋沮,源碼見 http://androidxref.com/9.0.0_r3/xref/system/core/libcutils/ashmem-dev.cpp#ashmem_create_region ,其中 ashmem_create_region 的后續(xù)調(diào)用鏈如下:

#define ASHMEM_DEVICE "/dev/ashmem" //Ashmem 設(shè)備驅(qū)動(dòng)

int ashmem_create_region(const char *name, size_t size){
    int ret, save_errno;
    int fd = __ashmem_open(); //創(chuàng)建匿名共享內(nèi)存
    if (fd < 0) {
        return fd;
    }
    if (name) {
        char buf[ASHMEM_NAME_LEN] = {0};
        strlcpy(buf, name, sizeof(buf));
        ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf)); //設(shè)置 Ashmem 名字
        if (ret < 0) {
            goto error;
        }
    }
}

static int __ashmem_open(){
    int fd;

    pthread_mutex_lock(&__ashmem_lock);
    fd = __ashmem_open_locked(); //創(chuàng)建匿名共享內(nèi)存
    pthread_mutex_unlock(&__ashmem_lock);
    return fd;
}

static int __ashmem_open_locked(){
    int ret;
    struct stat st;
    int fd = TEMP_FAILURE_RETRY(open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC)); //創(chuàng)建匿名共享內(nèi)存
    ...
    return fd;
}

直到 __ashmem_open_locked 方法中調(diào)用到 open(ASHMEM_DEVICE, O_RDWR | O_CLOEXEC) 方法缀壤,終于是到 Ashmem 設(shè)備驅(qū)動(dòng)函數(shù)了樊拓,對(duì)應(yīng)于上面的 ashmem_open 函數(shù)纠亚。另外 ashmem_ioctl 函數(shù)也被調(diào)用到了,即 ioctl(fd, ASHMEM_SET_NAME, buf)骑脱。

通過上面的分析知道 Ashmem 驅(qū)動(dòng)的 ashmem_open 函數(shù)是由 SharedMemory 的 create 方法觸發(fā)一步一步調(diào)用到的,那 ashmem_mmap 驅(qū)動(dòng)函數(shù)是怎么被調(diào)用到的呢苍糠?看 MemoryFile 的構(gòu)造方法叁丧,只可能是通過 SharedMemory 的 mapReadWrite 方法觸發(fā),下面來分析這個(gè)過程:

//android.os.SharedMemory.java
public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
    return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
}

public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
    ...
    long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
    ...
    return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
}

比較關(guān)鍵的是 mFileDescriptor岳瞭,它是執(zhí)行 SharedMemory create 方法申請(qǐng)匿名共享內(nèi)存后拥娄,返回的文件描述符。SharedMemory 中直接調(diào)用了系統(tǒng)的通用 mmap 函數(shù)瞳筏,并沒有對(duì)應(yīng)的 native 實(shí)現(xiàn)稚瘾,那它最終真的能調(diào)用到 ashmem_mmap 函數(shù)嗎? 繼續(xù)來跟蹤 mmap 調(diào)用:

//android.system.Os.java
public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException {
    return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset);
}

//libcore.io.Libcore.java
public final class Libcore {
    private Libcore() { }
    public static Os rawOs = new Linux();
    public static Os os = new BlockGuardOs(rawOs);
}

//libcore.io.Linux.java
public native long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException;

Libcore 中使用 BlockGuardOs 對(duì) Linux 進(jìn)行了一層包裝姚炕,但實(shí)際還是通過 Linux 來執(zhí)行的摊欠,最后調(diào)用到 Linux 中的 native mmap 方法,native 中對(duì)應(yīng)的實(shí)現(xiàn)是 mmap.cpp柱宦,源碼見 http://androidxref.com/9.0.0_r3/xref/bionic/libc/bionic/mmap.cpp#mmap

void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) {
    return mmap64(addr, size, prot, flags, fd, static_cast<off64_t>(offset));
}

到此為止些椒,由 SharedMemory 的 mapReadWrite 方法調(diào)用到 native mmap 函數(shù),傳遞的關(guān)鍵參數(shù)是文件描述符掸刊,后續(xù)它將這樣調(diào)用到 ashmem_mmap:
1.通過 fd 可以找到所屬設(shè)備免糕,也就是 Ashmem 設(shè)備
2.調(diào)用 Ashmem 設(shè)備的 ashmem_mmap 驅(qū)動(dòng)函數(shù)

這屬于 mmap 函數(shù)的內(nèi)部實(shí)現(xiàn),調(diào)用鏈比較復(fù)雜就不再具體展開忧侧,關(guān)鍵代碼如下:

if(file){
    ...
    error = file->f_op->mmap(file,vma);
    ...
}

file 代表文件或設(shè)備驅(qū)動(dòng)石窑,這里指的就是 Ashmem 設(shè)備,f_op 就是 Ashmem 設(shè)備驅(qū)動(dòng)函數(shù)集蚓炬,也就是上文提過的通過 misc_register 注冊(cè)的 Ashmem 設(shè)備描述松逊,至此便是 ashmem_mmap 驅(qū)動(dòng)函數(shù)的調(diào)用過程。

總結(jié)

不知你是否會(huì)覺得本文介紹的虛擬內(nèi)存無用肯夏,起初我有這樣的想法棺棵,作者在原書中是這樣描述的:本小節(jié)為讀者完整地還原了操作系統(tǒng)虛擬地址的概念與轉(zhuǎn)換原理,相信大家會(huì)在后續(xù)** Android 各子系統(tǒng)的學(xué)習(xí)中受益匪淺**熄捍。 這讓我想到前幾天看到的一個(gè)問題 "為什么要分析算法的時(shí)間復(fù)雜度和空間復(fù)雜度烛恤,是因?yàn)楝F(xiàn)在的計(jì)算機(jī)都是馮諾依曼結(jié)構(gòu)嗎?"余耽,基于基礎(chǔ)知識(shí)能有自己的理解和發(fā)散才是可貴的缚柏。

所以謙虛一點(diǎn)別自以為是,對(duì)知識(shí)保持敬畏碟贾、渴望币喧。技術(shù)知識(shí)的價(jià)值不在于是否會(huì)被用到轨域,而在于它能否對(duì)你的技術(shù)體系建設(shè)有幫助,能否讓你對(duì)本質(zhì)有更清晰的認(rèn)知杀餐,能否讓你的上層建筑更牢固干发。如果只盯著自己的那一點(diǎn)墨水坐井觀天,實(shí)力沒隨年齡上漲史翘,遲早會(huì)迎來焦慮的中年危機(jī)枉长,被行業(yè)淘汰。

那本文涉及的知識(shí)點(diǎn)對(duì)上層建筑有什么幫助呢琼讽?比如學(xué)習(xí)了 Ashmem 后必峰,再遇到跨進(jìn)程傳輸大數(shù)據(jù)的問題,是不是更有底氣了呢钻蹬?比如學(xué)習(xí)了 Android 的 Low Memory Killer 機(jī)制原理后吼蚁,才知道應(yīng)用保活本質(zhì)到底是在解決什么問題问欠,相比只是知道從網(wǎng)上搜來的幾個(gè)备未遥活方案,是不是更加胸有成竹呢顺献?比如在閱讀本文涉及的源碼時(shí)發(fā)現(xiàn) mutex_lock 隨處可見术唬,是不是很慶幸自己掌握了本系列第一章中的 進(jìn)程間同步機(jī)制 呢?

鏈接:細(xì)讀《深入理解 Android 內(nèi)核設(shè)計(jì)思想》系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滚澜,一起剝皮案震驚了整個(gè)濱河市粗仓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌设捐,老刑警劉巖借浊,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異萝招,居然都是意外死亡蚂斤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門槐沼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曙蒸,“玉大人,你說我怎么就攤上這事岗钩∨撸” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵兼吓,是天一觀的道長(zhǎng)臂港。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么审孽? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任县袱,我火速辦了婚禮,結(jié)果婚禮上佑力,老公的妹妹穿的比我還像新娘式散。我一直安慰自己,他們只是感情好打颤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布暴拄。 她就那樣靜靜地躺著,像睡著了一般瘸洛。 火紅的嫁衣襯著肌膚如雪揍移。 梳的紋絲不亂的頭發(fā)上次和,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天反肋,我揣著相機(jī)與錄音,去河邊找鬼踏施。 笑死石蔗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畅形。 我是一名探鬼主播养距,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼顽馋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼根时!你這毒婦竟也來了灯节?” 一聲冷哼從身側(cè)響起卦羡,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤诊笤,失蹤者是張志新(化名)和其女友劉穎怕午,沒想到半個(gè)月后扣猫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體革娄,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毕荐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年束析,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憎亚。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡员寇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出第美,到底是詐尸還是另有隱情蝶锋,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布什往,位于F島的核電站牲览,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜第献,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一贡必、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧庸毫,春花似錦仔拟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至载佳,卻和暖如春炒事,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔫慧。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工挠乳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姑躲。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓睡扬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親黍析。 傳聞我的和親對(duì)象是個(gè)殘疾皇子卖怜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容