文章轉載至CSDN社區羅升陽的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554
在上一文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃中, 我們簡要介紹了Android系統的匿名共享內存機制,其中,簡要提到了它具有輔助內存管理系統來有效地管理內存的特點,但是沒有進一步去了解它是如何實 現的。在本文中,我們將通過分析Android系統的匿名共享內存Ashmem驅動程序的源代碼,來深入了解它是如何輔助內存管理系
?? ? ? ?Android系統的匿名共享內存Ashmem機制并沒有自立山頭,從頭搞一套自己的共享內存機制,而是建立在Linux內核實現的共享內存的基礎上 的。與此同時,它又向Linux內存管理系統的內存回收算法注冊接口,告訴Linux內存管理系統它的某些內存塊不再使用了,可以被回收了,不過,這些不 再使用的內存需要由它的使用者來告訴Ashmem驅動程序。通過這種用戶-Ashmem驅動程序-內存管理系統三者的緊密合作,實現有效的內存管理機制, 適合移動設備小內存的特點。
?? ? ? ?Android系統的匿名共享內存Ashmem驅動程序利用了Linux的共享內存子系統導出的接口來實現自己的功能,因此,它的實現非常小巧,總共代 碼不到700行。雖然代碼很少,但是這里不打算機械式地一行一行地閱讀和分析Ashmem驅動程序的源代碼,而是通過使用情景來分析,這樣可以幫助我們清 晰地理解它的實現原理。我們這里所說的使用情景,將從Android系統的應用程序框架層提供的匿名共享內存接口開始,經過系統運行時庫層,最終到達驅動 程序層,通過這樣一個完整的過程來理解Android系統的匿名共享內存Ashmem機制。這里,我們將從上一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃介紹的Android應用程序框架層提供MemoryFile接口開始,分別介紹Android系統匿名共享內存的創建(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)四個使用情景。
?? ? ? ?在進入到這個四個使用情景前,我們先來看一下Ashmem驅動程序模塊的初始化函數,看看它給用戶空間暴露了什么接口,即它創建了什么樣的設備文件,以 及提供了什么函數來操作這個設備文件。Ashmem驅動程序實現在kernel/common/mm/ashmem.c文件中,它的模塊初始化函數定義為 ashmem_init:
[cpp] view plaincopy
static?struct?file_operations?ashmem_fops?=?{?? ????.owner?=?THIS_MODULE,?? ????.open?=?ashmem_open,?? ????.release?=?ashmem_release,?? ????.mmap?=?ashmem_mmap,?? ????.unlocked_ioctl?=?ashmem_ioctl,?? ????.compat_ioctl?=?ashmem_ioctl,?? };?? ?? static?struct?miscdevice?ashmem_misc?=?{?? ????.minor?=?MISC_DYNAMIC_MINOR,?? ????.name?=?"ashmem",?? ????.fops?=?&ashmem_fops,?? };?? ?? static?int?__init?ashmem_init(void)?? {?? ????int?ret;?? ?? ????......?? ?? ????ret?=?misc_register(&ashmem_misc);?? ????if?(unlikely(ret))?{?? ????????printk(KERN_ERR?"ashmem:?failed?to?register?misc?device!\n");?? ????????return?ret;?? ????}?? ?? ????......?? ?? ????return?0;?? }?? ?? ? ? 這里,我們可以看到,Ahshmem驅動程序在加載時,會創建一個/dev/ashmem的設備文件,這是一個misc類型的設備。注冊misc設備是通過misc_register函數進行的,關于這個函數的詳細實現,可以參考前面Android日志系統驅動程序Logger源代碼分析一 文,調用這個函數成功后,就會在/dev目錄下生成一個ashmem設備文件了。同時,我們還可以看到,這個設備文件提供了open、mmap、 release和ioctl四種操作。為什么沒有read和write操作呢?這是因為讀寫共享內存的方法是通過內存映射地址來進行的,即通過mmap系 統調用把這個設備文件映射到進程地址空間中,然后就直接對內存進行讀寫了,不需要通過read 和write文件操作,后面我們將會具體分析是如何實現的。 ?? ? ? 有了這個基礎之后,下面我們就分四個部分來分別介紹匿名共享內存的創建(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)使用情景。
?? ? ? ?一. 匿名共享內存的創建操作
?? ? ? ?在Android應用程序框架層提供MemoryFile類的構造函數中,進行了匿名共享內存的創建操作,我們先來看一下這個構造函數的實現,它位于 frameworks/base/core/java/android/os/MemoryFile.java文件中:
[java] view plaincopy
public?class?MemoryFile?? {?? ????......?? ?? ????private?static?native?FileDescriptor?native_open(String?name,?int?length)?throws?IOException;?? ?????? ????......?? ?? ????private?FileDescriptor?mFD;???????? ????......?? ????private?int?mLength;???? ?????? ????......?? ?? ???? ????public?MemoryFile(String?name,?int?length)?throws?IOException?{?? ????????mLength?=?length;?? ????????mFD?=?native_open(name,?length);?? ????????......?? ????}?? ?? ????......?? }?? ?? ? ? ?這里我們看到,這個構造函數最終是通過JNI方法native_open來創建匿名內存共享文件。這個JNI方法native_open實現在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:
[cpp] view plaincopy
static?jobject?android_os_MemoryFile_open(JNIEnv*?env,?jobject?clazz,?jstring?name,?jint?length)?? {?? ????const?char*?namestr?=?(name???env->GetStringUTFChars(name,?NULL)?:?NULL);?? ?? ????int?result?=?ashmem_create_region(namestr,?length);?? ?? ????if?(name)?? ????????env->ReleaseStringUTFChars(name,?namestr);?? ?? ????if?(result?<?0)?{?? ????????jniThrowException(env,?"java/io/IOException",?"ashmem_create_region?failed");?? ????????return?NULL;?? ????}?? ?? ????return?jniCreateFileDescriptor(env,?result);?? }?? ?? ? ? ?這個函數又通過運行時庫提供的接口ashmem_create_region來創建匿名共享內存,這個函數實現在system/core/libcutils/ashmem-dev.c文件中:
[cpp] view plaincopy
int?ashmem_create_region(const?char?*name,?size_t?size)?? {?? ????int?fd,?ret;?? ?? ????fd?=?open(ASHMEM_DEVICE,?O_RDWR);?? ????if?(fd?<?0)?? ????????return?fd;?? ?? ????if?(name)?{?? ????????char?buf[ASHMEM_NAME_LEN];?? ?? ????????strlcpy(buf,?name,?sizeof(buf));?? ????????ret?=?ioctl(fd,?ASHMEM_SET_NAME,?buf);?? ????????if?(ret?<?0)?? ????????????goto?error;?? ????}?? ?? ????ret?=?ioctl(fd,?ASHMEM_SET_SIZE,?size);?? ????if?(ret?<?0)?? ????????goto?error;?? ?? ????return?fd;?? ?? error:?? ????close(fd);?? ????return?ret;?? }?? ?? ? ? ?這里,一共通過執行三個文件操作系統調用來和Ashmem驅動程序進行交互,分雖是一個open和兩個ioctl操作,前者是打開設備文件ASHMEM_DEVICE,后者分別是設置匿名共享內存的名稱和大小。
?? ? ? ?在介紹這三個文件操作之前,我們先來了解一下Ashmem驅動程序的一個相關數據結構struct ashmem_area,這個數據結構就是用來表示一塊共享內存的,它定義在kernel/common/mm/ashmem.c文件中:
[cpp] view plaincopy
struct?ashmem_area?{?? ????char?name[ASHMEM_FULL_NAME_LEN]; ????struct?list_head?unpinned_list;? ????struct?file?*file;?????? ????size_t?size;???????????? ????unsigned?long?prot_mask;???? };?? ?? ? ? ?域name表示這塊共享內存的名字,這個名字會顯示/proc/<pid>/maps文件中,<pid>表示打開這個共享內存 文件的進程ID;域unpinned_list是一個列表頭,它把這塊共享內存中所有被解鎖的內存塊連接在一起,下面我們講內存塊的鎖定和解鎖操作時會看 到它的用法;域file表示這個共享內存在臨時文件系統tmpfs中對應的文件,在內核決定要把這塊共享內存對應的物理頁面回收時,就會把它的內容交換到 這個臨時文件中去;域size表示這塊共享內存的大小;域prot_mask表示這塊共享內存的訪問保護位。
?? ? ? ?在Ashmem驅動程中,所有的ashmem_area實例都是從自定義的一個slab緩沖區創建的。這個slab緩沖區是在驅動程序模塊初始化函數創建的,我們來看一個這個初始化函數的相關實現:
[cpp] view plaincopy
static?int?__init?ashmem_init(void)?? {?? ????int?ret;?? ?? ????ashmem_area_cachep?=?kmem_cache_create("ashmem_area_cache",?? ????????sizeof(struct?ashmem_area),?? ????????0,?0,?NULL);?? ????if?(unlikely(!ashmem_area_cachep))?{?? ????????printk(KERN_ERR?"ashmem:?failed?to?create?slab?cache\n");?? ????????return?-ENOMEM;?? ????}?? ?? ????......?? ?? ????return?0;?? }?? ?? ? ? ? 全局變量定義在文件開頭的地方:
[cpp] view plaincopy
static?struct?kmem_cache?*ashmem_area_cachep?__read_mostly;?? ?? ? ? ?它的類型是struct kmem_cache,表示這是一個slab緩沖區,由內核中的內存管理系統進行管理。
?? ? ? ?這里就是通過kmem_cache_create函數來創建一個名為"ashmem_area_cache"、對象大小為sizeof(struct ashmem_area)的緩沖區了。緩沖區創建了以后,就可以每次從它分配一個struct ashmem_area對象了。關于Linux內核的slab緩沖區的相關知識,可以參考前面Android學習啟動篇一文中提到的一本參考書籍《Understanding the Linux Kernel》的第八章Memory Managerment。
?? ? ? ?有了這些基礎知識后,我們回到前面的ashmem_create_region函數中。
?? ? ? ?首先是執行打開文件的操作:
[cpp] view plaincopy
fd?=?open(ASHMEM_DEVICE,?O_RDWR);?? ?? ? ? ?ASHMEM_DEVICE是一個宏,定義為:
[cpp] view plaincopy
#define?ASHMEM_DEVICE???"/dev/ashmem"?? ?? ? ? ? 這里就是匿名共享內存設備文件/dev/ashmem了。
?? ? ? ?從上面的描述我們可以知道,調用這個open函數最終會進入到Ashmem驅動程序中的ashmem_open函數中去:
[cpp] view plaincopy
static?int?ashmem_open(struct?inode?*inode,?struct?file?*file)?? {?? ????struct?ashmem_area?*asma;?? ????int?ret;?? ?? ????ret?=?nonseekable_open(inode,?file);?? ????if?(unlikely(ret))?? ????????return?ret;?? ?? ????asma?=?kmem_cache_zalloc(ashmem_area_cachep,?GFP_KERNEL);?? ????if?(unlikely(!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;?? }?? ?? ? ? ?首先是通過nonseekable_open函數來設備這個文件不可以執行定位操作,即不可以執行seek文件操作。接著就是通過 kmem_cache_zalloc函數從剛才我們創建的slab緩沖區ashmem_area_cachep來創建一個ashmem_area結構體 了,并且保存在本地變量asma中。再接下去就是初始化變量asma的其它域,其中,域name初始為ASHMEM_NAME_PREFIX,這是一個 宏,定義為:
[cpp] view plaincopy
#define?ASHMEM_NAME_PREFIX?"dev/ashmem/"?? #define?ASHMEM_NAME_PREFIX_LEN?(sizeof(ASHMEM_NAME_PREFIX)?-?1)?? ?? ? ? ?函數的最后是把這個ashmem_area結構保存在打開文件結構體的private_data域中,這樣,Ashmem驅動程序就可以在其它地方通過這個private_data域來取回這個ashmem_area結構了。
?? ? ? ?到這里,設備文件/dev/ashmem的打開操作就完成了,它實際上就是在Ashmem驅動程序中創建了一個ashmem_area結構,表示一塊新的共享內存。
?? ? ? ?再回到ashmem_create_region函數中,又調用了兩次ioctl文件操作分別來設備這塊新建的匿名共享內存的名字和大小。在 kernel/comon/mm/include/ashmem.h文件中,ASHMEM_SET_NAME和ASHMEM_SET_SIZE的定義為:
[cpp] view plaincopy
#define?ASHMEM_NAME_LEN?????256?? ?? #define?__ASHMEMIOC?????0x77?? ?? #define?ASHMEM_SET_NAME?????_IOW(__ASHMEMIOC,?1,?char[ASHMEM_NAME_LEN])?? #define?ASHMEM_SET_SIZE?????_IOW(__ASHMEMIOC,?3,?size_t)?? ?? ? ? 先來看ASHMEM_SET_NAME命令的ioctl調用,它最終進入到Ashmem驅動程序的ashmem_ioctl函數中:
[cpp] view plaincopy
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;?? ????......?? ????}?? ?? ????return?ret;?? }?? ?? ? ? 這里通過set_name函數來進行實際操作:
[cpp] view plaincopy
static?int?set_name(struct?ashmem_area?*asma,?void?__user?*name)?? {?? ????int?ret?=?0;?? ?? ????mutex_lock(&ashmem_mutex);?? ?? ???? ????if?(unlikely(asma->file))?{?? ????????ret?=?-EINVAL;?? ????????goto?out;?? ????}?? ?? ????if?(unlikely(copy_from_user(asma->name?+?ASHMEM_NAME_PREFIX_LEN,?? ????????????????????name,?ASHMEM_NAME_LEN)))?? ????????ret?=?-EFAULT;?? ????asma->name[ASHMEM_FULL_NAME_LEN-1]?=?'\0';?? ?? out:?? ????mutex_unlock(&ashmem_mutex);?? ?? ????return?ret;?? }?? ?? ? ? ?這個函數實現很簡單,把用戶空間傳進來的匿名共享內存的名字設備到asma->name域中去。注意,匿名共享內存塊的名字的內容分兩部分,前一 部分是前綴,這是在open操作時,由驅動程序默認設置的,固定為ASHMEM_NAME_PREFIX,即"dev/ashmem/";后一部分由用戶 指定,這一部分是可選的,即用戶可以不調用ASHMEM_SET_NAME命令來設置匿名共享內存塊的名字。
?? ? ? ?再來看ASHMEM_SET_SIZE命令的ioctl調用,它最終也是進入到Ashmem驅動程序的ashmem_ioctl函數中:
[cpp] view plaincopy
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_SIZE:?? ????????ret?=?-EINVAL;?? ????????if?(!asma->file)?{?? ????????????ret?=?0;?? ????????????asma->size?=?(size_t)?arg;?? ????????}?? ????????break;?? ????......?? ????}?? ?? ????return?ret;?? }?? ?? ? ? ?這個實現很簡單,只是把用戶空間傳進來的匿名共享內存的大小值保存在對應的asma->size域中。
?? ? ? ?這樣,ashmem_create_region函數就執先完成了,層層返回,最后回到應用程序框架層提供的接口Memory的構造函數中,整個匿名共 享內存的創建過程就完成了。前面我們說過過,Ashmem驅動程序不提供read和write文件操作,進程若要訪問這個共享內存,必須要把這個設備文件 映射到自己的進程空間中,然后進行直接內存訪問,這就是我們下面要介紹的匿名共享內存設備文件的內存映射操作了。
?? ? ? ?二.?匿名共享內存設備文件的內存映射操作
?? ? ? ?在MemoryFile類的構造函數中,進行了匿名共享內存的創建操作后,下一步就是要把匿名共享內存設備文件映射到進程空間來了:
[java] view plaincopy
public?class?MemoryFile?? {?? ????......?? ?? ???? ????private?static?native?int?native_mmap(FileDescriptor?fd,?int?length,?int?mode)?? ????????throws?IOException;?? ?????? ????......?? ?? ????private?int?mAddress;??? ?????? ????......?? ?? ???? ????public?MemoryFile(String?name,?int?length)?throws?IOException?{?? ????????......?? ????????mAddress?=?native_mmap(mFD,?length,?PROT_READ?|?PROT_WRITE);?? ????????......?? ????}?? }?? ?? ? ? ? 映射匿名共享內存設備文件到進程空間是通過JNI方法native_mmap來進行的。這個JNI方法實現在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:
[cpp] view plaincopy
static?jint?android_os_MemoryFile_mmap(JNIEnv*?env,?jobject?clazz,?jobject?fileDescriptor,?? ????????jint?length,?jint?prot)?? {?? ????int?fd?=?jniGetFDFromFileDescriptor(env,?fileDescriptor);?? ????jint?result?=?(jint)mmap(NULL,?length,?prot,?MAP_SHARED,?fd,?0);?? ????if?(!result)?? ????????jniThrowException(env,?"java/io/IOException",?"mmap?failed");?? ????return?result;?? }?? ?? ? ? ?這里的文件描述符fd是在前面open匿名設備文件/dev/ashmem獲得的,有個這個文件描述符后,就可以直接通過mmap來執行內存映射操作了。這個mmap系統調用最終進入到Ashmem驅動程序的ashmem_mmap函數中:
[cpp] view plaincopy
static?int?ashmem_mmap(struct?file?*file,?struct?vm_area_struct?*vma)?? {?? ????struct?ashmem_area?*asma?=?file->private_data;?? ????int?ret?=?0;?? ?? ????mutex_lock(&ashmem_mutex);?? ?? ???? ????if?(unlikely(!asma->size))?{?? ????????ret?=?-EINVAL;?? ????????goto?out;?? ????}?? ?? ???? ????if?(unlikely((vma->vm_flags?&?~asma->prot_mask)?&?PROT_MASK))?{?? ????????ret?=?-EPERM;?? ????????goto?out;?? ????}?? ?? ????if?(!asma->file)?{?? ????????char?*name?=?ASHMEM_NAME_DEF;?? ????????struct?file?*vmfile;?? ?? ????????if?(asma->name[ASHMEM_NAME_PREFIX_LEN]?!=?'\0')?? ????????????name?=?asma->name;?? ?? ???????? ????????vmfile?=?shmem_file_setup(name,?asma->size,?vma->vm_flags);?? ????????if?(unlikely(IS_ERR(vmfile)))?{?? ????????????ret?=?PTR_ERR(vmfile);?? ????????????goto?out;?? ????????}?? ????????asma->file?=?vmfile;?? ????}?? ????get_file(asma->file);?? ?? ????if?(vma->vm_flags?&?VM_SHARED)?? ????????shmem_set_file(vma,?asma->file);?? ????else?{?? ????????if?(vma->vm_file)?? ????????????fput(vma->vm_file);?? ????????vma->vm_file?=?asma->file;?? ????}?? ????vma->vm_flags?|=?VM_CAN_NONLINEAR;?? ?? out:?? ????mutex_unlock(&ashmem_mutex);?? ????return?ret;?? }?? ?? ? ? ?這個函數的實現也很簡單,它調用了Linux內核提供的shmem_file_setup函數來在臨時文件系統tmpfs中創建一個臨時文件,這個臨時 文件與Ashmem驅動程序創建的匿名共享內存對應。函數shmem_file_setup是Linux內核中用來創建共享內存文件的方法,而Linux 內核中的共享內存機制其實是一種進程間通信(IPC)機制,它的實現相對也是比較復雜,Android系統的匿名共享內存機制正是由于直接使用了 Linux內核共享內存機制,它才會很小巧,它站在巨人的肩膀上了。關于Linux內核中的共享內存的相關知識,可以參考前面Android學習啟動篇一文中提到的一本參考書籍《Linux內核源代碼情景分析》的第六章傳統的Unix進程間通信第七小節共享內存。
?? ? ? ?通過shmem_file_setup函數創建的臨時文件vmfile最終就保存在vma->file中了。這里的vma是由Linux內核的文 件系統層傳進來的,它的類型為struct vm_area_struct,它表示的是當前進程空間中一塊連續的虛擬地址空間,它的起始地址可以由用戶來指定,也可以由內核自己來分配,這里我們從 JNI方法native_mmap調用的mmap的第一個參數為NULL可以看出,這塊連續的虛擬地址空間的起始地址是由內核來指定的。文件內存映射操作 完成后,用戶訪問這個范圍的地址空間就相當于是訪問對應的文件的內容了。有關Linux文件的內存映射操作,同樣可以參考前面Android學習啟動篇一文中提到的一本參考書籍《Linux內核源代碼情景分析》的第二章內存管理第十三小節系統調用mmap。從這里我們也可以看出,Android系統的匿名共享內存是在虛擬地址空間連續的,但是在物理地址空間就不一定是連續的了。
?? ? ? ?同時,這個臨時文件vmfile也會保存asma->file域中,這樣,Ashmem驅動程序后面就可以通過在asma->file來操作這個匿名內存共享文件了。
?? ? ? ?函數ashmem_mmap執行完成后,經過層層返回到JNI方法native_mmap中去,就從mmap函數的返回值中得到了這塊虛擬空間的起始地 址了,這個起始地址最終返回到應用程序框架層的MemoryFile類的構造函數中,并且保存在成員變量mAddress中,后面,共享內存的讀寫操作就 是對這個地址空間進行操作了。
?? ? ? ?三.?匿名共享內存的讀寫操作
?? ? ? ?因為前面對匿名共享內存文件進行內存映射操作,這里對匿名內存文件內容的讀寫操作就比較簡單了,就像訪問內存變量一樣就行了。
?? ? ? ?我們來看一下MemoryFile類的讀寫操作函數:
[cpp] view plaincopy
public?class?MemoryFile?? {?? ????......?? ?? ????private?static?native?int?native_read(FileDescriptor?fd,?int?address,?byte[]?buffer,?? ????????int?srcOffset,?int?destOffset,?int?count,?boolean?isUnpinned)?throws?IOException;?? ????private?static?native?void?native_write(FileDescriptor?fd,?int?address,?byte[]?buffer,?? ????????int?srcOffset,?int?destOffset,?int?count,?boolean?isUnpinned)?throws?IOException;?? ?????? ????......?? ?? ????private?FileDescriptor?mFD;???????? ????private?int?mAddress;??? ????private?int?mLength;???? ????private?boolean?mAllowPurging?=?false;?? ?? ????......?? ?? ???? ????public?int?readBytes(byte[]?buffer,?int?srcOffset,?int?destOffset,?int?count)??? ????throws?IOException?{?? ????????if?(isDeactivated())?{?? ????????????throw?new?IOException("Can't?read?from?deactivated?memory?file.");?? ????????}?? ????????if?(destOffset?<?0?||?destOffset?>?buffer.length?||?count?<?0?? ????????????||?count?>?buffer.length?-?destOffset?? ????????????||?srcOffset?<?0?||?srcOffset?>?mLength?? ????????????||?count?>?mLength?-?srcOffset)?{?? ????????????????throw?new?IndexOutOfBoundsException();?? ????????}?? ????????return?native_read(mFD,?mAddress,?buffer,?srcOffset,?destOffset,?count,?mAllowPurging);?? ????}?? ?? ???? ????public?void?writeBytes(byte[]?buffer,?int?srcOffset,?int?destOffset,?int?count)?? ????????throws?IOException?{?? ????????????if?(isDeactivated())?{?? ????????????????throw?new?IOException("Can't?write?to?deactivated?memory?file.");?? ????????????}?? ????????????if?(srcOffset?<?0?||?srcOffset?>?buffer.length?||?count?<?0?? ????????????????||?count?>?buffer.length?-?srcOffset?? ????????????????||?destOffset?<?0?||?destOffset?>?mLength?? ????????????????||?count?>?mLength?-?destOffset)?{?? ????????????????????throw?new?IndexOutOfBoundsException();?? ????????????}?? ????????????native_write(mFD,?mAddress,?buffer,?srcOffset,?destOffset,?count,?mAllowPurging);?? ????}?? ?? ????......?? }?? ?? ? ? ?這里,我們可以看到,MemoryFile的匿名共享內存讀寫操作都是通過JNI方法來實現的,讀操作和寫操作的JNI方法分別是 native_read和native_write,它們都是定義在frameworks/base/core/jni /adroid_os_MemoryFile.cpp文件中:
[cpp] view plaincopy
static?jint?android_os_MemoryFile_read(JNIEnv*?env,?jobject?clazz,?? ????????jobject?fileDescriptor,?jint?address,?jbyteArray?buffer,?jint?srcOffset,?jint?destOffset,?? ????????jint?count,?jboolean?unpinned)?? {?? ????int?fd?=?jniGetFDFromFileDescriptor(env,?fileDescriptor);?? ????if?(unpinned?&&?ashmem_pin_region(fd,?0,?0)?==?ASHMEM_WAS_PURGED)?{?? ????????ashmem_unpin_region(fd,?0,?0);?? ????????jniThrowException(env,?"java/io/IOException",?"ashmem?region?was?purged");?? ????????return?-1;?? ????}?? ?? ????env->SetByteArrayRegion(buffer,?destOffset,?count,?(const?jbyte?*)address?+?srcOffset);?? ?? ????if?(unpinned)?{?? ????????ashmem_unpin_region(fd,?0,?0);?? ????}?? ????return?count;?? }?? ?? static?jint?android_os_MemoryFile_write(JNIEnv*?env,?jobject?clazz,?? ????????jobject?fileDescriptor,?jint?address,?jbyteArray?buffer,?jint?srcOffset,?jint?destOffset,?? ????????jint?count,?jboolean?unpinned)?? {?? ????int?fd?=?jniGetFDFromFileDescriptor(env,?fileDescriptor);?? ????if?(unpinned?&&?ashmem_pin_region(fd,?0,?0)?==?ASHMEM_WAS_PURGED)?{?? ????????ashmem_unpin_region(fd,?0,?0);?? ????????jniThrowException(env,?"java/io/IOException",?"ashmem?region?was?purged");?? ????????return?-1;?? ????}?? ?? ????env->GetByteArrayRegion(buffer,?srcOffset,?count,?(jbyte?*)address?+?destOffset);?? ?? ????if?(unpinned)?{?? ????????ashmem_unpin_region(fd,?0,?0);?? ????}?? ????return?count;?? }?? ?? ? ? ?這里的address參數就是我們在前面執行mmap來映射匿名共享內存文件到內存中時,得到的進程虛擬地址空間的起始地址了,因此,這里就直接可以訪 問,不必進入到Ashmem驅動程序中去,這也是為什么Ashmem驅動程序沒有提供read和write文件操作的原因。
?? ? ? ?這里我們看到的ashmem_pin_region和ashmem_unpin_region兩個函數是系統運行時庫提供的接口,用來執行我們前面說的 匿名共享內存的鎖定和解鎖操作,它們的作用是告訴Ashmem驅動程序,它的哪些內存塊是正在使用的,需要鎖定,哪些內存是不需要使用了,可以它解鎖,這 樣,Ashmem驅動程序就可以輔助內存管理系統來有效地管理內存了。下面我們就看看Ashmem驅動程序是如果輔助內存管理系統來有效地管理內存的。
?? ? ? ?四.?匿名共享內存的鎖定和解鎖操作 ?? ? ? ?前面提到,Android系統的運行時庫提到了執行匿名共享內存的鎖定和解鎖操作的兩個函數ashmem_pin_region和 ashmem_unpin_region,它們實現在system/core/libcutils/ashmem-dev.c文件中:
[cpp] view plaincopy
int?ashmem_pin_region(int?fd,?size_t?offset,?size_t?len)?? {?? ????struct?ashmem_pin?pin?=?{?offset,?len?};?? ????return?ioctl(fd,?ASHMEM_PIN,?&pin);?? }?? ?? int?ashmem_unpin_region(int?fd,?size_t?offset,?size_t?len)?? {?? ????struct?ashmem_pin?pin?=?{?offset,?len?};?? ????return?ioctl(fd,?ASHMEM_UNPIN,?&pin);?? }?? ?? ? ? 它們的實現很簡單,通過ASHMEM_PIN和ASHMEM_UNPIN兩個ioctl操作來實現匿名共享內存的鎖定和解鎖操作。
?? ? ? 我們先看來一下ASHMEM_PIN和ASHMEM_UNPIN這兩個命令號的定義,它們的定義可以在kernel/common/include/linux/ashmem.h文件中找到:
[cpp] view plaincopy
#define?__ASHMEMIOC?????0x77?? ?? #define?ASHMEM_PIN??????_IOW(__ASHMEMIOC,?7,?struct?ashmem_pin)?? #define?ASHMEM_UNPIN????????_IOW(__ASHMEMIOC,?8,?struct?ashmem_pin)?? ?? ? ? 它們的參數類型為struct ashmem_pin,它也是定義在kernel/common/include/linux/ashmem.h文件中:
[cpp] view plaincopy
struct?ashmem_pin?{?? ????__u32?offset;??? ????__u32?len;?? };?? ?? ? ? 這個結構體只有兩個域,分別表示要鎖定或者要解鎖的內塊塊的起始大小以及大小。
?? ? ? 在分析這兩個操作之前,我們先來看一下Ashmem驅動程序中的一個數據結構struct ashmem_range,這個數據結構就是用來表示某一塊被解鎖(unpinnd)的內存:
[cpp] view plaincopy
struct?ashmem_range?{?? ????struct?list_head?lru;??????? ????struct?list_head?unpinned;?? ????struct?ashmem_area?*asma;??? ????size_t?pgstart;????????? ????size_t?pgend;??????????? ????unsigned?int?purged;???????? };?? ?? ? ? ?域asma表示這塊被解鎖的內存所屬于的匿名共享內存,它通過域unpinned連接在asma->unpinned_list表示的列表中;域 pgstart和paend表示這個內存塊的開始和結束頁面號,它們表示一個前后閉合的區間;域purged表示這個內存塊占用的物理內存是否已經被回 收;這塊被解鎖的內存塊除了保存在它所屬的匿名共享內存asma的解鎖列表unpinned_list之外,還通過域lru保存在一個全局的最近最少使用 列表ashmem_lru_list列表中,它的定義如下:
[cpp] view plaincopy
static?LIST_HEAD(ashmem_lru_list);?? ?? ? ? ?了解了這個數據結構之后,我們就可以來看ashmem_ioctl函數中關于ASHMEM_PIN和ASHMEM_UNPIN的操作了:
[cpp] view plaincopy
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:?? ????????ret?=?ashmem_pin_unpin(asma,?cmd,?(void?__user?*)?arg);?? ????????break;?? ????......?? ????}?? ?? ????return?ret;?? }?? ?? ? ? ?它們都是通過ashmem_pin_unpin來進一步處理:
[cpp] view plaincopy
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;?? ?? ????if?(unlikely(!asma->file))?? ????????return?-EINVAL;?? ?? ????if?(unlikely(copy_from_user(&pin,?p,?sizeof(pin))))?? ????????return?-EFAULT;?? ?? ???? ????if?(!pin.len)?? ????????pin.len?=?PAGE_ALIGN(asma->size)?-?pin.offset;?? ?? ????if?(unlikely((pin.offset?|?pin.len)?&?~PAGE_MASK))?? ????????return?-EINVAL;?? ?? ????if?(unlikely(((__u32)?-1)?-?pin.offset?<?pin.len))?? ????????return?-EINVAL;?? ?? ????if?(unlikely(PAGE_ALIGN(asma->size)?<?pin.offset?+?pin.len))?? ????????return?-EINVAL;?? ?? ????pgstart?=?pin.offset?/?PAGE_SIZE;?? ????pgend?=?pgstart?+?(pin.len?/?PAGE_SIZE)?-?1;?? ?? ????mutex_lock(&ashmem_mutex);?? ?? ????switch?(cmd)?{?? ????case?ASHMEM_PIN:?? ????????ret?=?ashmem_pin(asma,?pgstart,?pgend);?? ????????break;?? ????case?ASHMEM_UNPIN:?? ????????ret?=?ashmem_unpin(asma,?pgstart,?pgend);?? ????????break;?? ????......?? ????}?? ?? ????mutex_unlock(&ashmem_mutex);?? ?? ????return?ret;?? }?? ?? ? ? ?首先是獲得用戶空間傳進來的參數,并保存在本地變量pin中,這是一個struct ashmem_pin類型的變量,這個結構體我們在前面已經見過了,它包括了要pin/unpin的內存塊的起始地址和大小,這里的起始地址和大小都是以 字節為單位的,因此,通過轉換把它們換成以頁面為單位的,并且保存在本地變量pgstart和pgend中。這里除了要對參數作一個安全性檢查外,還要一 個處理邏輯是,如果從用戶空間傳進來的內塊塊的大小值為0 ,則認為是要pin/unpin整個匿名共享內存。
?? ? ? ?函數最后根據當前要執行的是ASHMEM_PIN操作還是ASHMEM_UNPIN操作來分別執行ashmem_pin和ashmem_unpin來進 一步處理。創建匿名共享內存時,默認所有的內存都是pinned狀態的,只有用戶告訴Ashmem驅動程序要unpin某一塊內存時,Ashmem驅動程 序才會把這塊內存unpin,之后,用戶可以再告訴Ashmem驅動程序要重新pin某一塊之前被unpin過的內塊,從而把這塊內存從unpinned 狀態改為pinned狀態,也就是說,執行ASHMEM_PIN操作時,目標對象必須是一塊當前處于unpinned狀態的內存塊。
?? ? ? 我們先來看一下ASHMEM_UNPIN操作,進入到ashmem_unpin函數:
[cpp] view plaincopy
static?int?ashmem_unpin(struct?ashmem_area?*asma,?size_t?pgstart,?size_t?pgend)?? {?? ????struct?ashmem_range?*range,?*next;?? ????unsigned?int?purged?=?ASHMEM_NOT_PURGED;?? ?? restart:?? ????list_for_each_entry_safe(range,?next,?&asma->unpinned_list,?unpinned)?{?? ???????? ????????if?(range_before_page(range,?pgstart))?? ????????????break;?? ?? ???????? ????????if?(page_range_subsumed_by_range(range,?pgstart,?pgend))?? ????????????return?0;?? ????????if?(page_range_in_range(range,?pgstart,?pgend))?{?? ????????????pgstart?=?min_t(size_t,?range->pgstart,?pgstart),?? ????????????pgend?=?max_t(size_t,?range->pgend,?pgend);?? ????????????purged?|=?range->purged;?? ????????????range_del(range);?? ????????????goto?restart;?? ????????}?? ????}?? ?? ????return?range_alloc(asma,?range,?purged,?pgstart,?pgend);?? }?? ?? ? ? ?這個函數的主體就是在遍歷asma->unpinned_list列表,從中查找當前處于unpinned狀態的內存塊是否與將要unpin的內 存塊[pgstart, pgend]是否相交,如果相交,則要執行合并操作,即調整pgstart和pgend的大小,然后通過調用range_del函數刪掉原來的已經被 unpinned過的內存塊,最后再通過range_alloc函數來重新unpinned這塊調整過后的內存塊[pgstart, pgend],這里新的內存塊[pgstart, pgend]已經包含了剛才所有被刪掉的unpinned狀態的內存。注意,這里如果找到一塊相并的內存塊,并且調整了pgstart和pgend的大小 之后,要重新再掃描一遍asma->unpinned_list列表,因為新的內存塊[pgstart, pgend]可能還會與前后的處于unpinned狀態的內存塊發生相交。
?? ? ? ?我們來看一下range_before_page的操作,這是一個宏定義:
[cpp] view plaincopy
#define?range_before_page(range,?page)?\?? ??((range)->pgend?<?(page))?? ?? ? ? ?表示range描述的內存塊是否在page頁面之前,如果是,則整個描述就結束了。從這里我們可以看出asma->unpinned_list列表是按照頁面號從大到小進行排列的,并且每一塊被unpin的內存都是不相交的。
?? ? ? ?再來看一下page_range_subsumed_by_range的操作,這也是一個宏定義:
[cpp] view plaincopy
#define?page_range_subsumed_by_range(range,?start,?end)?\?? ??(((range)->pgstart?<=?(start))?&&?((range)->pgend?>=?(end)))?? ?? ? ? 表示range描述的內存塊是不是包含了[start, end]這個內存塊,如果包含了,則說明當前要unpin的內存塊已經處于unpinned狀態,什么也不用操作,直接返回即可。
?? ? ? 再看page_range_in_range的操作,它也是一個宏定義:
[cpp] view plaincopy
#define?page_range_in_range(range,?start,?end)?\?? ??(page_in_range(range,?start)?||?page_in_range(range,?end)?||?\?? ???page_range_subsumes_range(range,?start,?end))?? ?? ? ?它用到的其它兩個宏分別定義為:
[cpp] view plaincopy
#define?page_range_subsumed_by_range(range,?start,?end)?\?? ??(((range)->pgstart?<=?(start))?&&?((range)->pgend?>=?(end)))?? ?? #define?page_in_range(range,?page)?\?? ?(((range)->pgstart?<=?(page))?&&?((range)->pgend?>=?(page)))?? ?? ? ?它們都是用來判斷兩個內存區間是否相交的。
?? ? ?兩個內存塊相交分為四種情況:
?? ? ?|-------range-----|? ? ? ? |-------range------|?? ? ? |--------range---------|? ? ? ? ? ? ? ? ?|----range---|
?? ? ? |-start----end-| ? ? ? |-start-----end-| ?? ? ? ? ? ? ? ? ? ? ? |-start-------end-| ? ? ? ?|-start-----------end-| ?? ? ? ? ? ? ? ? (1) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(2) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(3) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(4) ?? ? ?第一種情況,前面已經討論過了,對于第二到第四種情況,都是需要執行合并操作的。
?? ? ?再來看從asma->unpinned_list中刪掉內存塊的range_del函數:
[cpp] view plaincopy
static?void?range_del(struct?ashmem_range?*range)?? {?? ????list_del(&range->unpinned);?? ????if?(range_on_lru(range))?? ????????lru_del(range);?? ????kmem_cache_free(ashmem_range_cachep,?range);?? }?? ?? ? ?這個函數首先把range從相應的unpinned_list列表中刪除,然后判斷它是否在lru列表中:
[cpp] view plaincopy
#define?range_on_lru(range)?\?? ??((range)->purged?==?ASHMEM_NOT_PURGED)?? ?? ? ?如果它的狀態purged等于ASHMEM_NOT_PURGED,即對應的物理頁面尚未被回收,它就位于lru列表中,通過調用lru_del函數進行刪除:
[cpp] view plaincopy
static?inline?void?lru_del(struct?ashmem_range?*range)?? {?? ????list_del(&range->lru);?? ????lru_count?-=?range_size(range);?? }?? ?? ? ? 最后調用kmem_cache_free將它從slab緩沖區ashmem_range_cachep中釋放。
?? ? ? 這里的slab緩沖區ashmem_range_cachep定義如下:
[cpp] view plaincopy
static?struct?kmem_cache?*ashmem_range_cachep?__read_mostly;?? ?? ? ? 它和前面介紹的slab緩沖區ashmem_area_cachep一樣,是在Ashmem驅動程序模塊初始化函數ashmem_init進行初始化的:
[cpp] view plaincopy
static?int?__init?ashmem_init(void)?? {?? ????int?ret;?? ?? ????......?? ?? ????ashmem_range_cachep?=?kmem_cache_create("ashmem_range_cache",?? ????????sizeof(struct?ashmem_range),?? ????????0,?0,?NULL);?? ????if?(unlikely(!ashmem_range_cachep))?{?? ????????printk(KERN_ERR?"ashmem:?failed?to?create?slab?cache\n");?? ????????return?-ENOMEM;?? ????}?? ?? ????......?? ?? ????printk(KERN_INFO?"ashmem:?initialized\n");?? ?? ????return?0;?? }?? ?? ? ? 回到ashmem_unpin函數中,我們再來看看range_alloc函數的實現:
[cpp] view plaincopy
static?int?range_alloc(struct?ashmem_area?*asma,?? ???????????????struct?ashmem_range?*prev_range,?unsigned?int?purged,?? ???????????????size_t?start,?size_t?end)?? {?? ????struct?ashmem_range?*range;?? ?? ????range?=?kmem_cache_zalloc(ashmem_range_cachep,?GFP_KERNEL);?? ????if?(unlikely(!range))?? ????????return?-ENOMEM;?? ?? ????range->asma?=?asma;?? ????range->pgstart?=?start;?? ????range->pgend?=?end;?? ????range->purged?=?purged;?? ?? ????list_add_tail(&range->unpinned,?&prev_range->unpinned);?? ?? ????if?(range_on_lru(range))?? ????????lru_add(range);?? ?? ????return?0;?? }?? ?? ? ? 這個函數的作用是從slab 緩沖區中ashmem_range_cachep分配一個ashmem_range,然后對它作相應的初始化,放在相應的 ashmem_area->unpinned_list列表中,并且還要判斷這個range的purged是否是 ASHMEM_NOT_PURGED狀態,如果是,還要把它放在lru列表中:
[cpp] view plaincopy
static?inline?void?lru_add(struct?ashmem_range?*range)?? {?? ????list_add_tail(&range->lru,?&ashmem_lru_list);?? ????lru_count?+=?range_size(range);?? }?? ?? ? ? 這樣,ashmem_unpin的源代碼我們就分析完了。
?? ? ? 接著,我們再來看一下ASHMEM_PIN操作,進入到ashmem_pin函數:
[cpp] view plaincopy
static?int?ashmem_pin(struct?ashmem_area?*asma,?size_t?pgstart,?size_t?pgend)?? {?? ????struct?ashmem_range?*range,?*next;?? ????int?ret?=?ASHMEM_NOT_PURGED;?? ?? ????list_for_each_entry_safe(range,?next,?&asma->unpinned_list,?unpinned)?{?? ???????? ????????if?(range_before_page(range,?pgstart))?? ????????????break;?? ?? ???????? ????????if?(page_range_in_range(range,?pgstart,?pgend))?{?? ????????????ret?|=?range->purged;?? ?? ???????????? ????????????if?(page_range_subsumes_range(range,?pgstart,?pgend))?{?? ????????????????range_del(range);?? ????????????????continue;?? ????????????}?? ?? ???????????? ????????????if?(range->pgstart?>=?pgstart)?{?? ????????????????range_shrink(range,?pgend?+?1,?range->pgend);?? ????????????????continue;?? ????????????}?? ?? ???????????? ????????????if?(range->pgend?<=?pgend)?{?? ????????????????range_shrink(range,?range->pgstart,?pgstart-1);?? ????????????????continue;?? ????????????}?? ?? ???????????? ????????????range_alloc(asma,?range,?range->purged,?? ????????????????????pgend?+?1,?range->pgend);?? ????????????range_shrink(range,?range->pgstart,?pgstart?-?1);?? ????????????break;?? ????????}?? ????}?? ?? ????return?ret;?? }?? ?? ? ? ?前面我們說過,被pin的內存塊,必須是在unpinned_list列表中的,如果不在,就什么都不用做。要判斷要pin的內存塊是否在 unpinned_list列表中,又要通過遍歷相應的asma->unpinned_list列表來找出與之相交的內存塊了。這個函數的處理方法 大體與前面的ashmem_unpin函數是一致的,也是要考慮四種不同的相交情況,這里就不詳述了,讀者可以自己分析一下。
?? ? ? ?這里我們只看一下range_shrink函數的實現:
[cpp] view plaincopy
static?inline?void?range_shrink(struct?ashmem_range?*range,?? ????????????????size_t?start,?size_t?end)?? {?? ????size_t?pre?=?range_size(range);?? ?? ????range->pgstart?=?start;?? ????range->pgend?=?end;?? ?? ????if?(range_on_lru(range))?? ????????lru_count?-=?pre?-?range_size(range);?? }?? ?? ? ? ?這個函數的實現很簡單,只是調整一下range描述的內存塊的起始頁面號,如果它是位于lru列表中,還要調整一下在lru列表中的總頁面數大小。
?? ? ? ?這樣,匿名共享內存的ASHMEM_PIN和ASHMEM_UNPIN操作就介紹完了,但是,我們還看不出來Ashmem驅動程序是怎么樣輔助內存管理 系統來有效管理內存的。有了前面這些unpinned的內存塊列表之后,下面我們就看一下Ashmem驅動程序是怎么樣輔助內存管理系統來有效管理內存 的。
?? ? ? ?首先看一下Ashmem驅動程序模塊初始化函數ashmem_init:
[cpp] view plaincopy
static?struct?shrinker?ashmem_shrinker?=?{?? ????.shrink?=?ashmem_shrink,?? ????.seeks?=?DEFAULT_SEEKS?*?4,?? };?? ?? static?int?__init?ashmem_init(void)?? {?? ????int?ret;?? ?? ????......?? ?? ????register_shrinker(&ashmem_shrinker);?? ?? ????printk(KERN_INFO?"ashmem:?initialized\n");?? ?? ????return?0;?? }?? ?? ? ? ?這里通過調用register_shrinker函數向內存管理系統注冊一個內存回收算法函數。在Linux內核中,當系統內存緊張時,內存管理系統就 會進行內存回收算法,將一些最近沒有用過的內存換出物理內存去,這樣可以增加物理內存的供應。因此,當內存管理系統進行內存回收時,就會調用到這里的 ashmem_shrink函數,讓Ashmem驅動程序執行內存回收操作:
[cpp] view plaincopy
static?int?ashmem_shrink(int?nr_to_scan,?gfp_t?gfp_mask)?? {?? ????struct?ashmem_range?*range,?*next;?? ?? ???? ????if?(nr_to_scan?&&?!(gfp_mask?&?__GFP_FS))?? ????????return?-1;?? ????if?(!nr_to_scan)?? ????????return?lru_count;?? ?? ????mutex_lock(&ashmem_mutex);?? ????list_for_each_entry_safe(range,?next,?&ashmem_lru_list,?lru)?{?? ????????struct?inode?*inode?=?range->asma->file->f_dentry->d_inode;?? ????????loff_t?start?=?range->pgstart?*?PAGE_SIZE;?? ????????loff_t?end?=?(range->pgend?+?1)?*?PAGE_SIZE?-?1;?? ?? ????????vmtruncate_range(inode,?start,?end);?? ????????range->purged?=?ASHMEM_WAS_PURGED;?? ????????lru_del(range);?? ?? ????????nr_to_scan?-=?range_size(range);?? ????????if?(nr_to_scan?<=?0)?? ????????????break;?? ????}?? ????mutex_unlock(&ashmem_mutex);?? ?? ????return?lru_count;?? }?? ?? ? ? ?這里的參數nr_to_scan表示要掃描的頁數,如果是0,則表示要查詢一下,當前Ashmem驅動程序有多少頁面可以回收,這里就等于掛在lru列 表的內塊頁面的總數了,即lru_count;否則,就要開始掃描lru列表,從中回收內存了,直到回收的內存頁數等于nr_to_scan,或者已經沒 有內存可回收為止。回收內存頁面是通過vm_truncate_range函數進行的,這個函數定義在kernel/common/mm /memory.c文件中,它是Linux內核內存管理系統實現的,有興趣的讀者可以研究一下。
?? ? ? ?這樣,Android系統匿名共享內存Ashmem驅動程序源代碼就分析完了,在下一篇文章中,我們將繼續分析Android系統的匿名共享內存機制,研究它是如何通過Binder進程間通信機制實現在不同進程程進行內存共享的,敬請關注。
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!
轉載于:https://www.cnblogs.com/Free-Thinker/p/4142112.html
總結
以上是生活随笔 為你收集整理的Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。