---
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 |
> 
## 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) 就是核心文件
> 
### 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)`
> 
:::
```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 中的內存共享機制的區別 & 聯繫
大致的順序如下圖
> 
### 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
:::
> 
* `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);
}
```
> 
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);
}
```
> 
3. 不重疊,不用合併,:`range_before_page` 判斷
```c=
// Ashmem.c
static inline bool range_before_page(struct ashmem_range *range,
size_t page)
{
return range->pgend < page;
}
```
> 
* `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` 處理,直接從列表中刪除並使用
> 
* Case #2 部分重疊 - 尾端重疊:`range_shrink` 處理,拓展準備要鎖定的範圍 到 重疊區塊的結尾
> 
* Case #3 部分重疊 - 前端重疊:`range_shrink` 處理,拓展準備要鎖定的範圍 到 重疊區塊的前端
> 
* Case #4 覆蓋重疊:將記憶體區塊切割,並把沒有使用到的記憶體串接到 `unpinned_list` 列表中
> 
* `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;
}
```
流程圖:
> 
關係圖:
> 
### [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 可以直接方問的地址
流程圖:
> 
關係圖:
> 
### 映射細節 - 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 關係:
> 
## Appendix & FAQ
:::info
:::
###### tags: `Android 系統`