<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
有了binder機制為什麼還需要匿名記憶體來實現IPC呢?我覺得很大的原因就是binder傳輸是有大小限制的,不說應用層的限制。在驅動中binder的傳輸大小被限制在了4M,分享一張圖片可能就超過了這個限制。匿名記憶體的主要解決思路就是通過binder傳輸檔案描述符,使得兩個程序都能存取同一個地址來實現共用。
在平常開發中android提供了MemoryFile來實現匿名記憶體。看下最簡單的實現。
const val GET_ASH_MEMORY = 1000 class MyService : Service() { val ashData = "AshDemo".toByteArray() override fun onBind(intent: Intent): IBinder { return object : Binder() { override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { when(code){ GET_ASH_MEMORY->{//收到使用者端請求的時候會煩 val descriptor = createMemoryFile() reply?.writeParcelable(descriptor, 0) reply?.writeInt(ashData.size) return true } else->{ return super.onTransact(code, data, reply, flags) } } } } } private fun createMemoryFile(): ParcelFileDescriptor? { val file = MemoryFile("AshFile", 1024)//建立MemoryFile val descriptorMethod = file.javaClass.getDeclaredMethod("getFileDescriptor") val fd=descriptorMethod.invoke(file)//反射拿到fd file.writeBytes(ashData, 0, 0,ashData.size)//寫入字串 return ParcelFileDescriptor.dup(fd as FileDescriptor?)//返回一個封裝的fd } }
Server的功能很簡單收到GET_ASH_MEMORY請求的時候建立一個MemoryFile,往裡寫入一個字串的byte陣列,然後將fd和字元長度寫入reply中返回給使用者端。
class MainActivity : AppCompatActivity() { val connect = object :ServiceConnection{ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { val reply = Parcel.obtain() val sendData = Parcel.obtain() service?.transact(GET_ASH_MEMORY, sendData, reply, 0)//傳輸訊號GET_ASH_MEMORY val pfd = reply.readParcelable<ParcelFileDescriptor>(javaClass.classLoader) val descriptor = pfd?.fileDescriptor//拿到fd val size = reply.readInt()//拿到長度 val input = FileInputStream(descriptor) val bytes = input.readBytes() val message = String(bytes, 0, size, Charsets.UTF_8)//生成string Toast.makeText(this@MainActivity,message,Toast.LENGTH_SHORT).show() } override fun onServiceDisconnected(name: ComponentName?) { } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<TextView>(R.id.intent).setOnClickListener { //啟動服務 bindService(Intent(this,MyService::class.java),connect, Context.BIND_AUTO_CREATE) } } }
使用者端也很簡單,啟動服務,傳送一個獲取MemoryFile的請求,然後通過reply拿到fd和長度,用FileInputStream讀取fd中的內容,最後通過toast可以驗證這個message已經拿到了。
public MemoryFile(String name, int length) throws IOException { try { mSharedMemory = SharedMemory.create(name, length); mMapping = mSharedMemory.mapReadWrite(); } catch (ErrnoException ex) { ex.rethrowAsIOException(); } }
MemoryFile就是對SharedMemory的一層封裝,具體的工能都是SharedMemory實現的。看SharedMemory的實現。
public static @NonNull SharedMemory create(@Nullable String name, int size) throws ErrnoException { if (size <= 0) { throw new IllegalArgumentException("Size must be greater than zero"); } return new SharedMemory(nCreate(name, size)); } private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
通過一個JNI獲得fd,從這裡可以推斷出java層也只是一個封裝,拿到的已經是建立好的fd。
//frameworks/base/core/jni/android_os_SharedMemory.cpp jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) { const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr; int fd = ashmem_create_region(name, size);//建立匿名記憶體塊 int err = fd < 0 ? errno : 0; if (name) { env->ReleaseStringUTFChars(jname, name); } if (fd < 0) { jniThrowErrnoException(env, "SharedMemory_create", err); return nullptr; } jobject jifd = jniCreateFileDescriptor(env, fd);//建立java fd返回 if (jifd == nullptr) { close(fd); } return jifd; }
通過cutils中的ashmem_create_region函數實現的建立
//system/core/libcutils/ashmem-dev.cpp int ashmem_create_region(const char *name, size_t size) { int ret, save_errno; if (has_memfd_support()) {//老版本相容用 return memfd_create_region(name ? name : "none", size); } int fd = __ashmem_open();//開啟Ashmem驅動 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));//通過ioctl設定名字 if (ret < 0) { goto error; } } ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));//通過ioctl設定大小 if (ret < 0) { goto error; } return fd; error: save_errno = errno; close(fd); errno = save_errno; return ret; }
標準的驅動互動操作
1.open開啟驅動
2.通過ioctl與驅動進行互動
下面看下open的流程
static int __ashmem_open() { int fd; pthread_mutex_lock(&__ashmem_lock); fd = __ashmem_open_locked(); pthread_mutex_unlock(&__ashmem_lock); return fd; } /* logistics of getting file descriptor for ashmem */ static int __ashmem_open_locked() { static const std::string ashmem_device_path = get_ashmem_device_path();//拿到Ashmem驅動路徑 if (ashmem_device_path.empty()) { return -1; } int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC)); return fd; }
回到MemoryFile的建構函式中,拿到了驅動的fd之後呼叫了mapReadWrite
public @NonNull ByteBuffer mapReadWrite() throws ErrnoException { return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize); } public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException { checkOpen(); validateProt(prot); if (offset < 0) { throw new IllegalArgumentException("Offset must be >= 0"); } if (length <= 0) { throw new IllegalArgumentException("Length must be > 0"); } if (offset + length > mSize) { throw new IllegalArgumentException("offset + length must not exceed getSize()"); } long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);//呼叫了系統的mmap boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0; Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire()); return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly); }
到這裡就有一個疑問,Linux就有共用記憶體,android為什麼要自己搞一套,只能看下Ashmemory驅動的實現了。
驅動第一步看init和file_operations
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 cachen"); goto out; } ashmem_range_cachep = kmem_cache_create("ashmem_range_cache", sizeof(struct ashmem_range), 0, SLAB_RECLAIM_ACCOUNT, NULL);//建立 if (!ashmem_range_cachep) { pr_err("failed to create slab cachen"); goto out_free1; } ret = misc_register(&ashmem_misc);//註冊為了一個misc裝置 ........ return ret; }
建立了兩個記憶體分配器ashmem_area_cachep和ashmem_range_cachep用於分配ashmem_area和ashmem_range
//common/drivers/staging/android/ashmem.c static const struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, .release = ashmem_release, .read_iter = ashmem_read_iter, .llseek = ashmem_llseek, .mmap = ashmem_mmap, .unlocked_ioctl = ashmem_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_ashmem_ioctl, #endif #ifdef CONFIG_PROC_FS .show_fdinfo = ashmem_show_fdinfo, #endif };
open呼叫的就是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);//分配一個ashmem_area if (!asma) return -ENOMEM; INIT_LIST_HEAD(&asma->unpinned_list);//初始化unpinned_list memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);//初始化一個名字 asma->prot_mask = PROT_MASK; file->private_data = asma; return 0; }
ioctl設定名字和長度呼叫的就是ashmem_ioctl
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_SET_NAME: ret = set_name(asma, (void __user *)arg); break; case ASHMEM_SET_SIZE: ret = -EINVAL; mutex_lock(&ashmem_mutex); if (!asma->file) { ret = 0; asma->size = (size_t)arg; } mutex_unlock(&ashmem_mutex); break; } ........ }
實現也都很簡單就是改變了一下asma裡的值。接下來就是重點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 */ if (!asma->size) {//判斷設定了size ret = -EINVAL; goto out; } /* requested mapping size larger than object size */ if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {//判斷大小是否超過了虛擬記憶體 ret = -EINVAL; goto out; } /* requested protection bits must match our allowed protection mask */ 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); if (!asma->file) {//是否建立過臨時檔案,沒建立過進入 char *name = ASHMEM_NAME_DEF; struct file *vmfile; struct inode *inode; if (asma->name[ASHMEM_NAME_PREFIX_LEN] != ' ') name = asma->name; /* ... and allocate the backing shmem file */ vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);//呼叫linux函數在tmpfs中建立臨時檔案 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 = vmfile; /* * override mmap operation of the vmfile so that it can't be * remapped which would lead to creation of a new vma with no * asma permission checks. Have to override get_unmapped_area * as well to prevent VM_BUG_ON check for f_ops modification. */ if (!vmfile_fops.mmap) {//設定了臨時檔案的檔案操作,防止有其他程式mmap這個臨時檔案 vmfile_fops = *vmfile->f_op; vmfile_fops.mmap = ashmem_vmfile_mmap; vmfile_fops.get_unmapped_area = ashmem_vmfile_get_unmapped_area; } vmfile->f_op = &vmfile_fops; } get_file(asma->file); /* * XXX - Reworked to use shmem_zero_setup() instead of * shmem_set_file while we're in staging. -jstultz */ if (vma->vm_flags & VM_SHARED) {//這塊記憶體是不是需要跨程序 ret = shmem_zero_setup(vma);//設定檔案 if (ret) { fput(asma->file); goto out; } } else { /** 實現就是把vm_ops設定為NULL static inline void vma_set_anonymous(struct vm_area_struct *vma) { vma->vm_ops = NULL; } */ vma_set_anonymous(vma); } vma_set_file(vma, asma->file); /* XXX: merge this with the get_file() above if possible */ fput(asma->file); out: mutex_unlock(&ashmem_mutex); return ret; }
函數很長,但是思路還是很清晰的。建立臨時檔案,設定檔案操作。其中呼叫的都是linux的系統函數了,看真正設定的shmem_zero_setup函數
int shmem_zero_setup(struct vm_area_struct *vma) { struct file *file; loff_t size = vma->vm_end - vma->vm_start; /* * Cloning a new file under mmap_lock leads to a lock ordering conflict * between XFS directory reading and selinux: since this file is only * accessible to the user through its mapping, use S_PRIVATE flag to * bypass file security, in the same way as shmem_kernel_file_setup(). */ file = shmem_kernel_file_setup("dev/zero", size, vma->vm_flags); if (IS_ERR(file)) return PTR_ERR(file); if (vma->vm_file) fput(vma->vm_file); vma->vm_file = file; vma->vm_ops = &shmem_vm_ops;//很重要的操作將這塊虛擬記憶體的vm_ops設定為shmem_vm_ops if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) && ((vma->vm_start + ~HPAGE_PMD_MASK) & HPAGE_PMD_MASK) < (vma->vm_end & HPAGE_PMD_MASK)) { khugepaged_enter(vma, vma->vm_flags); } return 0; } static const struct vm_operations_struct shmem_vm_ops = { .fault = shmem_fault,//Linux的共用記憶體實現的基礎 .map_pages = filemap_map_pages, #ifdef CONFIG_NUMA .set_policy = shmem_set_policy, .get_policy = shmem_get_policy, #endif };
到這裡共用記憶體的初始化就結束了。
//frameworks/base/core/java/android/os/MemoryFile.java public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) throws IOException { beginAccess(); try { mMapping.position(destOffset); mMapping.put(buffer, srcOffset, count); } finally { endAccess(); } } private void beginAccess() throws IOException { checkActive(); if (mAllowPurging) { if (native_pin(mSharedMemory.getFileDescriptor(), true)) { throw new IOException("MemoryFile has been purged"); } } } private void endAccess() throws IOException { if (mAllowPurging) { native_pin(mSharedMemory.getFileDescriptor(), false); } }
其中beginAccess和endAccess是對應的。呼叫的都是native_pin是一個native函數,一個引數是true一個是false。pin的作用就是鎖住這塊記憶體不被系統回收,當不使用的時候就解鎖。
static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor, jboolean pin) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0)); if (result < 0) { jniThrowException(env, "java/io/IOException", NULL); } return result == ASHMEM_WAS_PURGED; }
呼叫的ashmem_pin_region和ashmem_unpin_region來實現解鎖和解鎖。實現還是在ashmem-dev.cpp
//system/core/libcutils/ashmem-dev.cpp int ashmem_pin_region(int fd, size_t offset, size_t len) { ....... ashmem_pin pin = { static_cast<uint32_t>(offset), static_cast<uint32_t>(len) }; return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin))); }
通過的也是ioclt通知的驅動。加鎖的細節就不展開了。具體的寫入就是利用linux的共用記憶體機制實現的共用。
共用簡單的實現方式就是通過mmap同一個檔案來實現。但是真實檔案的讀寫速度實在是太慢了,所以利用tmpfs這個虛擬檔案系統,建立了一個虛擬檔案來讀寫。同時這塊虛擬記憶體在上面也寫到重寫了vm_ops。當有程序操作這個虛擬記憶體的時候會觸發缺頁錯誤,接著會去查詢Page快取,由於是第一次所以沒有快取,讀取實體記憶體,同時加入Page快取,當第二個程序進來的時也觸發缺頁錯誤時就能找到Page快取了,那麼他們操作的就是同一塊實體記憶體了。
看完之後發現AshMemory是基於Linux的共用記憶體實現的。做了幾點改造
以上就是Android 匿名記憶體深入分析的詳細內容,更多關於Android 匿名記憶體的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45