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;
}

  函数 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访问接口了。

猜你喜欢

转载自blog.csdn.net/Xiaoma_Pedro/article/details/131078263