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


    声明

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

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

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

      #define ASHMEM_DEVICE "/dev/ashmem"
      
      • 1

      调用函数 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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

      函数 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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

      函数 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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      函数 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

      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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      函数 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

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

  • 相关阅读:
    guava常见用法整理(不定期更新)
    Oracle函数之聚组函数
    git查看日志
    Web渗透_手动漏洞挖掘
    CSDN竞赛-第六期
    WEB网络渗透的基础知识
    暴雨信息|低碳发展共拓数字能源产业绿色空间
    行业选择有多重要?选Java我没后悔
    关于“兆易创新杯”中国研究生电子设计竞赛的一点个人小经验
    蓝桥杯十四届c/c++省赛:飞机降落、接龙数列(Java版可AC代码)
  • 原文地址:https://blog.csdn.net/Xiaoma_Pedro/article/details/131078263