--- title: 'Android 匿名共享內存 - Ashmem' disqus: kyleAlien --- Android 匿名共享內存 - Ashmem === ## OverView of Content [TOC] ## Ashmem 概述 Anonymous Shared Memory 簡稱 Ashmem,是 Android 特有的內存共享機制,**它可以將指定的 ++物理內存分別映射到各自的進程的虛擬地址中++ ,==為了實現內存共享==** * 匿名共享記憶體與 Linux 系統實現的共用記憶體相同,都是 **使用核心提供為基礎的 tmpfs(暫存檔案系統) 來實現** :::info * **Ashmem 功能** 它對記憶體區塊更加精細的管理,應用程式可以 **動態將一塊匿名記憶體劃分為許多小區塊,當不再使用時,透過系統自動幫你回收** ::: :::success * **Ashmem 與 Linux 的差異** 傳統 Linux 使用整數標示該區塊的記憶體,Android 使用檔案描述符號 FD 描述該區塊記憶體 * **Android Ashmem 使用檔案描述符 FD 的好處** 1. Android Binder 機制中有一個描述為 `BINDER_TYPE_FD`,Binder 驅動就會把該 FD 複製到目標程序中,實現檔案共享 > 也就是告訴目標程序該塊記憶體的標示 FD 2. 可以透過 open 映射到應用中,進而讓應用直接修改其值 ::: ### Ashmem 驅動 & cutils Lib - 關聯 * Android 為了用戶方便使用,有提供一個執行時期函數庫 **cutils**,可以透過 **cutils 存取 Ashmem 驅動**,也提供了 C++、Java 的介面存取 cutils,概念如下 | 調用者使用 | 函數 | 目的地 | | -------- | -------- | -------- | | Framework | `ashmem_create_region` | utils library | | Framework | `ashmem_pin_region` | utils library | | Framework | `ashmem_unpin_region` | utils library | | - | - | - | | utils | `open` | Kernel | | utils | `ioctl` | Kernel | > ![](https://i.imgur.com/I82OWxP.png) ## Ashmem 驅動設備 - 結構 * Asdhmem 的實現主要是依靠驅動層實現 `/dev/ashmem` 虛擬裝置;核心文件在 Linux 工程中,其中像是 [**ashmem.c**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/staging/android/ashmem.c)、[**ashmem.h**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/staging/android/uapi/ashmem.h) 就是核心文件 > ![](https://i.imgur.com/P5Qxjjo.png) ### ashmem_area - 共用記憶體 * `ashmem_area` 結構用來 **描述共用記憶體**,並且同時 **會被寫入 `/proc/<PID>/maps` 中** (PID 是建立 Ashmem 應用的 PID,就可以知道該塊記憶體是由哪個應用創建) | 成員 | 功能 | 補充 | | - | - | - | | name | 該 Ashmem 記憶體區塊的名稱 | 如果沒有標明,則使用預設 `dev/ashme` | | unpinned_list | 該列表存儲許多小塊記可用憶體,地址從大到小,不相交互 | pin 代表鎖住無法使用,反知 unpin 表示可以使用 | | **file** | 指向 tmpfs 檔案系統中的一個檔案 | | | size | 描述 `file` 大小 | | | prot_mask | 描述該 Ashmem 的存取權限 | | :::info * 成員 `file` 對應一個 tmpfs 中的一個真正檔案 * 成員 `prot_mask` 預設 Mask 是 `#define PROT_MASK (PROT_EXEC | PROT_READ | PROT_WRITE)` > ![](https://i.imgur.com/GMTTbv1.png) ::: ```c= // ashmem.h #define ASHMEM_NAME_LEN 256 // --------------------------------------------------------------- // ashmem.c #define ASHMEM_NAME_PREFIX "dev/ashmem/" // 11 // `-1` 是為了扣除字符串的結尾規範 `\0` #define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1) // 256 + 11 #define ASHMEM_FULL_NAME_LEN (ASHMEM_NAME_LEN + ASHMEM_NAME_PREFIX_LEN) struct ashmem_area { char name[ASHMEM_FULL_NAME_LEN]; // ASHMEM_FULL_NAME_LEN 代表該區塊命名的長度 struct list_head unpinned_list; struct file *file; size_t size; unsigned long prot_mask; }; ``` ### ashmem_range - 解鎖狀態記憶體 * `ashmem_range` 結構用來 **描述解鎖 (可用) 狀態記憶體區塊** | 成員 | 功能 | 補充 | | - | - | - | | lru | 鏈結全域 `ashmem_lru_list` 鏈表 | | | unpinned | 鏈結到宿主的 `unpinned_list` 中 | 成員 `asma` 會再描述 | | asma | 描述 `unpinned` 成員宿主匿名共用記憶體的區域 | 宿主 代表 tmpfs 中的檔案 | | pgstart、pgend | 該記憶體的起始、結束位置 | 單位為 **頁** | | purged | 該記憶體是否已被回收 | 1 表示回收,0 未回收 | :::info * 當系統資源不足時,會釋放 `ashmem_range#lru` 使用最少的記憶體 ::: ```c= // ashmem.h #define ASHMEM_NOT_PURGED 0 #define ASHMEM_WAS_PURGED 1 // --------------------------------------------------------------- // ashmem.c static LIST_HEAD(ashmem_lru_list); struct ashmem_range { // 最近列表 struct list_head lru; // 鏈結到 ashmem_lru_list struct list_head unpinned; struct ashmem_area *asma; size_t pgstart; size_t pgend; unsigned int purged; // 是否已被回收 }; ``` :::warning * **Ashmem 驅動所有對於 `ashmem_range`、`ashmem_area` 操作都是互斥**;所以使用 `ashmem_mutex` 互斥鎖 ```c= // ashmem.c static DEFINE_MUTEX(ashmem_mutex); ``` ::: ### ashmem_pin - 被鎖定的記憶體 * `ashmem_pin` 結構用來 **描述即將要被 鎖定 or 要被解鎖的記憶體** | 成員 | 功能 | 補充 | | - | - | - | | offset | 要被操控的記憶體在宿主記憶體中的 **偏移量** | Byte 為單位;宿主 代表 tmpfs 中的檔案 | | len | 要被操控的記憶體的 **大小** | Byte 為單位;宿主 代表 tmpfs 中的檔案 | ```c= // ashmem.h struct ashmem_pin { __u32 offset; /* offset into region, in bytes, page-aligned */ __u32 len; /* length forward from offset, in bytes, page-aligned */ }; ``` ## Ashmem 啟動流程 * 接下來主要關心 ashmem 設備的幾個問題 1. ashmem 設備節點何時被創建的 2. 驅動設備對應的操作函數:`open`、`mmap`、`ioctl` 等等,這些函數的實現原理 3. ashmem 與 Linux 中的內存共享機制的區別 & 聯繫 大致的順序如下圖 > ![](https://i.imgur.com/I4wKyZT.png) ### ueventd 進程啟動 - 創建節點 * 當 Android 系統啟動時,init 進程會讀取 **[init.rc 文件](https://cs.android.com/android/platform/superproject/+/master:system/core/rootdir/init.rc)**,而 ueventd 進程就是這時啟動的 ```shell= # /rootdir/init.rc on early-init ... 省略部份 start ueventd service ueventd /system/bin/ueventd class core ## 類型為 core critical seclabel u:r:ueventd:s0 shutdown critical ``` * [**ueventd**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/ueventd.cpp) 服務進程 source code 在 `system/core/init/ueventd.cpp` ```c= // ueventd.cpp int ueventd_main(int argc, char** argv) { ... // @ 查看 GetConfiguration 函數 auto ueventd_configuration = GetConfiguration(); ... 省略部份 } static UeventdConfiguration GetConfiguration() { auto hardware = android::base::GetProperty("ro.hardware", ""); struct LegacyPathInfo { std::string legacy_path; std::string preferred; }; std::vector<LegacyPathInfo> legacy_paths{ {"/vendor/ueventd.rc", "/vendor/etc/ueventd.rc"}, {"/odm/ueventd.rc", "/odm/etc/ueventd.rc"}, {"/ueventd." + hardware + ".rc", "another ueventd.rc file"}}; // 收集分析檔案列表 std::vector<std::string> canonical{"/system/etc/ueventd.rc"}; if (android::base::GetIntProperty("ro.product.first_api_level", 10000) < __ANDROID_API_T__) { // 添加其他 ueventd 檔案 for (const auto& info : legacy_paths) { // 添加到 canonical 列表 canonical.push_back(info.legacy_path); } } else { ... } // 解析 ueventd.rc 檔案 return ParseConfig(canonical); } ``` * 在默認情況下 ueventd 會去解析 `ueventd.rc` & `ueventd<hardware>.rc`... 等等檔案 檔案,來加載指定的設備,接下來看[**uevnet.rc**](https://cs.android.com/android/platform/superproject/+/master:system/core/rootdir/ueventd.rc),它會啟動 binder、ashmem... ```shell= # /rootdir/ueventd.rc /dev/null 0666 root root /dev/zero 0666 root root /dev/full 0666 root root /dev/ptmx 0666 root root /dev/tty 0666 root root /dev/random 0666 root root /dev/urandom 0666 root root /dev/ashmem* 0666 root root /dev/binder 0666 root root /dev/hwbinder 0666 root root /dev/vndbinder 0666 root root /dev/pmsg0 0222 root log /dev/dma_heap/system 0444 system system /dev/dma_heap/system-uncached 0444 system system /dev/dma_heap/system-secure 0444 system system ``` :::info * 讀取完 `uevent.rc` 後會馬上創建節點? 包括 binder、ashmem... 一系列的設備節點信息都會在這裡被讀取到系統中,**但並不會馬上被創建節點** ::: ### ashmem init 分析 - 創建驅動設備 * 分析完 uevent.rc 後,啟動會觸發到 [**ashmem.c 文件**](https://android.googlesource.com/kernel/msm/+/2e4946a60e539d649fcce01cf55d642dd720cfaf/drivers/staging/android/ashmem.c) 的 `ashmem_init(void)` 靜態函數,並在這裡設定該驅動的 Fos 操作函數指標 | 函數指標行為 | Ashmem 實作函數 | | - | - | | `.owner` | ashmem_open | | `.release` | ashmem_release | | `.read` | ashmem_mmap | | `.mmap` | ashmem_mmap | | `.compat_ioctl` | .compat_ioctl | ```c= // ashmem.c // 透過 `device_initcall` 宏初始化 device_initcall(ashmem_init); static int __init ashmem_init(void) { int ret; // 分配兩個 slab 緩衝區 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); // 註冊後可見 `/dev/ashmem` 虛擬設備 ... /// 註冊記憶體回收函數,為 `ashmem_shrinker` ! ret = register_shrinker(&ashmem_shrinker, "android-ashmem"); return 0; ... } // 文件操作 (函數指標 static const 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, }; // miscdevice 結構定義 (linux/miscdevice.h) static struct miscdevice ashmem_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "ashmem", .fops = &ashmem_fops, // 操作指標結構 }; ``` * 從上面可以看到 `ashmem_misc` 被註冊到 **MISC 設備**(**miscellaneous 也就是區分到 ++雜項設備++**),**透過驅動註冊後可以看到 `/dev/ashmem` 虛擬設備** :::success * **FOPS** ? > 原本的意思是 **file_operations** 結構是 **用來操作檔案的重要資料 結構**,透過此結構才能對檔做開啟、讀取等操作,而指向 此結構的指標則稱為fops 雜項驅動設備註冊可以看 [**Binder 概述 - Misc device**](https://hackmd.io/8E1AyMAgRzym3g6ktNV37Q?view#Binder-%E6%A6%82%E8%BF%B0---Misc-device) 分析 ::: * **slab 設備**:`ashmem_area_cachep`、`ashmem_range_cachep` 是 ashmem 後續一系列操作的基礎,進程間的匿名共享內存將從這裡分配 :::info * **Linux 內存分配機制** Linux 內核在內存管理上有 [**Slab、Slub、Slob 三種機制**](https://blog.csdn.net/Rong_Toa/article/details/106440497),Slub 是在 2.6.23 版本之前採用的內存分配手動,之後用 Slub 替代,**Slob 則更適合嵌入式系統** > Slob 用來管理小物件的記憶體分配、釋放 ::: ## ashmem 驅動操作函數 依照上面我們可以看到 Ashmem 註冊的操作函數,接著我們分析 ^1^ `ashmem_open`、^2^ `ashmem_mmap`、^3^ `ashmem_ioctl` 這幾個函數 ### ashmem 驅動操作 - open * **`ashmem_open` 函數**:主要是從 Slab 區塊 (`ashmem_area_cachep`) 分配一塊 `ashmem_area` 內存,並對其初始化,並把它紀錄在 `file->private_data` 內 ```c= // ashmem.c #define ASHMEM_NAME_PREFIX "dev/ashmem/" #define ASHMEM_NAME_PREFIX_LEN (sizeof(ASHMEM_NAME_PREFIX) - 1) static int ashmem_open(struct inode *inode, struct file *file) { struct ashmem_area *asma; int ret; // 開啟檔案 ret = generic_file_open(inode, file); ... // 從 Slab 區塊 (`ashmem_area_cachep`) 分配一塊 `ashmem_area` 內存 asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL); ... // 初始化 unpin(未鎖定) 列表 INIT_LIST_HEAD(&asma->unpinned_list); // 名稱初始化 memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN); asma->prot_mask = PROT_MASK; // 存取保護位 // 存放在 file#private_data 成員中 file->private_data = asma; return 0; } ``` :::info * **在應用執行 Ashmem 的 mmap 之前可以設定 Ashmem 的名稱、大小**,如果已經 mmap (產生暫存檔案) 後,就不行再修改 ::: ### ashmem 驅動操作 - mmap * **`ashmem_mmap` 函數**:主要做兩件事情 * **創建臨時文件 `shmem_file_setup`** (由呼叫進程創建) * **內存映射 `shmem_set_file`** ```c= // ashmem.c static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) // 使用者空間的描述 { // 取出在 ashmem_open 創建的進程空間 struct ashmem_area *asma = file->private_data; int ret = 0; // Mutex 鎖(互斥鎖) mutex_lock(&ashmem_mutex); // 在做 mmap 之前,一定要先 ++通過 ioctl 設定大小++ if (unlikely(!asma->size)) { ret = -EINVAL; goto out; } // 權限保護檢查 if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask)) & calc_vm_prot_bits(PROT_MASK))) { ret = -EPERM; goto out; } ... // 如果 file 為空 代表這當前進程尚未有 Ashmem 的暫存檔案 if (!asma->file) { char *name = ASHMEM_NAME_DEF; // 預設名稱 struct file *vmfile; if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0') // 如果使用者有再設定名稱,就用使用者設定的名稱 name = asma->name; // shmem_file_setup 由 Linux 提供,要在 ++tmpfs 中創建臨時文件++ // 該臨時文件(vmfile)就是為了內存共享 vmfile = shmem_file_setup(name, asma->size, vma->vm_flags); // 判斷是否創建失敗 if (unlikely(IS_ERR(vmfile))) { ret = PTR_ERR(vmfile); goto out; } asma->file = vmfile; // 設定共同文件,下次進程就不會再進來 } get_file(asma->file); if (vma->vm_flags & VM_SHARED) // @ `shmem_set_file` shmem_set_file(vma, asma->file); // 開啟內存映射 else { if (vma->vm_file) fput(vma->vm_file); vma->vm_file = asma->file; } vma->vm_flags |= VM_CAN_NONLINEAR; asma->vm_start = vma->vm_start; out: // 解開互斥鎖 mutex_unlock(&ashmem_mutex); return ret; } ``` :::info * 可以得到以下結論: 若兩個進程想要相互分享同一個文件,**那傳入的參數 `*file` 就要相同,file 決定了是否是分享空間** 所以進程 A 如果要與進程 B 共享暫存檔案,可以透過 Binder 把 File FD 傳給進程 B ::: > ![](https://i.imgur.com/37l3QMF.png) * `shmem_set_file` 函數:映射記憶體到應用程式中,**並設定缺頁 (Page Fault) 時發生的處理函數**;當核心發出缺頁通知時就會另外處理 ## ashmem 驅動操作 - ioctl * `ashmem_ioctl` 函數:`ashmem_ioctl` 是對 Ashmem 內存共享區的操作 | cmd | 功能 | 使用函數 | | -------- | -------- | -------- | | ASHMEM_SET_NAME | 設置 Ashmem 名稱 | set_name | | ASHMEM_GET_NAME | 取得 Ashmem 名稱 | get_name | | ASHMEM_SET_SIZE | 設置 Ashmem 大小 | - | | ASHMEM_GET_SIZE | 取得 Ashmem 大小 | - | | ASHMEM_SET_PROT_MASK | | set_prot_mask | | ASHMEM_PIN | 鎖定記憶體區塊 | ashmem_pin_unpin | | ASHMEM_UNPIN | 解鎖定記憶體區塊 | ashmem_pin_unpin | | ASHMEM_GET_PIN_STATUS | 記憶體狀態 | ashmem_pin_unpin | | ASHMEM_PURGE_ALL_CACHES | | ashmem_shrink | | ASHMEM_CACHE_FLUSH_RANGE | | ashmem_cache_op | | ASHMEM_CACHE_CLEAN_RANGE | | ashmem_cache_op | | ASHMEM_CACHE_INV_RANGE | | ashmem_cache_op | ```c= // ashmem.c static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; long ret = -ENOTTY; switch (cmd) { case ASHMEM_SET_NAME: ret = set_name(asma, (void __user *) arg); break; case ASHMEM_GET_NAME: ret = (asma, (void __user *) arg); break; case ASHMEM_SET_SIZE: ret = -EINVAL; if (!asma->file) { ret = 0; asma->size = (size_t) arg; } break; ... } return ret; } ``` ### ashmem ioctl - 記憶體操控 * ashmem 驅動會將 tmp 棧存檔案分為多個小塊記憶體,每個區塊的記憶體都有 鎖定(pin 代表已使用,不可再用)、解鎖(unpin 尚未使用,可以使用) 兩個狀態,其中指令如下 | 驅動命令 cmd | 功能 | 使用函數 | | -------- | -------- | -------- | | ASHMEM_PIN | 鎖定記憶體區塊 | ashmem_pin_unpin | | ASHMEM_UNPIN | 解鎖定記憶體區塊 | ashmem_pin_unpin | | ASHMEM_GET_PIN_STATUS | 記憶體狀態 | ashmem_pin_unpin | ```c= // uapi/ashmem.h #define ASHMEM_PIN _IOW(__ASHMEMIOC, 7, struct ashmem_pin) #define ASHMEM_UNPIN _IOW(__ASHMEMIOC, 8, struct ashmem_pin) #define ASHMEM_GET_PIN_STATUS _IO(__ASHMEMIOC, 9) // ----------------------------------------------------------- // Ashmem.c static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ashmem_area *asma = file->private_data; long ret = -ENOTTY; switch (cmd) { ... 省略其他 case case ASHMEM_PIN: case ASHMEM_UNPIN: case ASHMEM_GET_PIN_STATUS: // @ 分析 ashmem_pin_unpin 函數 ret = ashmem_pin_unpin(asma, cmd, (void __user *)arg); break; ... return ret; } ``` * `ashmem_pin_unpin` 函數: 1. 檢查當前是否可以進行解鎖、鎖定 (tmp 檔案是否建立、是否指地超出大小...等等) 2. 計算記憶開開始、結束位置 :::info 這個位置是以 **頁** 為單位,並且是 **相對位置** (相對於 `ashmem_area` 的位置) ::: 3. 再依照 cmd 呼叫不同函數 | cmd | 實際處理函數 | | - | - | | ASHMEM_PIN | `ashmem_pin` | | ASHMEM_UNPIN | `ashmem_unpin` | | ASHMEM_GET_PIN_STATUS | `ashmem_get_pin_status` | ```c= static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd, void __user *p) { struct ashmem_pin pin; size_t pgstart, pgend; int ret = -EINVAL; struct ashmem_range *range = NULL; // 複製使用者空間資料到 `pin` 中 if (copy_from_user(&pin, p, sizeof(pin))) return -EFAULT; if (cmd == ASHMEM_PIN || cmd == ASHMEM_UNPIN) { // 計算準備要操控的空間 range = kmem_cache_zalloc(ashmem_range_cachep, GFP_KERNEL); if (!range) return -ENOMEM; } // 互斥鎖 mutex_lock(&ashmem_mutex); wait_event(ashmem_shrink_wait, !atomic_read(&ashmem_shrink_inflight)); // 檢查是否已經有棧存檔案可以使用 (file 檔案是在 mmap 時建立的) if (!asma->file) goto out_unlock; /* per custom, you can pass zero for len to mean "everything onward" */ // 如果大小指定為 0,代表全部 `ashmem_range` 空間 if (!pin.len) pin.len = PAGE_ALIGN(asma->size) - pin.offset; // 是否對齊邊界 if ((pin.offset | pin.len) & ~PAGE_MASK) goto out_unlock; // 是否超出大小 (-1) if (((__u32)-1) - pin.offset < pin.len) goto out_unlock; // 是否超出大小 (超出 `ashmem_range` 尾端) if (PAGE_ALIGN(asma->size) < pin.offset + pin.len) goto out_unlock; // 計算 Start、End (以 頁 當單位) pgstart = pin.offset / PAGE_SIZE; pgend = pgstart + (pin.len / PAGE_SIZE) - 1; switch (cmd) { case ASHMEM_PIN: ret = ashmem_pin(asma, pgstart, pgend, &range); break; case ASHMEM_UNPIN: ret = ashmem_unpin(asma, pgstart, pgend, &range); break; case ASHMEM_GET_PIN_STATUS: ret = ashmem_get_pin_status(asma, pgstart, pgend); break; } out_unlock: mutex_unlock(&ashmem_mutex); if (range) kmem_cache_free(ashmem_range_cachep, range); return ret; } ``` ### ashmem ioctl - 解鎖 記憶體區塊 * 從上面我們可以知道 **解鎖** 記憶體區塊實作是 `ashmem_unpin` 函數:遍歷 未鎖定鏈表 (`unpinned_list`),找到合適位置再進行判斷處理 ```c= // ashmem.h static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend, struct ashmem_range **new_range) { struct ashmem_range *range = NULL, *iter, *next; unsigned int purged = ASHMEM_NOT_PURGED; restart: // 遍歷 未鎖定鏈表,找到合適位置再進行判斷處理 list_for_each_entry_safe(iter, next, &asma->unpinned_list, unpinned) { /* short circuit: this is our insertion point */ if (range_before_page(iter, pgstart)) { range = iter; break; } // 用戶可以要求我們取消已完全固定的頁面 或部分固定 // 我們在這里處理這兩種情況 if (page_range_subsumed_by_range(iter, pgstart, pgend)) return 0; if (page_range_in_range(iter, pgstart, pgend)) { // 重新計算 Start、End pgstart = min(iter->pgstart, pgstart); pgend = max(iter->pgend, pgend); purged |= iter->purged; // @ 查看 range_del 函數 range_del(iter); goto restart; } } range = list_prepare_entry(range, &asma->unpinned_list, unpinned); // 插入 `unpinned_list` 列表 // @ 查看 range_alloc 函數 range_alloc(asma, range, purged, pgstart, pgend, new_range); return 0; } ``` * 前面有提到,解鎖區塊都是按照位置 **從大到小的順序** (並且 **不相交互**) 儲存在 ashmem_range#`unpinned_list` 成員列表中,所以需要遍歷陣列,找到合適的位置合併,有以下幾種情況 1. 完全重疊,不須拓展合併:`page_range_subsumed_by_range` 判斷,直接返回 ```c= // Ashmem.c static inline bool page_range_subsumed_by_range(struct ashmem_range *range, size_t start, size_t end) { return (range->pgstart <= start) && (range->pgend >= end); } ``` > ![](https://i.imgur.com/GGKqxHc.png) 2. 部分重疊,並找需要拓展合併:`page_range_in_range` 判斷 ```c= // Ashmem.c static inline bool page_range_in_range(struct ashmem_range *range, size_t start, size_t end) { return page_in_range(range, start) || page_in_range(range, end) || page_range_subsumes_range(range, start, end); } ``` > ![](https://i.imgur.com/8xknZqX.png) 3. 不重疊,不用合併,:`range_before_page` 判斷 ```c= // Ashmem.c static inline bool range_before_page(struct ashmem_range *range, size_t page) { return range->pgend < page; } ``` > ![](https://i.imgur.com/7I9c3PS.png) * `range_del` 函數 1. `list_del`:刪除未鎖定鏈表中的指定 range 2. `range_on_lru`:判斷是否在 LRU 列表上,如果在 LRU 列表則刪除 ```c= // ashmem.c static inline bool range_on_lru(struct ashmem_range *range) { // 如果解鎖狀態的記憶體上未被系統清理,就一定存在 LRU 列表中 ! return range->purged == ASHMEM_NOT_PURGED; } static void range_del(struct ashmem_range *range) { list_del(&range->unpinned); // 刪除未鎖定鏈表中的指定 range if (range_on_lru(range)) // @ 查看 lru_del 函數 lru_del(range); // 釋放記憶體空間 kmem_cache_free(ashmem_range_cachep, range); } ``` * `lru_del` 函數:從 LRU 列表中刪除指定記憶體區塊,並調整全域變數 `lru_count` 的大小 (表上當前 LRU 的大小) ```c= // ashmem.c static unsigned long lru_count; static inline unsigned long range_size(struct ashmem_range *range) { // + 1 是因為,[pgend、pgstart] 是閉合區間 // e.g pgend = 1, pgstart = 2 // 由於閉區間 range_size + 1 = (2 -1 -1) + 1 = 1 return range->pgend - range->pgstart + 1; } static inline void lru_del(struct ashmem_range *range) { list_del(&range->lru); lru_count -= range_size(range); } ``` * `range_alloc` 函數:串接合併後的記憶體空間到 unpinned 列表後方、並將新的 Range 放到 LRU 列表中 ```c= // ashmem.c static void range_alloc(struct ashmem_area *asma, struct ashmem_range *prev_range, unsigned int purged, size_t start, size_t end, struct ashmem_range **new_range) { struct ashmem_range *range = *new_range; *new_range = NULL; range->asma = asma; range->pgstart = start; range->pgend = end; range->purged = purged; // 串接到 unpinned 列表後方 list_add_tail(&range->unpinned, &prev_range->unpinned); // 將新的 Range 放到 LRU 列表中 if (range_on_lru(range)) lru_add(range); // @ 查看 lru_add } ``` * `lru_add` 函數:將新的記憶體區塊放置 LRU 列表後方,並調整 LRU 列表的大小 ```c= // ashmem.c static inline void lru_add(struct ashmem_range *range) { list_add_tail(&range->lru, &ashmem_lru_list); lru_count += range_size(range); } ``` ### ashmem ioctl - 鎖定 記憶體區塊 * 從上面我們可以知道 **鎖定** 記憶體區塊實作是 `ashmem_pin` 函數:遍歷 未鎖定鏈表 (`unpinned_list`),找到合適位置再進行判斷處理 ```c= // ashmem.c static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend, struct ashmem_range **new_range) { struct ashmem_range *range, *next; int ret = ASHMEM_NOT_PURGED; list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { /* moved past last applicable page; we can short circuit */ if (range_before_page(range, pgstart)) break; // 從上面我們可以知道可能有多種情況符合 `page_range_in_range` if (page_range_in_range(range, pgstart, pgend)) { ret |= range->purged; /* Case #1: Easy. Just nuke the whole thing. */ if (page_range_subsumes_range(range, pgstart, pgend)) { range_del(range); continue; } /* Case #2: We overlap from the start, so adjust it */ if (range->pgstart >= pgstart) { range_shrink(range, pgend + 1, range->pgend); continue; } /* Case #3: We overlap from the rear, so adjust it */ if (range->pgend <= pgend) { range_shrink(range, range->pgstart, pgstart - 1); continue; } /* * Case #4: We eat a chunk out of the middle. A bit * more complicated, we allocate a new range for the * second half and adjust the first chunk's endpoint. */ // 串接上 `unpinned_list` 列表 range_alloc(asma, range, range->purged, pgend + 1, range->pgend, new_range); // @ 查看 range_shrink range_shrink(range, range->pgstart, pgstart - 1); break; } } return ret; } ``` * 關於記憶體的判斷一樣有幾種:部分、完全、覆蓋重疊 * Case #1 完全重疊 `page_range_subsumes_range` 判斷:`range_del` 處理,直接從列表中刪除並使用 > ![](https://i.imgur.com/bScDPx7.png) * Case #2 部分重疊 - 尾端重疊:`range_shrink` 處理,拓展準備要鎖定的範圍 到 重疊區塊的結尾 > ![](https://i.imgur.com/jfLpgqG.png) * Case #3 部分重疊 - 前端重疊:`range_shrink` 處理,拓展準備要鎖定的範圍 到 重疊區塊的前端 > ![](https://i.imgur.com/3UQsdqW.png) * Case #4 覆蓋重疊:將記憶體區塊切割,並把沒有使用到的記憶體串接到 `unpinned_list` 列表中 > ![](https://i.imgur.com/WvWrSO6.png) * `range_shrink` 函數:調整目標 range 的 start、end 數值,並同時調整 LRU 列表 ```c= // ashmem.c static inline void range_shrink(struct ashmem_range *range, size_t start, size_t end) { size_t pre = range_size(range); range->pgstart = start; range->pgend = end; if (range_on_lru(range)) lru_count -= pre - range_size(range); } ``` ### ashmem 匿名記憶體回收 :::info * 現在說明的並不是使用者操控,是前面我們所說像系統註冊 `ashmem_shrinker` 函數,系統回收記憶體實會呼叫該函數 ```c= // ashmem.c static struct shrinker ashmem_shrinker = { .count_objects = ashmem_shrink_count, // 系統回收記憶體時呼叫的函數 .scan_objects = ashmem_shrink_scan, // @ 查看 ashmem_shrink_scan 函數 .seeks = DEFAULT_SEEKS * 4, }; static int __init ashmem_init(void) { ... ret = register_shrinker(&ashmem_shrinker, "android-ashmem"); } ``` ::: * `ashmem_shrink_scan` 函數:檢查 `ashmem_lru_list` 最近解鎖的區塊 ```c= // ashmem.c static unsigned long ashmem_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) { unsigned long freed = 0; /* We might recurse into filesystem code, so bail out if necessary */ // 判斷是否有禁止回收 if (!(sc->gfp_mask & __GFP_FS)) return SHRINK_STOP; // 嘗試獲取鎖 if (!mutex_trylock(&ashmem_mutex)) return -1; // 遍歷 `ashmem_lru_list` while (!list_empty(&ashmem_lru_list)) { // 取得記憶體區塊 struct ashmem_range *range = list_first_entry(&ashmem_lru_list, typeof(*range), lru); // 以 "頁" 為單位的 Start、End loff_t start = range->pgstart * PAGE_SIZE; loff_t end = (range->pgend + 1) * PAGE_SIZE; struct file *f = range->asma->file; get_file(f); atomic_inc(&ashmem_shrink_inflight); range->purged = ASHMEM_WAS_PURGED; // 標示已回收 // 從 LRU 列表中刪除 lru_del(range); freed += range_size(range); // 調整大小 mutex_unlock(&ashmem_mutex); f->f_op->fallocate(f, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, start, end - start); fput(f); if (atomic_dec_and_test(&ashmem_shrink_inflight)) wake_up_all(&ashmem_shrink_wait); if (!mutex_trylock(&ashmem_mutex)) goto out; if (--sc->nr_to_scan <= 0) break; } mutex_unlock(&ashmem_mutex); out: return freed; } ``` ## Ashmem 應用 - [MemoryDealer](https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/MemoryDealer.cpp) * **Android 系統就是透過 Ashmem 設備來實現進程內存共享**,以下看看 [**MemoryDealer**](https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/MemoryDealer.cpp),MemoryDealer 有兩個重要成員 ^1^ mAllocator(內存分配器) 、^2^ mHeap([**MemoryHeapBase**](https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/MemoryHeapBase.cpp) 內存載體) * MemoryDealer 可以看做是 Ashmem 的封裝類,內部也有使用到 Binder 機制,像是 **[AudioFlinger](https://android.googlesource.com/platform/frameworks/av/+/49dd5cf3469cd755321f8ec10013ad4fcfaf723d/services/audioflinger/AudioFlinger.cpp) 就是通過它來與 [AudioTrack](https://android.googlesource.com/platform/frameworks/av/+/master/media/libaudioclient/AudioTrack.cpp) 實現跨進程的音頻數據傳遞** 1. AudioFlinger 是 Server 2. AudioTrack 是 Client ```cpp= // /audioflinger/AudioFlinger.cpp // 但這個取名為 Client... ? AudioFlinger::Client::Client(const sp<AudioFlinger>& audioFlinger, pid_t pid) : RefBase(), mAudioFlinger(audioFlinger), // 建構 MemoryDealer 物件 mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")), mPid(pid), mTimedTrackCount(0) { // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer } ``` ### [AudioFlinger](https://android.googlesource.com/platform/frameworks/av/+/49dd5cf3469cd755321f8ec10013ad4fcfaf723d/services/audioflinger/AudioFlinger.cpp) Server 開闢內存 - [MemoryHeapBase](https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/MemoryHeapBase.cpp) & [ashmem](https://android.googlesource.com/platform/system/core/+/4f6e8d7a00cbeda1e70cc15be9c4af1018bdad53/libcutils/ashmem-dev.c) | 重點相關類 | 補充 | | -------- | -------- | | AudioFlinger.cpp / AudioFlinger.h | | | TrackBase(AudioFlinger 內部類) | | | MemoryDealer.cpp / MemoryDealer.h | | | ashmem-dev.c | | | MemoryBase.h | | | **IMemory.h** | | | **MemoryHeapBase.h** | | * 先一個結論觀察問題,AudioFlinger 是如何透過 MemoryHeapBase 創建共用的記憶體區塊的 (這樣才能進程共享同一個記憶體區塊) ```cpp= // /binder/MemoryDealer.cpp // 建構函數 MemoryDealer::MemoryDealer(size_t size, const char* name, uint32_t flags) : mHeap(sp<MemoryHeapBase>::make(size, flags, name)), mAllocator(new SimpleBestFitAllocator(size)) {} // ---------------------------------------------------------------------- // /binder/MemoryHeapBase.cpp MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name) : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags), mDevice(nullptr), mNeedUnmap(false), mOffset(0) { const size_t pagesize = getpagesize(); size = ((size + pagesize-1) & ~(pagesize-1)); // 1. ashmem_create_region 重點函數 int fd = ashmem_create_region(name == nullptr ? "MemoryHeapBase" : name, size); ... if (fd >= 0) { // 2. mapfd 重點函數 if (mapfd(fd, true, size) == NO_ERROR) { ... } } ``` * 從上面可以看到兩個重點函數 **^1^ ashmem_create_region**、**^2^ mapfd**,這個函數時現在 `/libcutils/ashmem-host.c` 或是 [**`/libcutils/ashmem-dev.c`**](https://android.googlesource.com/platform/system/core/+/4f6e8d7a00cbeda1e70cc15be9c4af1018bdad53/libcutils/ashmem-dev.c) 1. **ashmem_create_region** 函數:開啟 ashmem,並透過 **ioctl 設定文件名稱、大小** (前面有提到,在 mmap 之前要透 ioctl 設定 size) ```cpp= // /libcutils/ashmem-dev.c #define ASHMEM_DEVICE "/dev/ashmem" int ashmem_create_region(const char *name, size_t size) { int fd, ret; fd = open(ASHMEM_DEVICE, O_RDWR); if (fd < 0) return fd; if (name) { char buf[ASHMEM_NAME_LEN]; strlcpy(buf, name, sizeof(buf)); ret = ioctl(fd, ASHMEM_SET_NAME, buf); // 設定名稱(ashmem_ioctl's cmd 命令) if (ret < 0) goto error; } ret = ioctl(fd, ASHMEM_SET_SIZE, size); // 設定大小(ashmem_ioctl's cmd 命令) if (ret < 0) goto error; return fd; error: close(fd); return ret; } ``` :::info * 補充 ashmem `/libcutils/ashmem-host.c`(**host 結尾是給模擬器用的**) `/libcutils/ashmem-dev.c`(真實設備) ::: 2. **mapfd 函數:開闢內存 mmap** ```cpp= // MemoryHeapBase status_t MemoryHeapBase::mapfd(int fd, bool writeableByCaller, size_t size, off_t offset) { ... if ((mFlags & DONT_MAP_LOCALLY) == 0) { ... void* base = (uint8_t*)mmap(nullptr, size, prot, MAP_SHARED, fd, offset); ... mBase = base; // 內存映射起始點 mNeedUnmap = true; } else { ... } mFD = fd; // 文件描述 mSize = size; // 被映射的空間大小 mOffset = offset; // 偏移量 return NO_ERROR; } ``` 流程圖: > ![](https://i.imgur.com/IMmW4AN.png) 關係圖: > ![](https://i.imgur.com/MyOlo7H.png) ### [AudioTrack](https://android.googlesource.com/platform/frameworks/av/+/master/media/libaudioclient/AudioTrack.cpp) Client 獲取映射內存 * 當 AudioFlinger 這個進程開闢好內存後要如何把他的文件描述(共用區塊)傳遞給 AudioTrack (Client 端)呢? **就是透過 Binder 機制** | 重點相關類 | 補充 | | -------- | -------- | | AudioTrack.cpp / AduioTrack.h| | | TrackBase.h | | | IAudioTrack.h | | | IMemory.h | | * AudioTrack 透過以下方式取得文件描述 1. AudioTrack 透過 getCblk 獲取一個 **++==IMemory 對象==++**,**==track 就是 Audio & AudioFlinger 的連接通道==** [**IAudioTrack.h**](https://android.googlesource.com/platform/frameworks/av/+/master/media/libaudioclient/include/media/IAudioTrack.h) ```cpp= // /libaudioclient/AudioTrack.cpp status_t AudioTrack::createTrack_l() { ... // 透過 audioFlinger 創建一個 IAudioTrack sp<IAudioTrack> track = audioFlinger->createTrack(input, output, &status); // sp 是智能指針 sp<IMemory> iMem = track->getCblk(); ... } // --------------------------------------------------------- // /media/libaudioclient/IAudioTrack.cpp virtual sp<IMemory> getCblk() const { Parcel data, reply; sp<IMemory> cblk; data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor()); status_t status = remote()->transact(GET_CBLK, data, &reply); if (status == NO_ERROR) { cblk = interface_cast<IMemory>(reply.readStrongBinder()); if (cblk != 0 && cblk->unsecurePointer() == NULL) { cblk.clear(); } } return cblk; } ``` **最終 (透過 Binder 機制) AudioTrack 的 getCblk 會呼叫到 [TrackBase.h](https://android.googlesource.com/platform/frameworks/av/+/master/services/audioflinger/TrackBase.h) 的 (已在 AudioFlinger 進程)getCblk() 方法** ```cpp= // /audioflinger/TrackBase.h sp<IMemory> mCblkMemory; // 宣告 sp<IMemory> getCblk() const { return mCblkMemory; } // mCblkMemory 誰設定 ? ``` mCblkMemory 又是由 AudioFlinger 構造方法內申請 (查看 AudioFlinger 的構造方法) ```cpp= // /audioflinger/AudioFlinger.h sp<Client> mClient; // /audioflinger/AudioFlinger.cpp AudioFlinger::ThreadBase::TrackBase::TrackBase(...) : mClient(client), ... { ... // client->heap() 就是 MemoryDealer mCblkMemory = client->heap()->allocate(size); ... } ``` client->heap() 就是前面說的 MemoryDealer,所以最終呼叫到了 MemoryDealer#allocate 方法 ```cpp= // /libs/binder/MemoryDealer.cpp sp<IMemory> MemoryDealer::allocate(size_t size) { sp<IMemory> memory; const ssize_t offset = allocator()->allocate(size); if (offset >= 0) { memory = sp<Allocation>::make(sp<MemoryDealer>::fromExisting(this), heap(), offset, size); } return memory; } ``` 2. Allocation 創建 (MemoryDealer.cpp 中,繼承 [**MemoryBase**](https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/include/binder/MemoryBase.h)),其中 **第二個參數對應 AudioFligner 的 ==MemoryHeapBase==,這樣就連接到了 Server 端的 MemoryHeapBase** **這是為了要讓 ++IMemoryHeap++ & ++IMemory++ 建立聯繫** ```cpp= // /libs/binder/MemoryDealer.cpp Allocation::Allocation( const sp<MemoryDealer>& dealer, const sp<IMemoryHeap>& heap, // 對應 MemoryHeapBase ssize_t offset, size_t size) : MemoryBase(heap, offset, size), mDealer(dealer) { #ifndef NDEBUG void* const start_ptr = (void*)(intptr_t(heap->base()) + offset); memset(start_ptr, 0xda, size); #endif } ``` 3. [**IMemory**](https://android.googlesource.com/platform/frameworks/native/+/android-7.1.1_r58/include/binder/IMemory.h) 中進一步獲取,真正的內存共享 IMemory 就是我們在第 1 點中提到的 getCblk 所獲取,**iMem 則是 BpMemory (IMemory 內部類)** ```cpp= // /libaudioclient/AudioTrack.cpp status_t AudioTrack::createTrack_l() { ... // 透過 audioFlinger 創建一個 IAudioTrack // 創建一個給 AudioTrack 訪問的空間 sp<IAudioTrack> track = audioFlinger->createTrack(input, output, &status); ... // 獲取共享內存 BpMemory sp<IMemory> iMem = track->getCblk(); ... // 取得指向 AudioTrack 的指針 void *iMemPointer = iMem->unsecurePointer(); ... // 取得內存映射到 AudioTrack 中的虛擬地址 audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer); mCblk = cblk; } ``` 變量 cblk 的數據類型是 **audio_track_cblk_t,內部存有共享內存,也就是映射到 Audio_track 進程空間中的 ++虛擬地址++(mmap 的結果)** 結論:createTrack_l 這個函數主要做了這幾件事 * 得到 AudioFlinger 中創建的這塊共享內存的地址 * 將這個地址進一部轉化為 AudioTrack 可以直接方問的地址 流程圖: > ![](https://i.imgur.com/0zAvPvG.png) 關係圖: > ![](https://i.imgur.com/iYCVJWm.png) ### 映射細節 - assertReallyMapped * 前面說過兩個進程在 **ashmem 中能共享內存是因為使用了同一個文件**,也就是 iMem->unsecurePointer() 要完成的任務 ```cpp= // /libaudioclient/AudioTrack.cpp status_t AudioTrack::createTrack_l() { ... // 透過 audioFlinger 創建一個 IAudioTrack // 創建一個給 AudioTrack 訪問的空間 sp<IAudioTrack> track = audioFlinger->createTrack(input, output, &status); ... // 獲取共享內存 BpMemory sp<IMemory> iMem = track->getCblk(); ... // 取得指向 AudioTrack 的指針 void *iMemPointer = iMem->unsecurePointer(); ... // 取得內存映射到 AudioTrack 中的虛擬地址 audio_track_cblk_t* cblk = static_cast<audio_track_cblk_t*>(iMemPointer); mCblk = cblk; } ``` * 而獲取的方式就是從 IMemeory 的 pointer 方法取得,實現在 [**IMemory.cpp**](https://android.googlesource.com/platform/frameworks/native/+/jb-dev/libs/binder/IMemory.cpp) ```cpp= // 定義 /include/binder/IMemory.h class IMemory : public IInterface { public: ... void* pointer() const; }; //-------------------------------------------------------------------- // 實現 /libs/binder/IMemory.cpp void* IMemory::pointer() const { ssize_t offset; sp<IMemoryHeap> heap = getMemory(&offset); void* const base = heap!=0 ? heap->base() : MAP_FAILED; if (base == MAP_FAILED) return 0; return static_cast<char*>(base) + offset; } //-------------------------------------------------------------------- // /libs/binder/IMemory.cpp sp<IMemoryHeap> BpMemory::getMemory(ssize_t* offset, size_t* size) const { if (mHeap == 0) { Parcel data, reply; data.writeInterfaceToken(IMemory::getInterfaceDescriptor()); if (remote()->transact(GET_MEMORY, data, &reply) == NO_ERROR) { sp<IBinder> heap = reply.readStrongBinder(); ssize_t o = reply.readInt32(); size_t s = reply.readInt32(); if (heap != 0) { mHeap = interface_cast<IMemoryHeap>(heap); if (mHeap != 0) { mOffset = o; mSize = s; } } } } if (offset) *offset = mOffset; if (size) *size = mSize; return mHeap; } //-------------------------------------------------------------------- // /libs/binder/IMemory.cpp 內部類 class BpMemory : public BpInterface<IMemory> { public: BpMemory(const sp<IBinder>& impl); virtual ~BpMemory(); virtual sp<IMemoryHeap> getMemory(ssize_t* offset=0, size_t* size=0) const; private: mutable sp<IMemoryHeap> mHeap; mutable ssize_t mOffset; mutable size_t mSize; }; //-------------------------------------------------------------------- // 宣告 /include/binder/IMemory.h class IMemoryHeap : public IInterface { public: virtual void* getBase() const = 0; ... void* base() const { return getBase(); } }; //-------------------------------------------------------------------- // /libs/binder/IMemory.cpp void* BpMemoryHeap::getBase() const { assertMapped(); return mBase; } ``` * 其重點就在 **assertMapped()** 這個函數中 (BpMemoryHeap 是 IMemory.cpp 的內部類) ```cpp= // /libs/binder/IMemory.cpp void BpMemoryHeap::assertMapped() const { // 若是第一次調用 則會進來 if (mHeapId == -1) { sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder()); // find_heap 函數 sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get())); heap->assertReallyMapped(); ... } } ``` 1. 假設 `mHeapId == -1`,說明還沒有把共享內存映射到虛擬空間中(自己的進程還沒拿到映射) 2. find_heap:考慮到一個進程可能有多個 BpMemoryHeap 對應同一個共享內存,所以使用 find_heap 來找到相對的 heap * 使用 **Binder 機制讀取 ashmem 設備** ```cpp= // /libs/binder/IMemory.cpp void BpMemoryHeap::assertReallyMapped() const { if (mHeapId == -1) { Parcel data, reply; data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor()); status_t err = remote()->transact(HEAP_ID, data, &reply); // 獲取 AudioFlinger 進程端對應的 ashmem 設備描述符 int parcel_fd = reply.readFileDescriptor(); ... int fd = dup( parcel_fd ); ... Mutex::Autolock _l(mLock); if (mHeapId == -1) { mRealHeap = true; mBase = mmap(0, size, access, MAP_SHARED, fd, offset); ... } } } ``` * 這裡使用了 Binder 機制 remote->transact 最終會呼叫到 onTransact 函數,並在 getHeapID 取得文件描述 ```cpp= // /libs/binder/IMemory.cpp status_t BnMemoryHeap::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { switch(code) { case HEAP_ID: { CHECK_INTERFACE(IMemoryHeap, data, reply); // 取得文件描述 reply->writeFileDescriptor(getHeapID()); reply->writeInt32(getSize()); reply->writeInt32(getFlags()); reply->writeInt32(getOffset()); return NO_ERROR; } break; default: return BBinder::onTransact(code, data, reply, flags); } } // --------------------------------------------------------------------- // /binder/MemoryHeapBase.cpp int MemoryHeapBase::getHeapID() const { return mFD; } ``` IMemory & IMemoryHeap 關係: > ![](https://i.imgur.com/f1dMDnQ.png) ## Appendix & FAQ :::info ::: ###### tags: `Android 系統`