Android系统的Ashmem匿名共享内存子系统分析(2)- 运行时库cutils的Ashmem访问接口

news2024/7/6 20:06:38

声明

  • 其实对于Android系统的Ashmem匿名共享内存系统早就有分析的想法,记得2019年6、7月份Mr.Deng离职期间约定一起对其进行研究的,但因为我个人问题没能实施这个计划,留下些许遗憾…
  • 文中参考了很多书籍及博客内容,可能涉及的比较多先不具体列出来了;
  • 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:cm-14.1 Android系统启动过程分析(1)-如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机

1 Ashmem的架构

  Android 系统实现的 Ashmem 匿名共享内存子系统,用来在应用程序之间共享数据。Ashmem 与传统的Linux系统实现的共享内存一样,都是基于内核提供的临时文件系统tmpfs实现的,但是 Ashmem 对内存块进行了更为精细化的管理。应用程序可以动态地将一块匿名共享内存划分为若干个小块,当这些小块内存不再需要使用时,它们就可以被内存管理系统回收。通过这种动态的、分而治之的内存管理方式,Android系统就能够有效地使用系统内存,适应内存较小的移动设备环境。

  匿名共享内存系统是以Ashmem驱动程序为基础的,系统中所有的匿名共享内存都由Ashmem驱动程序负责分配和管理。Android系统在 Native 层提供了 C/C++ 调用接口和 Framework 层提供了 Java 调用接口。

  • 在Framework 层中,提供了两个C++类 MemoryBase 和 MemoryHeapBase,以及一个 Java 类 MemoryFile 来使用匿名共享内存。
  • 在运行时库 cutils 中,主要提供了三个C函数 ashmem_create_region、ashmem_pin_region 和 ashmem_unpin_region 来访问 Ashmem 驱动程序。

  Ashmem驱动程序在启动时,会创建一个 /dev/ashmem 设备文件,这样,运行时库 cutils 中的匿名共享内存接口就可以通过文件操作函数 open 和 ioctl 等来访问 Ashmem 驱动程序。
在这里插入图片描述

  传统的 Linux 系统使用一个整数来标志一块共享内存,而 Android 系统则使用一个文件描述符来标志一块匿名共享内存。使用文件描述符来描述一块匿名共享内存有两个好处:

  1. 可以方便地将它映射到进程的地址空间,从而可以直接访问它的内容;
  2. 可以使用 Binder 进程间通信机制来传输这个文件描述符,从而实现在不同的应用程序之间共享一块匿名内存。

  Binder 进程间通信机制使用一个类型为 BINDER_TYPE_FD 的 Binder 对象来描述一个文件描述符,当 Binder 驱动程序发现进程间通信数据中包含有这种 Binder 对象时,就会将对应的文件描述符复制到目标进程中,从而实现在两个进程中共享同一个文件。

2 运行时库cutils的Ashmem访问接口

运行时库 cutils 的匿名共享内存访问接口实现在源码 system/core/libcutils/ashmem-dev.c 文件中。这个文件提供了五个 C 接口来访问 Ashmem 驱动程序,它们分别是:

  • ashmem_create_region
  • ashmempin_region
  • ashmem_unpin_region
  • ashmem_set_prot_region
  • ashmem_get_size_region

2.1 ashmem_create_region

/*
 * ashmem_create_region - creates a new ashmem region and returns the file
 * descriptor, or <0 on error
 *
 * `name' is an optional label to give the region (visible in /proc/pid/maps)
 * `size' is the size of the region, in page-aligned bytes
 */
int ashmem_create_region(const char *name, size_t size)
{
    int ret, save_errno;

    int fd = __ashmem_open();
    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));
        if (ret < 0) {
            goto error;
        }
    }

    ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));
    if (ret < 0) {
        goto error;
    }

    return fd;

error:
    save_errno = errno;
    close(fd);
    errno = save_errno;
    return ret;
}

  函数 ashmem_create_region 用来请求 Ashmem 驱动程序为应用程序创建一块匿名共享内存,其中参数 name 和 size 分别表示请求创建的匿名共享内存的名称和大小。请求Ashmem驱动程序创建一块匿名共享内存分三步来完成。

  1. 第一步是调用函数 open 打开设备文件 ASHMEM_DEVICE,即设备文件 /dev/ashmem,它的定义如下所示:

    #define ASHMEM_DEVICE "/dev/ashmem"
    

    调用函数 open 打开设备文件 /dev/ashmem 时,Ashmem 驱动程序的函数 ashmem_open 就会被调用主要是为应用程序创建一个 ashmem_area 结构体,用来描述一块匿名共享内存。打开了设备文件 /dev/ashmem 之后,就会得到一个文件描述符,接下来就可以通过这个文件描述符来访问前面请求 Ashmem 驱动程序创建的匿名共享内存

  2. 第二步是使用 IO 控制命令 ASHMEM_SET_NAME 来请求 Ashmem 驱动程序将前面所创建的匿名共享内存的名称修改为name

  3. 第三步是使用 IO 控制命令 ASHMEM_SET_SIZE 来请求 Ashmem 驱动程序将前面所创建的匿名共享内存的大小修改为 size

2.2 ashmem_pin_region

/*
	 fd:		前面打开设备文件 /dev/ashmem 所得到的一个文件描述符;
	 offset:	用来指定要锁定的内存块在其宿主匿名共享内存中的偏移地址
	 len:		用来指定要锁定的内存块在其宿主匿名共享内存中的长度
*/
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
    struct ashmem_pin pin = { offset, len };

    int ret = __ashmem_is_ashmem(fd);
    if (ret < 0) {
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin));
}

  函数 ashmem_pin_region 使用 IO 控制命令 ASHMEM_PIN 来请求 Ashmem 驱动程序锁定一小块匿名共享内存

2.3 ashmem_unpin_region

/*
	 fd:		前面打开设备文件 /dev/ashmem 所得到的一个文件描述符;
	 offset:	用来指定要解锁的内存块在其宿主匿名共享内存中的偏移地址
	 len:		用来指定要解锁的内存块在其宿主匿名共享内存中的长度
*/
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
    struct ashmem_pin pin = { offset, len };

    int ret = __ashmem_is_ashmem(fd);
    if (ret < 0) {
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_UNPIN, &pin));
}

  函数 ashmem_unpin_region 使用 IO 控制命令 ASHMEM_UNPIN 来请求 Ashmem 驱动程序解锁一小块匿名共享内存

2.4 ashmem_set_prot_region

/*
	 fd:		前面打开设备文件 /dev/ashmem 所得到的一个文件描述符;
	 prot:		指定要修改的访问保护位,它的取值为PROT_EXEC、PROTREAD、PROT_WRITE或其组合值;
*/
int ashmem_set_prot_region(int fd, int prot)
{
    int ret = __ashmem_is_ashmem(fd);
    if (ret < 0) {
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_PROT_MASK, prot));
}

  函数 ashmem_set_prot_region 使用 IO 控制命令 ASHMEM_SET_PROT_MASK 来请求 Ashmem 驱动程序修改一块匿名共享内存的访问保护位。Ashmem 驱动程序中的函数 set_prot_mask 负责处理 IO 控制命令 ASHMEM_SET_PROT_MASK,它的实现如下所示:

static int set_prot_mask(struct ashmem_area *asma, unsigned long prot)
{
    int ret = 0;

    mutex_lock(&ashmem_mutex);

    /* the user can only remove, not add, protection bits */
    if (unlikely((asma->prot_mask & prot) != prot)) {
        ret = -EINVAL;
        goto out;
    }

    /* does the application expect PROT_READ to imply PROT_EXEC? */
    if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
        prot |= PROT_EXEC;

    asma->prot_mask = prot;

out:
    mutex_unlock(&ashmem_mutex);
    return ret;
}

  Ashmem 驱动程序在创建一块匿名共享内存时,将它的访问保护位设置为 PROT_MASK,表示这块匿名共享内存具有可执行、读和写权限。此后,应用程序只能删除它的访问保护位,而不能增加它的访问保护位。因此,第8行的i语句首先检查要修改的访问保护位 prot 是否超出了目标匿名共享内存 asma 所允许的范围。

  有一种特殊情况,即当当前进程 current 的 personality 属性的 READIMPLIES_EXEC 位被设置为1时,第14行就会检查参数 prot 的 PROT_READ 位是否被设置为1。如果是,那么第15行就将它的 PROT_EXEC 位也设置为1,因为当一个进程的 personality 属性的 READ_IMPLIES_EXEC 位被设置为1时,就表示当它有权限读一块内存时,也隐含着它对该内存有执行权限。最后,第17行将目标匿名共享内存 asma 的访问保护位 prot_mask 设置为参数 prot 的值。

2.5 ashmem_get_size_region

/*
	 fd:		前面打开设备文件 /dev/ashmem 所得到的一个文件描述符;
*/
int ashmem_get_size_region(int fd)
{
    int ret = __ashmem_is_ashmem(fd);
    if (ret < 0) {
        return ret;
    }

    return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
}

  函数 ashmem_get_size_region 使用 IO 控制命令 ASHMEM_GET_SIZE 来请求 Ashmem 驱动程序返回块匿名共享内存的大小。Ashmem 驱动程序中的函数 ashmem_ioctl 负责处理 IO 控制命 ASHMEM_GET_SIZE,它的实现如下所示:

static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct ashmem_area *asma = file->private_data;		//先找到要获得其大小的匿名共享内存 asma
    long ret = -ENOTTY;

    switch (cmd) {
    case ASHMEM_SET_NAME:
        ret = set_name(asma, (void __user *) arg);
        break;
    case ASHMEM_GET_NAME:
        ret = get_name(asma, (void __user *) arg);
        break;
    case ASHMEM_SET_SIZE:
        ret = -EINVAL;
        if (!asma->file) {
            ret = 0;
            asma->size = (size_t) arg;
        }
        break;
    case ASHMEM_GET_SIZE:
        ret = asma->size;								//再将它的大小size返回给调用者
        break;
    case ASHMEM_SET_PROT_MASK:
        ret = set_prot_mask(asma, arg);
        break;
    case ASHMEM_GET_PROT_MASK:
        ret = asma->prot_mask;
        break;
    case ASHMEM_PIN:
    case ASHMEM_UNPIN:
    case ASHMEM_GET_PIN_STATUS:
        ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);
        break;
    case ASHMEM_PURGE_ALL_CACHES:
        ret = -EPERM;
        if (capable(CAP_SYS_ADMIN)) {
            struct shrink_control sc = {
                .gfp_mask = GFP_KERNEL,
                .nr_to_scan = 0,
            };
            ret = ashmem_shrink(&ashmem_shrinker, &sc);
            sc.nr_to_scan = ret;
            ashmem_shrink(&ashmem_shrinker, &sc);
        }
        break;
    }

    return ret;
}

至此,分析完成运行时库 cutils 中的匿名共享内存的C访问接口了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/618398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

带领你打开C++的神秘之门--完结篇

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f…

【耗时一月】AWS Aurora 数据库 Failover 处理方案

Aurora简述 Amazon Aurora 是亚马逊自研的云原生数据库&#xff0c;除兼容性、性能、扩展性外&#xff0c;它在设计之初&#xff0c;就以极致的可用性作为目标&#xff0c;尽可能减少故障对应用程序的影响。 Amazon Aurora 在故障恢复方面的设计理念主要包括&#xff1a; 1. 能…

Lecture 11 Contextual Representation

目录 Problems with Word Vectors/Embeddings 词向量/嵌入的问题RNN 语言模型Bidirectional RNN 双向 RNNEmbeddings from Language Models 基于语言模型的嵌入ELMo 架构Downstream Task: POS Tagging 下游任务&#xff1a;词性标注ELMo 的表现如何&#xff1f;Other Findings上…

Word控件Spire.Doc 【其他】教程(7): 使用象征符号在 Word 中绘制复选框

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

河北沃克HEGERLS仓储货架生产厂家|夹抱式伸缩货叉四向穿梭车新型物流机器人

众所周知仓库作业主要是围绕存取、搬运、拣选、输送分拣而进行的&#xff0c;而随着物流作业的多样化、复杂化&#xff0c;四向穿梭车作为新的存储技术&#xff0c;以其灵活、柔性等特点而备受瞩目。河北沃克在成功研发四向穿梭车的基础上又对其进行了产品的横向发展。目前&…

为什么会有刷掉第一名、刷掉400+的院校?

本期为大家整理热门院校-“南昌大学”的择校分析&#xff0c;这个择校分析专题会为大家结合&#xff1a;初试复试占比、复试录取规则&#xff08;是否公平&#xff09;、往年录取录取名单、招生人数、分数线、专业课难度等进行分析。希望能够帮到大家! –所有数据来源于研招网…

数据库信息速递 10年的数据库使用习惯变革,数据库的使用习惯在被改变 (译)...

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

苹果的头显,只要看一眼就行

阅读本文大概需要 1.66 分钟。 今年的 WWDC23 开发者大会&#xff0c;不少人表示 iOS 更新了个寂寞&#xff0c;但 Vision Pro 头显却意外吸引眼球&#xff0c;看来苹果工程师都忙着搞头显去了。 苹果的头显终于还是来了&#xff0c;关于它的传闻&#xff0c;似乎这几年从未间断…

【立体视觉(一)】之成像原理与镜头畸变

【立体视觉&#xff08;一&#xff09;】之成像原理与镜头畸变 一、成像原理一&#xff09;针孔模型二&#xff09;坐标系转换1. 世界坐标系到相机坐标系2. 相机坐标系到图像坐标系3. 图像坐标系到像素坐标系4. 相机坐标系到像素坐标系5. 世界坐标系到像素坐标系 二、镜头畸变一…

数据结构 | 图的遍历(C语言)

一、数据结构定义 1、图 #define MaxVertexNum 100 // 最大可存储的节点数目/*图*/ typedef char VexterType; typedef int EdgeType;typedef struct GraphMatrix {VexterType Vexs[MaxVertexNum]; //结点 EdgeType Edges[MaxVertexNum][MaxVertexNum]; //边int vexnum, a…

【区块链】以太坊L2扩容方案与零知识证明

以太坊L2扩容方案与零知识证明 简介 简要概述以太坊L2层现有解决方案简要概述以太坊L2未来扩容的方向简要概述零知识证明的基本概念和零知识证明在以太坊的运用简要概述stark ware的两个产品&#xff0c;但并不推荐现有使用和研究简要概述polygon zkEVM&#xff0c;推荐使用和…

纷享销客斩获“2022-2023年度用户推荐SaaS品牌”

近日&#xff0c;纷享销客受邀出席CSIC2023第八届SaaS应用大会暨云领奖颁奖典礼活动。纷享销客作为国内领先的CRM 云厂商&#xff0c;凭借产品创新以及强大的行业解决方案能力&#xff0c;以绝对实力荣获“2022-2023年度用户推荐SaaS品牌”。 据悉&#xff0c; C SIC云领奖自 …

【SQL武林秘籍】玩转表及其数据

目录 &#x1f4d6;前言 &#x1f600;数据库约束类型 1️⃣not null 非空约束 2️⃣unique 唯一约束 3️⃣default 默认值约束 4️⃣primary key 主键约束 5️⃣foreign key 外键约束 6️⃣check 限制约束 &#x1f609;新增(insert select) &#x1f604;查询(进…

Podman 是 Docker 的直接替代品吗?

在许多地方&#xff0c;你可以读到Podman是Docker的替代品。但是&#xff0c;真的像听起来那么简单吗&#xff1f;在这篇文章中&#xff0c;你将从一个可以立即投入生产使用的Dockerfile开始&#xff0c;并执行Podman命令&#xff0c;就像你使用Docker时会做的那样。让我们看看…

【Vite环境变量】import.meta.env 和 loadEnv使用和区别

前言 我们在做项目时需要各种配置信息&#xff08;如应用标题、API 地址等&#xff09;&#xff0c;这些配置信息可能在不同环境下有所不同&#xff08;如开发环境和生产环境&#xff09;。 如果每次更改开发或者更改生产环境需要修改源代码中的相关配置&#xff0c;这会导致…

老生常谈:接口幂等性,防止并发插入重复数据

分布式系统中&#xff0c;接口幂等性问题&#xff0c;对于开发人员来说&#xff0c;是一个跟语言无关的公共问题。不知道你有没有遇到过这些场景&#xff1a; 有时我们在填写某些form表单时&#xff0c;保存按钮不小心快速点了两次&#xff0c;表中竟然产生了两条重复的数据&a…

AI时代的三类人:探索掌握AIGC,引领未来的人才之路

&#xff08;本文阅读时间&#xff1a;6 分钟&#xff09; 1 AI时代&#xff1a;ChatGPT引领AIGC技术革命 对于那些热衷于探索新技术的小伙伴而言&#xff0c;ChatGPT早已超越了抽象的概念&#xff0c;我们对其能力已有所了解。那么&#xff0c;ChatGPT究竟能够做些什么呢&…

Java 集合全教程

一、集合简介 集合&#xff08;有时称为容器&#xff09;只是将多个元素分组到单个单元中的对象。集合用于存储、检索、操作和传达聚合数据。通常&#xff0c;它们表示形成自然组的数据项&#xff0c;例如扑克手&#xff08;纸牌集合&#xff09;、邮件文件夹&#xff08;字母…

【Java多线程进阶】synchronized工作原理

前言 本期讲解 synchronized 工作的原理以及常见的锁优化机制&#xff0c;相信大家在看完这篇博文后对 synchronized 工作流程有一定的理解。话不多说&#xff0c;让我们快速进入学习吧~ 目录 1. 锁的工作流程 2. 偏向锁 3. 轻量级锁和重量级锁 3.1 轻量级锁 3.2 重量级锁…

Kubernetes基本存储

Kubernetes基本存储 容器的生命周期可能很短&#xff0c;会被频繁地创建和销毁&#xff0c;容器销毁时&#xff0c;保存在容器中的数据也会被清除。为了持久化保存容器中数据&#xff0c;引入Volume概念。 Volume时Pod中多个容器共同访问的共享目录&#xff0c;它被定义在Pod中…