Android匿名共享内存(Ashmem)

在Android中我们熟知的IPC方式有Socket文件ContentProviderBinder共享内存。其中共享内存的效率最高,可以做到0拷贝,在跨进程进行大数据传输,日志收集等场景下非常有用。共享内存是Linux自带的一种IPC机制,Android直接使用使用了该模型,不过做出了自己的改进,进而形成了Android的匿名共享内存。

本文将会通过android提供的MemoryFile源码来分析如何使用匿名共享内存,并使用native层代码实现一个简易版的MemoryFile。

MemoryFile简单使用

//MainActivity.kt 进程1
class MainActivity : AppCompatActivity() {
     var mBinder: Binder? = null
     val memoryFile:MemoryFile? = null
     private var mConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            mBinder = service as Binder
        }

        override fun onServiceDisconnected(className: ComponentName) {
            mBinder = null
        }
    }

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val intent = Intent(this, TestShareMemoryService::class.java)
        startService(intent)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }
    //1.创建共享内存,并通过binder传递文件描述符
	fun createMemoryFile(view: View) {
        //参数1文件名可为null,参数2文件大小
        memoryFile = MemoryFile("test", 1024)
        memoryFile?.apply {
            mBinder?.apply {
                val data = Parcel.obtain()
                val reply = Parcel.obtain()
                val getFileDescriptorMethod: Method =
                memoryFile.getClass().getDeclaredMethod("getFileDescriptor")
            	val fileDescriptor = getFileDescriptorMethod.invoke(memoryFile)
            	// 序列化,才可传送
            	val pfd = ParcelFileDescriptor.dup(fileDescriptor)
                data.writeFileDescriptor(fileDescriptor)
                transact(TestShareMemoryService.TRANS_CODE_SET_FD, data, reply, 0)
            }
        }

    }
    //2.写入数据
	fun write(data:ByteArray) {
    	memoryFile.write(data, 0, 0, data.size);
	}
}

//MainActivity2.kt 进程2
class MainActivity2 : AppCompatActivity() {
    var mBinder: IBinder? = null
    private var mConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            mBinder = service
        }

        override fun onServiceDisconnected(className: ComponentName) {
            mBinder = null
        }
    }

    fun read(view: View) {
        val data = Parcel.obtain()
        val reply = Parcel.obtain()
        mBinder?.apply {
            //从服务端获取MainActivity传递的文件描述符
            transact(TestShareMemoryService.TRANS_CODE_GET_FD, data, reply, 0)
            var fi: FileInputStream? = null
        var fileDescriptor: FileDescriptor? = null
        try {
                val pfd = reply.readFileDescriptor()
                if (pfd == null) {
                    return
                }
                fileDescriptor = pfd.fileDescriptor
                fi = FileInputStream(fileDescriptor)
            	//读取数据
                fi.read(buffer)
            }
        } catch (e: RemoteException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            if (fileDescriptor != null) {
                try {
                    fi.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }
            }
        }
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        val intent = Intent(this, TestShareMemoryService::class.java)
        startService(intent)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }

}
//TestShareMemoryService.kt 
class TestShareMemoryService : Service() {
    lateinit var fd: ParcelFileDescriptor

    companion object {
        const val TRANS_CODE_GET_FD = 0x0000
        const val TRANS_CODE_SET_FD = 0x0001
    }

    override fun onBind(intent: Intent?): IBinder {
        return TestBinder()
    }

    inner class TestBinder : Binder() {

        override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
            when (code) {
                TRANS_CODE_SET_FD -> {
                    //保存创建共享内存进程传递过来的文件描述符
                    fd = data.readFileDescriptor()
                }
                TRANS_CODE_GET_FD -> {
                    //将文件描述符传递给请求的进程
                    reply?.writeFileDescriptor(fd.fileDescriptor)
                }
            }
            return true
        }
    }
}

 

梳理一下流程

  • 1、进程1创建MemoryFile并写入数据
  • 2、通过Binder将MemoryFile的文件描述符传递到进程2
  • 3、进程2通过获取到的文件描述符进行数据的读写

这里流程中的第二步有一个问题,从进程1将文件描述符传递到进程2,那么这两个进程的文件描述符是同一个吗?

答案是这两个文件描述符并不是同一个,只不过他们都指向了内核中的同一个文件。

文件描述符

linux系统中的文件描述符是什么?在回答这个问题前先来看一下linux系统中进程是什么?

在linux系统中进程实际上就是一个结构体,而且线程和进程使用的是同一个结构体,其部分源码如下:

struct task_struct {
	// 进程状态
	long			  state;
	// 虚拟内存结构体
	struct mm_struct  *mm;
	// 进程号
	pid_t			  pid;
	// 指向父进程的指针
	struct task_struct __rcu  *parent;
	// 子进程列表
	struct list_head		children;
	// 存放文件系统信息的指针
	struct fs_struct		*fs;
	// 一个数组,包含该进程打开的文件指针
	struct files_struct		*files;
};

可以看到在结构体中有一个files字段,它记录着该进程打开的文件指针,而我们所说的文件描述符实际上就是这个files数组的索引,他们的关系如下图所示:

为了画图方便,这里将fd1fd2都写成了1,实际上每个进程被创建时,files的前三位被填入默认值,分别指向标准输入流、标准输出流、标准错误流。所以进程的文件描述符默认情况下 0 是输入,1 是输出,2 是错误。

从图中可以看出fd1fd2 其实并没有直接的关系,那么进程2是如何通过进程1的fd1 来生成一个同fd1 指向同一个 文件呢?

回想一下我们是怎么把fd1 转成fd2 的,是通过Binder#transact 方法实现的,因此我们来看一下Binder 的源码是如何做的

//Binder.c

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply) {
    
    switch(fp->type) {
            case BINDER_TYPE_FD: {
			int target_fd;
			struct file *file;
			
			// 通过进程1的fp->handle获取到真正的文件,在内核中是唯一的fd指向它
			file = fget(fp->handle);
			// 获取目标进程中未使用的文件描述符fd
			target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
			// 将目标进程的文件描述符fd和该file进行配对,这样目标进程就能通过target_fd找到file
			task_fd_install(target_proc, target_fd, file);
			
		} break;
    }
}

看了源码我们发现原理非常简单,其实就是通过内核中的Binder帮我们进行转换的,因为内核是有所有用户进程信息,所以它可以轻松的做到这一点。

还有一点需要说明的是,在上图中的file1,file2,file3并不一定是存在磁盘上的物理文件,也有可能是抽象的文件(虚拟文件),而本篇文章说的匿名共享内存 实际上就是映射到一个虚拟的文件,至于这块的内容可以看一下Linux的tmpfs文件系统 。

MemoryFile源码解析

共享内存的基础知识上面做了简单的介绍,现在来看看Android是如何做的。MemoryFile 是Android提供的java层匿名共享内存工具,通过它的源码来跟踪整个流程。

相关文件列表:

frameworks/base/core/java/android/os/
	- MemoryFile.java
	- SharedMemory.java
frameworks/base/core/jni/android_os_SharedMemory.cpp
system/core/libcutils/ashmem-dev.cpp
//MemoryFile.java
public MemoryFile(String name, int length) throws IOException {
            //通过SharedMemory创建匿名共享内存
            mSharedMemory = SharedMemory.create(name, length);
            //映射
            mMapping = mSharedMemory.mapReadWrite();    
    }
//SharedMemory
public static @NonNull SharedMemory create(@Nullable String name, int size)
            throws ErrnoException {
    	//实际上调用了native层去创建匿名共享内存,并返回文件描述符
        return new SharedMemory(nCreate(name, size));
    }

private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;

 

//android_os_SharedMemory.cpp
jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
    const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;
    //调用ashmem_create_region来创建匿名共享内存
    int fd = ashmem_create_region(name, size);
	//...
    jobject jifd = jniCreateFileDescriptor(env, fd);
    if (jifd == nullptr) {
        close(fd);
    }
    return jifd;
}

 

// ashmem-dev.cpp
int ashmem_create_region(const char *name, size_t size)
{
    int ret, save_errno;
	//打开匿名共享内存对应的虚拟文件,最终调用到 __ashmem_open_locked()
    int fd = __ashmem_open();
    if (fd < 0) {
        return fd;
    }
    if (name) {
        char buf[ASHMEM_NAME_LEN] = {0};
        strlcpy(buf, name, sizeof(buf));
        //通过ioctl设置名字,TEMP_FAILURE_RETRY宏定义会让返回的结果为false时一直重试
        //ioctl是系统调用,用户进程和内存进行交互,内部调用copy_from_user获取到用户进程传递的数据
        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;
}

static std::string get_ashmem_device_path() {
    static const std::string boot_id_path = "/proc/sys/kernel/random/boot_id";
    std::string boot_id;
    if (!android::base::ReadFileToString(boot_id_path, &boot_id)) {
        ALOGE("Failed to read %s: %s.\n", boot_id_path.c_str(), strerror(errno));
        return "";
    };
    boot_id = android::base::Trim(boot_id);
    return "/dev/ashmem" + boot_id;
}

static int __ashmem_open_locked()
{
    //获取匿名共享内存路径,Android Q之后使用这个方式获取
    static const std::string ashmem_device_path = get_ashmem_device_path();
    if (ashmem_device_path.empty()) {
        return -1;
    }
	//打开匿名共享内存使用的虚拟文件
    int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC));
    // Android Q之前的设备这里fd < 0,使用原来的路径"/dev/ashmem"
    if (fd < 0) {
        int saved_errno = errno;
        //打开匿名共享内存使用的虚拟文件
        fd = TEMP_FAILURE_RETRY(open("/dev/ashmem", O_RDWR | O_CLOEXEC));
        if (fd < 0) {
            return fd;
        }
    }
    //...
    return fd;
}

以上是获取匿名共享内存的文件描述符流程,总结一下核心的部分,只例举Android Q之前:

  • 1、open("/dev/ashmem", O_RDWR | O_CLOEXEC),打开虚拟文件
  • 2、ioctl(fd, ASHMEM_SET_NAME, buf),设置名字
  • 3、ioctl(fd, ASHMEM_SET_SIZE, size),设置大小

接下来来看一下如何通过文件描述符 映射到共享内存中

如上面分析的代码,在MemoryFile的构造函数中先调用了SharedMemory#create(name, size)方法创建了匿名文件,之后调用SharedMemory.mapReadOnly() 来将匿名文件映射到共享内存中,最终调用到了如下方法中:

//SharedMemory.java
 public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
        //通过mFileDescriptor文件描述符进行内存映射,并返回内存地址
        long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
        boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
     	//取消内存映射的Runnable,run方法中会调用Os.munmap(mAddress, mSize);
        Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
        //使用DirectByteBuffer直接对内存进行读写操作
        return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
    }

如果对linux系统熟悉的话看到Os.mmap()Os.munmap() 方法应该能知道内存映射实际上就是调用的linux系统函数mmapmunmap 函数,看一下man手册中的介绍

mmap, munmap - map or unmap files or devices into memory

  • mmap,映射文件或设备到内存中
  • munmap ,取消文件或设备到内存的映射

实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建 立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回 文件的。因此,采用共享内存的通信方式效率是非常高的。

看一下官方的注释,放了个连接,打开一看果然调用的mmap

//Os.java
	/**
     * See <a href="http://man7.org/linux/man-pages/man2/mmap.2.html">mmap(2)</a>.
     */
    public static long mmap(long address, long byteCount, int prot, int flags, FileDescriptor fd, long offset) throws ErrnoException { return Libcore.os.mmap(address, byteCount, prot, flags, fd, offset); }

至此,MemoryFile 源码的核心可以说是分析完了。

最后,稍微提一下linux内存映射的原理:

linux下,内存采用分页存储,一个物理页的大小是4k(即你理解的内存块),物理页有页号,如果a,b两个进程共享了8k的内存,比如代码区相同,则在双方进程的页表中(线性地址到物理地址的转换表,linux下逻辑地址和线性地址相同)会将各自的线性地址映射到那两个相同的物理页面上去。实际上内存就只有一份数据。

native实现一个简易版MemoryFile

现在来自定义一个MemoryFile,用到核心方法:

open(ASHMEM_NAME_DEF, O_RDWR);
mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ioctl(fd, ASHMEM_SET_NAME, name);
ioctl(fd, ASHMEM_SET_SIZE, size);
munmap((void *) addr, size);

第一步我们先把api接口定义出来,代码如下

class MyShareMemory(fd: Int) {
    private val mFd: Int = fd
    private val mSize: Int

    init {
        mSize = nGetSize(mFd)
        require(mSize > 0) { "FileDescriptor is not a valid ashmem fd" }
    }
	//获取可以使用Binder传输文件描述符的对象,用于跨进程传输文件描述符
    fun getFileDescriptor(): FileDescriptor {
        return ParcelFileDescriptor.fromFd(mFd).fileDescriptor;
    }

    companion object {
        init {
            System.loadLibrary("mysharememory-lib")
        }
        
        fun create(name: String, size: Int): MyShareMemory {
            require(size > 0) { "Size must be greater than zero" }
            return MyShareMemory(nCreate(name, size))
        }
		//创建需要映射的匿名文件
        @JvmStatic
        private external fun nCreate(name: String, size: Int): Int
		//获取大小
        @JvmStatic
        private external fun nGetSize(fd: Int): Int
		//关闭文件并解除映射
        @JvmStatic
        private external fun nClose(fd: Int)
		//写数据,这里的offset只设置了destOffset,没有写srcOffset,可以完善,nRead同理
        @JvmStatic
        private external fun nWrite(fd: Int, size: Int, offset: Int, data: ByteArray): Int
		//读数据
        @JvmStatic
        private external fun nRead(fd: Int, size: Int, offset: Int, data: ByteArray): Int

    }
}

接下来来实现这5个jni方法

extern "C"
JNIEXPORT jint JNICALL
Java_com_baidu_ryujin_ktc_MyShareMemory_nCreate(JNIEnv *env, jclass clazz, jstring name,
                                                jint size) {
    char *addr;
    int64_t ufd = 0;
    const char *_name = env->GetStringUTFChars(name, 0);
    //打开匿名文件并进行映射,addr为映射内存的地址,ufd为文件描述符
    int ret = create_shared_memory(_name, size, addr, ufd);
    env->ReleaseStringUTFChars(name, _name);
    return ufd;
}extern "C"
JNIEXPORT jint JNICALL
Java_com_baidu_ryujin_ktc_MyShareMemory_nGetSize(JNIEnv *env, jclass clazz,
                                                 jint fd) {
    return get_shared_memory_size(fd);
}extern "C"
JNIEXPORT void JNICALL
Java_com_baidu_ryujin_ktc_MyShareMemory_nClose(JNIEnv *env, jclass clazz, jint fd) {
    char *addr;
    //这里调用open去映射内存是为了获取addr,因为取消映射需要用到,这里是为了方便这么做,实际使用中可以保存起来
    open_shared_memory(addr, fd);
    close_shared_memory(fd, addr);
}extern "C"
JNIEXPORT jint JNICALL
Java_com_baidu_ryujin_ktc_MyShareMemory_nWrite(JNIEnv *env, jclass clazz, jint fd,
                                               jint size, jint offset, jbyteArray data_) {
    char *addr;
    int space = get_shared_memory_size(fd) - offset;
    if (size - space > 0) {
        return -1;
    }
    //同close一样,这里也是为了获取addr
    open_shared_memory(addr, fd);
    jbyte *data = env->GetByteArrayElements(data_, 0);
    //获取到共享内存地址后直接往里面写数据就行了
    memcpy(addr + offset, data, size);
    env->ReleaseByteArrayElements(data_, data, 0);
    return 0;
}extern "C"
JNIEXPORT jint JNICALL
Java_com_baidu_ryujin_ktc_MyShareMemory_nRead(JNIEnv *env, jclass clazz, jint fd, jint size,
                                              jint offset, jbyteArray data_) {
    //...
    return 0;
}

核心实现代码

int create_shared_memory(const char *name, int64_t size, char *&addr, int64_t &fd) {
    fd = open(ASHMEM_NAME_DEF, O_RDWR);//#define ASHMEM_NAME_DEF "dev/ashmem"
    if (fd < 0) {
        return -1;
    }
    int len = get_shared_memory_size(fd);
    if (len > 0) {//改fd已经映射,直接获取地址
        addr = (char *) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        return 1;
    } else {//未映射
        int ret = ioctl(fd, ASHMEM_SET_NAME, name);//设置名称
        if (ret < 0) {
            close(fd);
            return -1;
        }
        ret = ioctl(fd, ASHMEM_SET_SIZE, size);//设置大小
        if (ret < 0) {
            close(fd);
            return -1;
        }
        //内存映射
        addr = (char *) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    }
    return 0;
}

int open_shared_memory(char *&addr, int64_t fd) {
    int size = get_shared_memory_size(fd);
    if (size > 0) {
        addr = (char *) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    } else {
        return -1;
    }
    return 0;
}

int close_shared_memory(int64_t fd, char *&addr) {
    int size = get_shared_memory_size(fd);
    if (size < 0) {
        return -1;
    }
    //取消映射
    int ret = munmap((void *) addr, size);
    if (ret == -1) {
        return -1;
    }
    ret = close(fd);
    if (ret == -1) {
        return -1;
    }
    return 0;
}

int get_shared_memory_size(int64_t fd) {
    return ioctl(fd, ASHMEM_GET_SIZE, NULL);
}

现在就可以像MemoryFile一样使用自定义的MemoryFile进行跨进程数据传输了,具体的可以github上的demo: https://github.com/GhRyuJin/CustomAnroidShareMemory。

最后讨论一下两个问题,以下仅为个人思考,欢迎补充和指正:

一、Android为什么设计一个匿名共享内存,共享内存不能满足需求吗?

首先我们来思考一下共享内存和Android匿名共享内存最大的区别,那就是共享内存往往映射的是一个硬盘中真实存在的文件,而Android的匿名共享内存映射的一个虚拟文件。这说明Android又想使用共享内存进行跨进程通信,又不想留下文件,同时也不想被其它的进程不小心打开了自己进程的文件,因此使用匿名共享内存的好处就是:

  1. 不用担心共享内存映射的文件被其它进程打开导致数据异常。
  2. 不会在硬盘中生成文件,使用匿名共享内存的方式主要是为了通信,而且通信是很频繁的,不希望因为通信而生成很多的文件,或者留下文件。

二、为什么叫匿名共享内存?明明通过iotc设置了名字的?

这个问题在我看来是我之前对匿名这个词有些误解,其实匿名并不是没有名字,而是无法通过这些明面上的信息找到实际的对象,就像马甲一样。匿名共享内存也正是如此,虽然我们设置了名字,但是另外的进程通过同样的名字创建匿名共享内存却并不指向同一个内存了(代码验证过),虽然名字相同,但是背后的人却已经换了。这同时也回答上个问题,为什么匿名共享内存不用担心被其它进程映射进行数据读写(除非经过自己的同意,也就是通过binder传递了文件描述符给另一个进程)。


Android系统共享内存

1.共享内存简介

共享内存是进程间通讯的一种方式,通过映射一块公共内存到各自的进程空间来达到共享内存的目的。
通常进程内存空间是4G,这个大小是由内存指针长度决定的,如果指针长度32位,那么地址最大编号为0xffffffff, 为4G。
上面的内存实际指的是进程的虚拟地址空间,还需要经过内存映射才能访问到真实的物理内存,这些工作对用户是透明的,不需要用户关心,操作系统都已经帮我们做好了。
通常虚拟内存地址和物理内存地址,但是存在一种对应关系。比如,进程操作的0x12345561这块内存地址,经过OS映射之后,可能实际的物理地址是0x87888312。
下图说明了虚拟内存与物理内存之间的关系。

两个不同的进程可以同时访问同一块内存吗?答案是肯定的。这就是内存共享,该机制由操作系统提供和实现。那么是如何做到的呢? Android平台上内存共享通常按如下做法实现:

  1. 进程A创建并打开一个文件(可以是设备文件/dev/ashmem),得到一个文件描述符fd.
  2. 通过mmap调用将fd映射成内存映射文件。在mmap调用中指定参数用于标识创建的是共享内存。
  3. 进程B打开同一个文件,也得到一个文件描述符,这样A和B就打开了同一个文件。
  4. 进程B也要用mmap调用指定参数表示想使用共享内存,并传递打开的fd。这样A和B就通过打开同一个文件并构造内存映射,实现进程间内存共享。

对于进程间需要传递大量数据的场景下,这种通信方式是十分高效的。

2. MemoryHeapBase与MemoryBase

Android在Native层通过MemoryHeapBase与MemoryBase两个类实现共享内存。

class AudioTrackJniStorage {
    public:
        sp<MemoryHeapBase>         mMemHeap;
        sp<MemoryBase>             mMemBase;

.......
~AudioTrackJniStorage() {
        mMemBase.clear();
        mMemHeap.clear();
    }

bool allocSharedMem(int sizeInBytes) {
		//先new一个MemoryHeapBase,再以它为参数new一个MemoryBase
		//(1) MemoryHeapBase
        mMemHeap = new MemoryHeapBase(sizeInBytes, 0, "AudioTrack Heap Base");
        if (mMemHeap->getHeapID() < 0) {
            return false;
        }
        //(2) MemoryBase
        mMemBase = new MemoryBase(mMemHeap, 0, sizeInBytes);
        return true;
    }


MemoryHeapBase与MemoryBase 类关系继承图如下

MemoryHeapBase是一个Binder类,承担BnMemoryHeapBase的角色, 实例由服务端创建,BpMemoryHeapBase 由客户端使用。
MemoryHeapBase有多个构造函数,创建共享内存方式不同, 使用时按需选择
[–>MemoryHeapBase.cpp ]

MemoryHeapBase::MemoryHeapBase()
    : mFD(-1), mSize(0), mBase(MAP_FAILED),
      mDevice(NULL), mNeedUnmap(false), mOffset(0)
{
}

//通过ashmem设备创建共享内存,上size表示共享内存大小,flag为0, name为"AudioTrack Heap Base"
MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
    const size_t pagesize = getpagesize(); //获取系统内存页大小,一般为4kb
    size = ((size + pagesize-1) & ~(pagesize-1));
    //创建共享内存, ashmem_create_region函数由libcutils提供, 真实设备上将打开/dev/ashmem设备得到一个fd
    int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
    ALOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno));
    if (fd >= 0) {
    //将通过mmap方式得到内存地址
        if (mapfd(fd, size) == NO_ERROR) {
            if (flags & READ_ONLY) {
            //设置只读方式
                ashmem_set_prot_region(fd, PROT_READ);
            }
        }
    }
}
/*	从指定设备创建共享内存
     * maps memory from the given device
     */
MemoryHeapBase::MemoryHeapBase(const char* device, size_t size, uint32_t flags)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
    int open_flags = O_RDWR;
    if (flags & NO_CACHING)
        open_flags |= O_SYNC;

    int fd = open(device, open_flags);
    ALOGE_IF(fd<0, "error opening %s: %s", device, strerror(errno));
    if (fd >= 0) {
        const size_t pagesize = getpagesize();
        size = ((size + pagesize-1) & ~(pagesize-1));
        if (mapfd(fd, size) == NO_ERROR) {
            mDevice = device;
        }
    }
}
/*	映射指定文件描述符指向的内存, 使用dup()方式copy
     * maps the memory referenced by fd. but DOESN'T take ownership
     * of the filedescriptor (it makes a copy with dup()
     */
MemoryHeapBase::MemoryHeapBase(int fd, size_t size, uint32_t flags, uint32_t offset)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
    const size_t pagesize = getpagesize();
    size = ((size + pagesize-1) & ~(pagesize-1));
    mapfd(fcntl(fd, F_DUPFD_CLOEXEC, 0), size, offset);
}
......
}


MemoryHeapBase 类成员变量说明:

int         mFD; //ashmem_crate_region返回的文件描述符
    size_t      mSize; //所要分配内存大小
    void*       mBase;//变量指向共享内存起始地址
    uint32_t    mFlags;
    const char* mDevice; //指定设备
    bool        mNeedUnmap;
    uint32_t    mOffset; //内存偏移量


MemoryHeapBase 使用了引用计数、延迟分配物理内存(使用时才分配)等手段优化了传统内存共享方式。

MemoryBase也是一个Binder类,其声明在MemoryBase.h中,内容很简单,一起看下:

class MemoryBase : public BnMemory 
{
public:
//构造函数
    MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size);
    virtual ~MemoryBase();
   
    virtual sp<IMemoryHeap> getMemory(ssize_t* offset, size_t* size) const;

protected:
    size_t getSize() const { return mSize; } //返回大小
    ssize_t getOffset() const { return mOffset; } //返回偏移量
    // 返回MemoryHeapBase对象
    const sp<IMemoryHeap>& getHeap() const { return mHeap; }

private:
    size_t          mSize;
    ssize_t         mOffset;
    sp<IMemoryHeap> mHeap;
};

// ---------------------------------------------------------------------------
}; // namespace android

3. 流程总结

总结下使用MemoryHeapBase与MemoryBase实现共享内存的相关流程:

  1. 分配一块共享内存,这样两个进程可以共享这块内存
  2. 基于Binder通信,这样这两个类可以跨进程交互。

另外说明下: 这两个类没有提供同步对象保护这块共享内存, 在使用流程中必然需要提供一个跨进程的同步对象保护它。

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

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

相关文章

深入解析War包和Jar包机制

一、概述 代码编写完成后&#xff0c;需要部署到服务器&#xff0c;但部署到服务器对文件格式是有要求&#xff0c;原生的源代码目前是无法支持直接部署到服务器上的。目前有两种主要的文件格式War包和Jar包&#xff0c;通过一定的机制将源代码变成War包或Jar包&#xff0c;就…

42. 接雨水(Java)

目录 题目描述:输入&#xff1a;输出&#xff1a;代码实现&#xff1a; 题目描述: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 输入&#xff1a; height [0,1,0,2,1,0,1,3,2,1,2,1]输出&#xff1…

WebKit是什么?

WebKit是一个开源的浏览器引擎&#xff0c;它用于呈现网页内容在许多现代浏览器中&#xff0c;包括Safari浏览器、iOS内置浏览器、以及一些其他浏览器如Google Chrome的早期版本。以下是一些关于WebKit的重要信息&#xff1a; 起源和发展&#xff1a;WebKit最初是由苹果公司为其…

上传文件报错e20004 阿里云盘:空间不足 送的空间突然全到期了。免费无法长久 百度网盘扛住了压力,没有跟风。

https://blog.csdn.net/chenhao0568/article/details/137332783?spm1001.2014.3001.5501 免费撑不住了&#xff0c;这样下去干不过老大呀。百度网盘扛住了压力&#xff0c;没有跟风。

计算机网络——34LANs

LANs MAC地址和ARP 32bit IP地址 网络层地址用于使数据到达目标IP子网&#xff1a;前n - 1跳从而到达子网中的目标节点&#xff1a;最后一跳 LAN&#xff08;MAC/物理/以太网&#xff09;地址&#xff1a; 用于使帧从一个网卡传递到与其物理连接的另一个网卡&#xff08;在同…

计算机网络练习-计算机网络概述与性能指标

计算机网络概述 ----------------------------------------------------------------------------------------------------------------------------- 1. 计算机网络最据本的功能的是( )。 1,差错控制 Ⅱ.路由选择 Ⅲ,分布式处理 IV.传输控制 …

人眼对亮度的感知

对比两本书的说法 计算机图形学的算法基础 david f.rogers 如图所示: 然后看数字图像处理_第三版_中_冈萨雷斯的说法&#xff1a; 视觉错觉对于做图像处理没有什么大用。前面两点有用。 第一点。马赫带效应&#xff0c;明暗变化太强的时候&#xff0c;出现马赫带。较明区域是…

蓝桥杯刷题-14-更小的数-区间DP⭐

蓝桥杯2023年第十四届省赛真题-更小的数 //区间DP #include <iostream> #include<bits/stdc.h> #define int long long using namespace std; const int N5e310; int f[N][N]; void solve(){string s;cin>>s;int ans0;for(int len2;len<s.size();len){for…

Sora是什么?Sora怎么使用?Sora最新案例视频以及常见问题答疑

Sora 是什么&#xff1f; 2024年2月16日&#xff0c;OpenAI 在其官网上面正式宣布推出文本生成视频的大模型Sora 这样说吧给你一段话&#xff0c; 让你写一篇800字的论文&#xff0c;你的理解很可能都有偏差&#xff0c;那么作为OpenAi要做文生视频到底有多难&#xff0c;下面…

牛市来临,模块化赛道可能会出现下个以太坊?

市场专家普遍预测&#xff0c;2024年将成为加密货币市场迎来新一轮牛市的关键时刻。研究人员将下一次比特币&#xff08;BTC&#xff09;减半以及2024年现货BTC ETF&#xff08;交易所交易基金&#xff09;的可能性视为推动下一次牛市的潜在因素。这一牛市的可能爆发有望吸引大…

Leetcode 148. 排序链表

心路历程&#xff1a; 这道题通过很简单&#xff0c;但是如果想要用O(1)的空间复杂度O(nlogn)的时间复杂度的话&#xff0c;可能得需要双指针快排的思路。 解法&#xff1a;遍历模拟 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0…

Pillow教程09:图片格式(png,jpg,ico等)批量转换+批量修改图片尺寸

---------------Pillow教程集合--------------- Python项目18&#xff1a;使用Pillow模块&#xff0c;随机生成4位数的图片验证码 Python教程93&#xff1a;初识Pillow模块&#xff08;创建Image对象查看属性图片的保存与缩放&#xff09; Pillow教程02&#xff1a;图片的裁…

PLM系统同步到SAP修改、停用BOM数据

MATNR 1 类型 MATNR CHAR 40 0 0 物料编号 WERKS 1 类型 WERKS_D CHAR 4 0 0 工厂 STLAN 1 类型 STLAN CHAR 1 0 0 物料清单用途 STLAL 1 类型 STLAL CHAR 2 0 0 备选物料清单 AENNR …

2024.4.7作业

//登陆界面 this->setWindowTitle("传奇霸业"); this->setWindowIcon(QIcon("C:\\Users\\l1693\\Desktop\\pictures\\1.png")); this->resize(400,300); this->setFixedSize(400,300); //登录界面修饰 //底图 QLabel *lab5 new QLabel(this);…

深入理解数据结构第三弹——二叉树(3)——二叉树的基本结构与操作

二叉树&#xff08;1&#xff09;&#xff1a;深入理解数据结构第一弹——二叉树&#xff08;1&#xff09;——堆-CSDN博客 二叉树&#xff08;2&#xff09;&#xff1a;深入理解数据结构第二弹——二叉树&#xff08;2&#xff09;——堆排序及其时间复杂度-CSDN博客 前言…

前端学习之DOM编程案例:全选反选案例

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>全选反选</title> </head> <body><input type"checkbox" id"all">全选<ul><li><…

File,IO流,递归详解

File类 介绍 java.io.File类是Java语言提供了用来描述文件和目录(文件夹)的 构造 方法 注意&#xff1a; 构造方法中通常用的是第一个方法文件和目录可以通过File封装成对象File封装的对象仅仅是一个路径名&#xff0c;它是可以存在的&#xff0c;也可以不存在 绝对路径…

linux 安装JDK

一、安装jdk mkdir -p /export/servers # 软件安装的目录 0 . 使用rpm -qa | grep java 查看是否已经安装了jdk 使用: rpm -e --nodeps 软件的名称 将jdk进行卸载 执行完成后, 查看是否全部删除: 需要解压jdk的压缩包 tar -zxvf jdk-8u144-linux-x64.tar.gz -C /export/s…

Tcl学习笔记(二)——表达式、字符串

目录 1. 表达式 算数操作符 关系操作符 逻辑操作符 按位操作符 选择操作符 数学函数 字符串操作 2. 字符串 字符串长度、大小写转换、裁剪、重复 字符串类型 字符的获取 字符串的添加、删除、替换 字符串的比较 字符串的简单搜索 字符串的匹配 格式化…

计算机网络 实验指导 实验9

实验9 三层交换机综合实验 1.实验拓扑图 名称相连的接口IP地址网关PC1F0/3172.1.1.2/28172.1.1.1/28PC2F0/4172.1.1.18/28172.1.1.17/28PC3F0/5172.1.1.34/28172.1.1.33/28PC4F0/3172.1.1.3/28172.1.1.1/28PC5F0/4172.1.1.19/28172.1.1.17/28PC6F0/5172.1.1.35/28172.1.1.33/2…