Android ashmem 原理分析

源码基于:Andoird U + Kernel-5.10

0. 简介

ashmem 称为匿名共享内存(Anonymous Shared Memory),它以驱动程序的形式实现在内核空间中。它有两个特点:

  • 能否辅助内存管理系统来有效地管理不再使用的内存块(pin / unpin);

  • 通过Binder进程间通信机制来实现内存共享;

虽然 Binder机制已经可以实现了跨进程的高效通信,但是Binder 通信所允许的数据是有限制的(如下代码),如果需要大量数据交互就有限制了。

frameworks/native/libs/binder/ProcessState.cpp

//限制了大小为
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)

ashmem 系统大概分三层:

  • Java 层使用 MemoryFile.java 或 SharedMemory.java 来创建ashmem 共享内存;

  • Native 层分两部分:

    • 一部分是从Java 层调下来的 JNI 接口,另外是给Native 层使用的MemoryHeapBase.cpp 文件;

    • 另一部分是ashmem 的lib,实现的函数定义在 ashmem-dev.cpp 文件中;

  • Kernel 层就是 ashmem 的驱动,ashmem 的核心就是通过驱动来管理共享内存;

1. ashmem 驱动原理分析

源码:drivers/staging/android/ashmem.c

  • 用户层都是通过fd 进行mmap 进行映射;

  • 虽然 fd 在不同的进程可能不相同,但其对应的 file 结构是相同的;

  • file 结构中的成员 private_data 指向 ashmem_area,这就是匿名共享内存的核心数据结构;

  • ashmem_area 中的file 是映射的实际文件,通过shmem_file_setup() 函数创建,并指定其fops 为shmem_file_operations,该 fops 也被存在 ashmem 中的静态局部变量 vmfile_fops 中;

  • vmfile_fops 只初始化一次,后面再次调用 ashmem_mmap() 函数时将直接使用;

1.1 ashmem 的重要数据结构和变量

1.1.1 变量 ashmem_misc

static struct miscdevice ashmem_misc = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "ashmem",
        .fops = &ashmem_fops,
};

在 ashmem 驱动初始化的时候会调用 misc_register() 进行注册,详细可以查看 ashmem_init() 函数。

该设备中指定的 file_operations 是 ashmem_fops,如下。

1.1.2 变量 ashmem_fops

static const struct file_operations ashmem_fops = {
        .owner = THIS_MODULE,
        .open = ashmem_open,                    //节点文件open函数
        .release = ashmem_release,              //节点文件结构被释放时会调用release函数
        .read_iter = ashmem_read_iter, 
        .llseek = ashmem_llseek,
        .mmap = ashmem_mmap,                    //节点文件的mmap函数
        .unlocked_ioctl = ashmem_ioctl,         //节点文件的ioctl函数
#ifdef CONFIG_COMPAT
        .compat_ioctl = compat_ashmem_ioctl,    //32位用户系统调用64位驱动的ioctl函数时使用
#endif
#ifdef CONFIG_PROC_FS
        .show_fdinfo = ashmem_show_fdinfo,      //查询/proc/pid/fdinfos/[fd]时打印
#endif
};

compat_ioctl 对于64bit 的驱动必须要实现的ioctl,当有32 bit 的用户层调用 ioctl() 时,会callback 到这里,否则会返回 Not a typewriter 的错误。

64bit 用户层调用 64bit 驱动,或者 32bit 用户层调用 32bit 驱动时,都是callback unlocked_ioctl 函数。

show_fdinfo 函数用于查询 /proc/<pid>/fdinfos/<fd> 时打印,例如:

phone_shift:/ # cat /proc/967/fdinfo/34
pos:    0
flags:  0400002
mnt_id: 45
ino:    1210
inode:  6146
name:   gralloc_shared_memory
size:   2404

1.1.3 struct ashmem_area

struct ashmem_area {
        char name[ASHMEM_FULL_NAME_LEN];    //共享内存区域的名称,在/proc/<pid>/maps中携带
        struct list_head unpinned_list;     //用以串联所有的 ashmem_area
        struct file *file;                  //该共享内存的实际file
        size_t size;                        //该共享内存的大小
        unsigned long prot_mask;            //该共享内存文件的属性,包括r、w、x
};

驱动中通过该数据结构管理所有的共享内存区域,每个共享内存区域都有一个名字,前缀是ASHMEM_NAME_PREFIX (dev/ashmem/),这个名字通过 /proc/<pid>/maps 查看到,例如:

130|phone_shift:/ # cat /proc/967/maps | grep ashmem
76b525e000-76b526e000 rw-s 00000000 00:01 13                             /dev/ashmem/MessageQueue (deleted)
76b5309000-76b5319000 rw-s 00000000 00:01 2057                           /dev/ashmem/MessageQueue (deleted)
76b55c2000-76b55c3000 rw-s 00000000 00:01 3095                           /dev/ashmem/gralloc_shared_memory (deleted)
76b55c4000-76b55c5000 rw-s 00000000 00:01 4099                           /dev/ashmem/gralloc_shared_memory (deleted)

这是 hwc 进程中 ashmem 对应的 vma。在申请ashmem 的时候,都会指定其 name。

每个ashmem 都会在临时文件系统 tmpfs 中对应一个文件,也就是成员变量 file,并且通过 prot_mask 指定文件访问的权限,该 ashmem 在初始化的时候 prot_mask 被指定为 PROT_MASK,后期根据实际情况可以通过接口进行修改。

#define PROT_MASK                (PROT_EXEC | PROT_READ | PROT_WRITE)

ashmem 机制中,需要使用内存块(ashmem_range) 时需要调用ashmem_pin() 函数进行锁定,不被使用的内存会解除锁定。

如果内存块(ashmem_range) 解除锁定,会将这些内存块(ashmem_range) 添加到 asma->unpinned_list 链表中,且添加到 ashmem_lru_list 中便于内存紧张时进行回收。但是两个链表的插入方式不同:

  • asma->unpinned_list 是将 ashmem_range 的地址从大到小串联起来;

  • ashmem_lru_list 是按照LRU 方式将最新的 ashmem_range 插入到链表尾;

1.1.4 struct ashmem_range

struct ashmem_range {
        struct list_head lru;          //串联链表使用
        struct list_head unpinned;     //用以标记是否加入unpinned list
        struct ashmem_area *asma;      //该块内存归属于ashmem_area
        size_t pgstart;                //内存的起始page
        size_t pgend;                  //内存的结尾page,这块内存区间是[pgstart, pgend]
        unsigned int purged;           //标记这块区间是否被回收
};

ashmem_range 可以理解为 ashmem_area 中的内存块。

每个 ashmem_area 会被分成很多的小块(ashmem_range),这些小块会有两种状态:pin 和 unpin。如果该内存块为 unpin,则该内存块会被添加到 ashmem_area 中的 unpinned_list 链表中,通过成员变量 unpinned。

每个小块的内存区间是 [pgstart, pgend]。

1.1.5 struct ashmem_pin

struct ashmem_pin {
        __u32 offset;        /* offset into region, in bytes, page-aligned */
        __u32 len;        /* length forward from offset, in bytes, page-aligned */
};

提供给用户的结构体,标记 pin/unpin 时的内存区域,但是有一定要求:

  • len 可以为0,这样内存空间 ashmem_area 从 offset 之后的所有空间;

  • offset和 len 需要页对齐;

  • offset + len 不能超过 32位无符号数;

  • offset + len 不能超过 ashmem_area 空间;

1.1.6 ashmem_lru_list

/* LRU list of unpinned pages, protected by ashmem_mutex */
static LIST_HEAD(ashmem_lru_list);

static unsigned long lru_count;

在 ashmem_pin() 或这 ashmem_unpin() 中会分配新的 ashmem_range 对象,该结构体中成员 lru 用以串联链表并存放在 ashmem_lru_list 这个全局链表中,便于在内存使用紧张的时候进行回收。

与 ashmem_lru_list 对应的还有 lru_count 用于统计该 list 中页面数量。

1.2 ashmem 初始化

static int __init ashmem_init(void)
{
        int ret = -ENOMEM;

        ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
                                               sizeof(struct ashmem_area),
                                               0, 0, NULL);
        if (!ashmem_area_cachep) {
                pr_err("failed to create slab cache\n");
                goto out;
        }

        ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
                                                sizeof(struct ashmem_range),
                                                0, 0, NULL);
        if (!ashmem_range_cachep) {
                pr_err("failed to create slab cache\n");
                goto out_free1;
        }

        ret = misc_register(&ashmem_misc);
        if (ret) {
                pr_err("failed to register misc device!\n");
                goto out_free2;
        }

        ret = register_shrinker(&ashmem_shrinker);
        if (ret) {
                pr_err("failed to register shrinker!\n");
                goto out_demisc;
        }

        pr_info("initialized\n");

        return 0;

out_demisc:
        misc_deregister(&ashmem_misc);
out_free2:
        kmem_cache_destroy(ashmem_range_cachep);
out_free1:
        kmem_cache_destroy(ashmem_area_cachep);
out:
        return ret;
}
device_initcall(ashmem_init);

代码比较清晰,主要做了几件事:

  • 创建两个 slab cache:ashmem_area_cache ashmem_range_cache,后面struct ashmem_area 和 struct ashmem_range 都是从slab 中分配内存;

  • misc_register() 函数注册 ashmem 设备;

  • register_shrinker() 注册回收函数,当内存不足时系统会通过 shrink_slab() 函数轮训系统中所有调用 register_shrinker() 注册的回收函数;

1.3 ashmem_open()

static int ashmem_open(struct inode *inode, struct file *file)
{
        struct ashmem_area *asma;
        int ret;

        ret = generic_file_open(inode, file);
        if (ret)
                return ret;

        asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
        if (!asma)
                return -ENOMEM;

        INIT_LIST_HEAD(&asma->unpinned_list);
        memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);
        asma->prot_mask = PROT_MASK;
        file->private_data = asma;

        return 0;
}

用户通过 open("/dev/ashmem") 函数进而触发该函数,主要目的是创建一个 ashmem_area 并初始化。

注意三点:

  • ashmem_area 名称初始化就有了,默认是 “/dev/ashmem/”;

  • 默认ashmem_area 对应的共享文件是 PROT_MASK 权限;

  • ashmem_area 会被记录到 file->private_data 中;

1.4 ashmem_release()

static int ashmem_release(struct inode *ignored, struct file *file)
{
        struct ashmem_area *asma = file->private_data;
        struct ashmem_range *range, *next;

        mutex_lock(&ashmem_mutex);
        list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned)
                range_del(range);
        mutex_unlock(&ashmem_mutex);

        if (asma->file)
                fput(asma->file);
        kmem_cache_free(ashmem_area_cachep, asma);

        return 0;
}

当最后一个打开设备的用户执行 close() 系统调用时,内核会调用驱动设定的 fops->release() 函数。对应 ashmem 驱动来说就是调用 ashmem_release() 函数。

主要做了三件事情:

  • 遍历所有的 unpinned_list,将其中的 range 内存都释放掉;

  • fput() 将文件的计数减 1,如果发现 f_count为0了,那么将其对应的struct file结构删除。与其对应的是 fget() 函数,用以获取 struct file 结构并将 f_count 计数加 1;

  • 释放ashmem_area 内存;

1.5 ashmem_mmap()

static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
        static struct file_operations vmfile_fops;
        struct ashmem_area *asma = file->private_data;
        int ret = 0;

        mutex_lock(&ashmem_mutex);

        /* user needs to SET_SIZE before mapping */
        //用户在调用mmap进行映射之前需要先调用 set_size()函数配置大小,否则无法mmap
        if (!asma->size) {
                ret = -EINVAL;
                goto out;
        }

        /* requested mapping size larger than object size */
        //要求ashmem_area的size大于请求mapping的大小
        if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {
                ret = -EINVAL;
                goto out;
        }

        /* requested protection bits must match our allowed protection mask */
        //检测需要映射的vma的保护权限是否超过了ashmem_area的权限
        if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
            calc_vm_prot_bits(PROT_MASK, 0)) {
                ret = -EPERM;
                goto out;
        }
        vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);

        //这是ashmem_mmap()函数的核心处理,创建一个临时文件
        if (!asma->file) {
                char *name = ASHMEM_NAME_DEF;
                struct file *vmfile;
                struct inode *inode;

                //临时文件名默认/dev/ashmem,但如果asma->name已经配置好,则使用asma->name
                if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
                        name = asma->name;

                /* ... and allocate the backing shmem file */
                //利用linux原生的shmem,在tmpfs中创建一个临时文件,该文件只对内核态可见,用户态不可见
                vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
                if (IS_ERR(vmfile)) {
                        ret = PTR_ERR(vmfile);
                        goto out;
                }
                //临时文件初始化
                vmfile->f_mode |= FMODE_LSEEK;
                inode = file_inode(vmfile);
                lockdep_set_class(&inode->i_rwsem, &backing_shmem_inode_class);
                //更新asma->file,此后的共享内存对应临时文件
                asma->file = vmfile;

                //更新临时文件的fops,覆盖掉mmap函数,临时文件mmap不再做任何事情
                //并且,要覆盖掉get_unmapped_area接口,获取进程没有映射的内存空间
                //vmfile_fops为staic,只需要更新一次
                if (!vmfile_fops.mmap) {
                        vmfile_fops = *vmfile->f_op; //拿到vmfile的 fops,准备更新
                        vmfile_fops.mmap = ashmem_vmfile_mmap;
                        vmfile_fops.get_unmapped_area =
                                        ashmem_vmfile_get_unmapped_area;
                }
                //每次创建的临时文件的fops都使用vmfile_fops
                vmfile->f_op = &vmfile_fops;
        }
        get_file(asma->file);

        //如果该vma已经处于共享状态,调用shmem_zero_setup()配置vma->vm_ops为shmem_vm_ops
        //vma->vm_file在后面会更新掉
        if (vma->vm_flags & VM_SHARED) {
                ret = shmem_zero_setup(vma);
                if (ret) {
                        fput(asma->file);
                        goto out;
                }
        } else {
                vma_set_anonymous(vma);
        }

        //vma->vm_file将指定到asma->file,即指定到创建好的临时vmfile
        if (vma->vm_file)
                fput(vma->vm_file);
        vma->vm_file = asma->file;

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

当第一次mmap 时,会通过 shmem_file_setup() 在tmpfs 文件系统中创建一个临时文件(也许只是内核中的一个inode 节点)。该临时文件 vmfile 创建好之后会覆盖 asma->file,至此ashmem 与该vmfile 对应,ashmem 机制真正使用的map 对象就是该临时文件vmfile。

1.6 ashmem_ioctl()

涉及的命令有:

drivers/staging/android/uapi/ashmem.h

#define __ASHMEMIOC                0x77

#define ASHMEM_SET_NAME                _IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN])
#define ASHMEM_GET_NAME                _IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN])
#define ASHMEM_SET_SIZE                _IOW(__ASHMEMIOC, 3, size_t)
#define ASHMEM_GET_SIZE                _IO(__ASHMEMIOC, 4)
#define ASHMEM_SET_PROT_MASK           _IOW(__ASHMEMIOC, 5, unsigned long)
#define ASHMEM_GET_PROT_MASK           _IO(__ASHMEMIOC, 6)
#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)
#define ASHMEM_PURGE_ALL_CACHES        _IO(__ASHMEMIOC, 10)

具体实现可以查看源码,主要来看下 pin 和 unpin:

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_PIN:
        case ASHMEM_UNPIN:
        case ASHMEM_GET_PIN_STATUS:
                ret = ashmem_pin_unpin(asma, cmd, (void __user *)arg);
                break;
        }
        
        return ret;
}

1.7 ashmem_pin_unpin()

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/unpin时,会传递ashmem_pin类型的参数
        if (copy_from_user(&pin, p, sizeof(pin)))
                return -EFAULT;

        //当调用pin/unpin接口时,创建一个ashmem_range对象
        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));

        //如果临时文件vmfile还没有创建好,无法进行pin/unpin操作
        if (!asma->file)
                goto out_unlock;
        //pin.len的值可以设为0,即从pin.offset之后的全部内存
        if (!pin.len)
                pin.len = PAGE_ALIGN(asma->size) - pin.offset;
        //偏移地址和大小要求页对齐
        if ((pin.offset | pin.len) & ~PAGE_MASK)
                goto out_unlock;
        //需要操作内存末尾地址不能超出
        if (((__u32)-1) - pin.offset < pin.len)
                goto out_unlock;

        //需要操作内存末尾地址不能超过ashmem_area指定的size
        if (PAGE_ALIGN(asma->size) < pin.offset + pin.len)
                goto out_unlock;

        //操作区间需要页对齐
        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;
}

pin/unpin 对于 offset 和 len 有一定的要求:

  • len 可以为0,这样内存空间 ashmem_area 从 offset 之后的所有空间;

  • offset和len 需要页对齐;

  • offset + len 不能超过 32位无符号数;

  • offset + len 不能超过 ashmem_area 空间;

ashmem 机制中,正在使用的 ashmem_range 需要 pin,不被使用的 ashmem_range 需要 unpin。unpin 的ashmem_range 会添加到 asma->unpinned_list 链表中,且该 ashmem_range 会被添加到ashmem_lru_list 中。

pin 和 unpin 只是改变相关状态标记,并不会改变已经 mapping 的地址空间,因此,用户可以在unpin 后重新pin 住内存块。

注意:

函数的返回值为实际处理函数的范围值,有两种情况:ASHMEM_NOT_PURGED 和ASHMEM_WAS_PURGED。ASHMEM_NOT_PURGED 表示该内存块物理内存没有被回收。

1.7.1 ashmem_unpin()

static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend,
                        struct ashmem_range **new_range)
{
        struct ashmem_range *range, *next;
        unsigned int purged = ASHMEM_NOT_PURGED;

restart:
        list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) {
                /* short circuit: this is our insertion point */
                if (range_before_page(range, pgstart))
                        break;

                /*
                 * The user can ask us to unpin pages that are already entirely
                 * or partially pinned. We handle those two cases here.
                 */
                if (page_range_subsumed_by_range(range, pgstart, pgend))
                        return 0;
                if (page_range_in_range(range, pgstart, pgend)) {
                        pgstart = min(range->pgstart, pgstart);
                        pgend = max(range->pgend, pgend);
                        purged |= range->purged;
                        range_del(range);
                        goto restart;
                }
        }

        range_alloc(asma, range, purged, pgstart, pgend, new_range);
        return 0;
}

如 struct ashmem_area 中所述,一块匿名共享内存中的所有解锁内存块,都是按照地址从大到小的顺序保存在 unpinned_list 中。

该函数的目的是将该内存块(ashmem_range) 添加到 asma->unpinned_list 和 ashmem_lru_list 链表中。

当内存块插入到 asma->unpinned_list 时会考虑内存合并,有如下几种处理方式(绿色是old,红色是new):

  • A:新添加的内存块地址大于最大range,将直接添加到asma->unpinned_list 头部;

  • B:新添加的内存块完全处于某range 中,不做任何处理,原先的range 已经unpinned 了;

  • C、D、E:三种情况是合并的情况,都会将原先range 与新的range 进行合并,并将原先range删掉,保留新的range;

1.7.2 ashmem_pin()

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;

                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.
                         */
                        range_alloc(asma, range, range->purged,
                                    pgend + 1, range->pgend, new_range);
                        range_shrink(range, range->pgstart, pgstart - 1);
                        break;
                }
        }

        return ret;
}

ashmem机制中的一个内存块,最开始一定处于锁定状态,被解锁之后会被放入到 asma->unpinned_list 中。该函数的目的是遍历该 unpinned_list,寻找和指定的pgstart和pgend 相交或包含的内存块,对其进行重新锁定。

同ashmem_unpinned(),仍然将处理方式分为如下几种(绿色是old,红色是new):

A:需要pinned 的内存块不在 asma->unpinned_list 中,那不用过多考虑;

B:需要pinned 的内存块完全包含range中,继续轮询确认是否与其他range有交叉;

C、D:并不会删除range 后再重新分配,而是直接修改 range->pgstart 和 range->pgend,并相应的减少 LRU list中的页面数量;

E:这种情况比较特殊,会将range 切成两块,[range->pgstart, pgstart) 算在原来的range 中,(pgend, range->pgend] 算到new range中;

2. ashmem 在进程间共享的原理

假设进程 A 调用 open() 函数打开 /dev/ashmem,这样会得到一个匿名共享内存的一个struct file 和文件描述符,假如是 file1 和 fd1. 然后进程 B 通过 Binder 进程间通信机制请求进程 A 将fd1 返回给它,但 fd1 只在进程 A 中有效,因此 Binder 驱动程序在进程B 中创建一个新的描述符 fd2,使得fd2 也指向 file1,最后再将 fd2 返回给进程 B。这样描述符 fd1 和 fd2 就指向同一个文件结构体 file1,即指向同一个匿名共享内存。

3. ashmem 注意点

  • ashmem 机制相当于Linux 共享内存的扩展,扩展后使用更加便捷;

  • Android 中通过 binder 机制将 ashmem 的 fd 进行传递,增加安全性,同时避免了 buffer 拷贝,效率提升;

  • ashmem 不会占用Dalvik heap 和 Native heap,所以不会导致 OOM;

  • ashmem 占用空间的计算,是计算到第一个创建它的进程中,其他进程不会将 ashmem 计算在内;

参考:

https://www.kancloud.cn/alex_wsc/androids/477718

https://blog.csdn.net/vviccc/article/details/123237169

https://blog.csdn.net/run068/article/details/121695036

https://www.cjcbill.com/2019/04/15/android-ashmem/

https://blog.51cto.com/u_9420214/6331901

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式学习-PWM输出比较

简介 PWM技术 输出比较框图介绍 定时器部分 比较器控制部分 输出控制部分 相关寄存器

Linux:文件、fd

Linux:文件、fd 前言一、C语言中常见打开文件的函数接口二、打开文件的系统调用接口三、文件描述符fd四、为何Linux下一切皆文件 前言 文件 内容 属性 所有对文件的操作本质上就分为&#xff1a;对内容的修改和对属性的修改。  内容是数据&#xff0c;属性也是数据。所以存…

web自动化系列-使用普通模式编写测试用例以及存在问题(十六)

前面已经把selenium的主要操作介绍完毕 &#xff0c;接下来我们通过编写几条测试用例感受下selenium的用法 。 1.用例需求 还是以登录为例 &#xff0c;需要实现的测试用例为 &#xff1a; case1&#xff1a;输入正确的用户名和密码进行登录case2 : 输入正确的用户名和错误的…

小红书“脆皮”用户健康研究报告

人均脆皮、血脉觉醒、爆肝打工、脱发危机……各式各样的健康议题不断被推上生活舞台&#xff0c;年轻人纷纷自嘲&#xff1a;“20多岁的年纪&#xff0c;却有了60多岁的身体”。 近年&#xff0c;大健康行业欣欣向荣&#xff0c;小红书成为大众分享健康生活的聚集地&#xff0c…

Python 小抄

Python 备忘单 目录 1.语法和空格 2.注释 3.数字和运算 4.字符串处理 5.列表、元组和字典 6.JSON 7.循环 8.文件处理 9.函数 10.处理日期时间 11.NumPy 12.Pandas 要运行单元格&#xff0c;请按 ShiftEnter 或单击页面顶部的 Run&#xff08;运行&#xff09;。 1.语法和空格…

关于 vs2019 c++20 规范里的一个全局函数 _Test_callable

&#xff08;1&#xff09;看名思议&#xff0c;觉得这个函数可以测试其形参是否是可以被调用的函数&#xff0c;或可调用对象&#xff1f; 不&#xff0c;这个名字不科学。有误导&#xff0c;故特别列出。看下其源码&#xff08;该函数位于 头文件&#xff09;&#xff1a; 辅…

50.乐理基础-拍号的类型-混合拍子

混合拍子的定义&#xff1a; 1.由不同的单拍子组合起来的&#xff0c;如图1。 2.因为组合顺序有多种可能&#xff0c;所以次强拍的位置也有多种可能&#xff0c;如图3。 图1&#xff1a;四二拍是单拍子&#xff0c;四三拍也是单拍子&#xff0c;四二拍 与 四三拍就是 不同的单拍…

Google Ads被暂停的原因,如何防范?

跨境出海业务少不了需要做Google Ads推广业务&#xff1b;其中让投手们闻风丧胆的消息就是帐户被暂停。当 Google 检测到任何违反其政策且可能损害用户在线体验的行为时&#xff0c;就会发生这种情况。那么如何在做广告推广的同时&#xff0c;保证账号不被封禁呢&#xff1f;看…

59.基于SSM实现的网上花店系统(项目 + 论文)

项目介绍 本站是一个B/S模式系统&#xff0c;网上花店是在MySQL中建立数据表保存信息&#xff0c;运用SSMVue框架和Java语言编写。并按照软件设计开发流程进行设计实现充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SSM的网…

Springboot+MybatisPlus如何实现带验证码的登录功能

实现带验证码的登录功能由两部分组成&#xff1a;&#xff1a;1、验证码的获取 2、登录&#xff08;进行用户名、密码和验证码的判断&#xff09; 获取验证码 获取验证码需要使用HuTool中的CaptchaUtil.createLineCaptcha()来定义验证码的长度、宽度、验证码位数以及干扰线…

算术平均数

算术平均数&#xff08;average&#xff09;是一组数据相加后除以数据的个数而得到的结果&#xff0c;是度量数据水平的常用统计量&#xff0c;在参数估计和假设检验中经常用到。比如&#xff1a;用职工平均工资来衡量职工工资的一般水平&#xff0c;用平均体重来观察某一人群体…

uac驱动之const修饰的变量和const修饰的指针

const int*p // p所指向的空间是常量 不可修改 ,但p可以修改 int*const p // p所指向的空间是可以修改 ,p不可以修改 #include <stdio.h> #include <string.h>struct usb_string {char id;const char *s; };enum {STR_ASSOC,STR_AC_IF,STR_USB_OUT_IT,STR_USB_O…

4种企业防泄密的办法,强烈推荐第二种

4种企业防泄密的办法&#xff0c;强烈推荐第二种 企业信息泄密常见的原因有内部人员、黑客、违规收集信息、第三方合作商&#xff0c;以下将为你详细分析这些泄密原因以及应对的方法。 1、内部人员泄密 内部员工由于能够接触到敏感数据&#xff0c;成为主要的泄露数据群体。这…

2024年中国国际厨卫家居展览会(上海KIB厨卫展)

中国国际厨卫家居博览会&#xff08;KIB&#xff09;由中国五金制品协会、中国国际贸易促进委员会轻工行业分会、北京奥维云网大数据科技股份有限公司主办。从最初的“中国国际橱柜、厨房卫浴产品与技术博览会(CIKB&#xff09;”&#xff0c;到2001年与中国国际五金展&#xf…

【React】 打包扫描出现高风险文件 YUI 版本太低 JSEncrypt

漏洞定位 扫出漏洞的情况&#xff0c;多是在说下面几个工具&#xff1a; jquery js-cookie jsencrypt 参考链接 YUI:2.9.0 (Link) http://www.cvedetails.com/cve/CVE-2012-5883/ 1.于是在打包后的代码中搜索 YUI&#xff08;不区分大小写&#xff0c;不进行全字匹配&…

数据结构初阶 顺序表的补充

一. 题目的要求 写出三种链表的接口函数 它们的功能分别是 1 查找数的位置 2 在pos位置插入值 3 在pos位置删除值 二. 实现pos 这个其实很简单 找到一步步遍历 找到这个数字就返回 找不到就提示用户下 这个数字不存在 int SLFind(SL* ps,SLDateType x) {assert(ps);int…

27_Scala功能函数

文章目录 功能函数1.功能函数处理集合数据2.扁平化操作3.按照指定条件将数据集中的数据进行过滤4.集合通过 自定义函数进行分组5.mapValues6.sortBy函数 功能函数 1.功能函数处理集合数据 –集合的功能函数 map List --> map( logical ) --> newList–实现一个不确定的…

【Arduino】Free RTOS系统

目录 1、任务创建 2、任务删除 3、延迟函数 4、示例&#xff1a; ESP32的SDK包中内置了FreeRTOS&#xff0c;在FreeRTOS中&#xff0c;线程&#xff08;Thread&#xff09;和任务&#xff08;Task&#xff09;的概念是相同的。每个任务就是一个线程&#xff0c;有着自己的一…

QT实现Home框架的两种方式

在触摸屏开发QT界面一般都是一个Home页面&#xff0c;然后button触发进入子页面显示&#xff0c;下面介绍这个home框架实现的两种方式&#xff1a; 1.方式一&#xff1a;用stackedWidget实现 &#xff08;1&#xff09;StackedWidget控件在Qt框架中是一个用于管理多个子窗口或…

【多模态】30、GPT4V_OCR | GPT4V 在 OCR 数据集上效果测评

文章目录 一、背景二、测评2.1 场景文本识别2.2 手写文本识别2.3 手写数学公式识别2.4 图表结构识别&#xff08;不考虑单元格中的文本内容&#xff09;2.5 从内容丰富的文档中抽取信息 三、讨论 论文&#xff1a;EXPLORING OCR CAPABILITIES OF GPT-4V(ISION) : A QUANTITATIV…