對(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ī)制 呢?