文章轉(zhuǎn)載至CSDN社區(qū)羅升陽的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554
在上一文章Android系統(tǒng)匿名共享內(nèi)存Ashmem(Anonymous Shared Memory)簡要介紹和學(xué)習(xí)計劃中, 我們簡要介紹了Android系統(tǒng)的匿名共享內(nèi)存機制,其中,簡要提到了它具有輔助內(nèi)存管理系統(tǒng)來有效地管理內(nèi)存的特點,但是沒有進一步去了解它是如何實 現(xiàn)的。在本文中,我們將通過分析Android系統(tǒng)的匿名共享內(nèi)存Ashmem驅(qū)動程序的源代碼,來深入了解它是如何輔助內(nèi)存管理系
?? ? ? ?Android系統(tǒng)的匿名共享內(nèi)存Ashmem機制并沒有自立山頭,從頭搞一套自己的共享內(nèi)存機制,而是建立在Linux內(nèi)核實現(xiàn)的共享內(nèi)存的基礎(chǔ)上 的。與此同時,它又向Linux內(nèi)存管理系統(tǒng)的內(nèi)存回收算法注冊接口,告訴Linux內(nèi)存管理系統(tǒng)它的某些內(nèi)存塊不再使用了,可以被回收了,不過,這些不 再使用的內(nèi)存需要由它的使用者來告訴Ashmem驅(qū)動程序。通過這種用戶-Ashmem驅(qū)動程序-內(nèi)存管理系統(tǒng)三者的緊密合作,實現(xiàn)有效的內(nèi)存管理機制, 適合移動設(shè)備小內(nèi)存的特點。
?? ? ? ?Android系統(tǒng)的匿名共享內(nèi)存Ashmem驅(qū)動程序利用了Linux的共享內(nèi)存子系統(tǒng)導(dǎo)出的接口來實現(xiàn)自己的功能,因此,它的實現(xiàn)非常小巧,總共代 碼不到700行。雖然代碼很少,但是這里不打算機械式地一行一行地閱讀和分析Ashmem驅(qū)動程序的源代碼,而是通過使用情景來分析,這樣可以幫助我們清 晰地理解它的實現(xiàn)原理。我們這里所說的使用情景,將從Android系統(tǒng)的應(yīng)用程序框架層提供的匿名共享內(nèi)存接口開始,經(jīng)過系統(tǒng)運行時庫層,最終到達驅(qū)動 程序?qū)?#xff0c;通過這樣一個完整的過程來理解Android系統(tǒng)的匿名共享內(nèi)存Ashmem機制。這里,我們將從上一篇文章Android系統(tǒng)匿名共享內(nèi)存Ashmem(Anonymous Shared Memory)簡要介紹和學(xué)習(xí)計劃介紹的Android應(yīng)用程序框架層提供MemoryFile接口開始,分別介紹Android系統(tǒng)匿名共享內(nèi)存的創(chuàng)建(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)四個使用情景。
?? ? ? ?在進入到這個四個使用情景前,我們先來看一下Ashmem驅(qū)動程序模塊的初始化函數(shù),看看它給用戶空間暴露了什么接口,即它創(chuàng)建了什么樣的設(shè)備文件,以 及提供了什么函數(shù)來操作這個設(shè)備文件。Ashmem驅(qū)動程序?qū)崿F(xiàn)在kernel/common/mm/ashmem.c文件中,它的模塊初始化函數(shù)定義為 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驅(qū)動程序在加載時,會創(chuàng)建一個/dev/ashmem的設(shè)備文件,這是一個misc類型的設(shè)備。注冊misc設(shè)備是通過misc_register函數(shù)進行的,關(guān)于這個函數(shù)的詳細實現(xiàn),可以參考前面Android日志系統(tǒng)驅(qū)動程序Logger源代碼分析一 文,調(diào)用這個函數(shù)成功后,就會在/dev目錄下生成一個ashmem設(shè)備文件了。同時,我們還可以看到,這個設(shè)備文件提供了open、mmap、 release和ioctl四種操作。為什么沒有read和write操作呢?這是因為讀寫共享內(nèi)存的方法是通過內(nèi)存映射地址來進行的,即通過mmap系 統(tǒng)調(diào)用把這個設(shè)備文件映射到進程地址空間中,然后就直接對內(nèi)存進行讀寫了,不需要通過read 和write文件操作,后面我們將會具體分析是如何實現(xiàn)的。 ?? ? ? 有了這個基礎(chǔ)之后,下面我們就分四個部分來分別介紹匿名共享內(nèi)存的創(chuàng)建(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)使用情景。
?? ? ? ?一. 匿名共享內(nèi)存的創(chuàng)建操作
?? ? ? ?在Android應(yīng)用程序框架層提供MemoryFile類的構(gòu)造函數(shù)中,進行了匿名共享內(nèi)存的創(chuàng)建操作,我們先來看一下這個構(gòu)造函數(shù)的實現(xiàn),它位于 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);?? ????????......?? ????}?? ?? ????......?? }?? ?? ? ? ?這里我們看到,這個構(gòu)造函數(shù)最終是通過JNI方法native_open來創(chuàng)建匿名內(nèi)存共享文件。這個JNI方法native_open實現(xiàn)在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);?? }?? ?? ? ? ?這個函數(shù)又通過運行時庫提供的接口ashmem_create_region來創(chuàng)建匿名共享內(nèi)存,這個函數(shù)實現(xiàn)在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;?? }?? ?? ? ? ?這里,一共通過執(zhí)行三個文件操作系統(tǒng)調(diào)用來和Ashmem驅(qū)動程序進行交互,分雖是一個open和兩個ioctl操作,前者是打開設(shè)備文件ASHMEM_DEVICE,后者分別是設(shè)置匿名共享內(nèi)存的名稱和大小。
?? ? ? ?在介紹這三個文件操作之前,我們先來了解一下Ashmem驅(qū)動程序的一個相關(guān)數(shù)據(jù)結(jié)構(gòu)struct ashmem_area,這個數(shù)據(jù)結(jié)構(gòu)就是用來表示一塊共享內(nèi)存的,它定義在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表示這塊共享內(nèi)存的名字,這個名字會顯示/proc/<pid>/maps文件中,<pid>表示打開這個共享內(nèi)存 文件的進程ID;域unpinned_list是一個列表頭,它把這塊共享內(nèi)存中所有被解鎖的內(nèi)存塊連接在一起,下面我們講內(nèi)存塊的鎖定和解鎖操作時會看 到它的用法;域file表示這個共享內(nèi)存在臨時文件系統(tǒng)tmpfs中對應(yīng)的文件,在內(nèi)核決定要把這塊共享內(nèi)存對應(yīng)的物理頁面回收時,就會把它的內(nèi)容交換到 這個臨時文件中去;域size表示這塊共享內(nèi)存的大小;域prot_mask表示這塊共享內(nèi)存的訪問保護位。
?? ? ? ?在Ashmem驅(qū)動程中,所有的ashmem_area實例都是從自定義的一個slab緩沖區(qū)創(chuàng)建的。這個slab緩沖區(qū)是在驅(qū)動程序模塊初始化函數(shù)創(chuàng)建的,我們來看一個這個初始化函數(shù)的相關(guān)實現(xiàn):
[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緩沖區(qū),由內(nèi)核中的內(nèi)存管理系統(tǒng)進行管理。
?? ? ? ?這里就是通過kmem_cache_create函數(shù)來創(chuàng)建一個名為"ashmem_area_cache"、對象大小為sizeof(struct ashmem_area)的緩沖區(qū)了。緩沖區(qū)創(chuàng)建了以后,就可以每次從它分配一個struct ashmem_area對象了。關(guān)于Linux內(nèi)核的slab緩沖區(qū)的相關(guān)知識,可以參考前面Android學(xué)習(xí)啟動篇一文中提到的一本參考書籍《Understanding the Linux Kernel》的第八章Memory Managerment。
?? ? ? ?有了這些基礎(chǔ)知識后,我們回到前面的ashmem_create_region函數(shù)中。
?? ? ? ?首先是執(zhí)行打開文件的操作:
[cpp] view plaincopy
fd?=?open(ASHMEM_DEVICE,?O_RDWR);?? ?? ? ? ?ASHMEM_DEVICE是一個宏,定義為:
[cpp] view plaincopy
#define?ASHMEM_DEVICE???"/dev/ashmem"?? ?? ? ? ? 這里就是匿名共享內(nèi)存設(shè)備文件/dev/ashmem了。
?? ? ? ?從上面的描述我們可以知道,調(diào)用這個open函數(shù)最終會進入到Ashmem驅(qū)動程序中的ashmem_open函數(shù)中去:
[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函數(shù)來設(shè)備這個文件不可以執(zhí)行定位操作,即不可以執(zhí)行seek文件操作。接著就是通過 kmem_cache_zalloc函數(shù)從剛才我們創(chuàng)建的slab緩沖區(qū)ashmem_area_cachep來創(chuàng)建一個ashmem_area結(jié)構(gòu)體 了,并且保存在本地變量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)?? ?? ? ? ?函數(shù)的最后是把這個ashmem_area結(jié)構(gòu)保存在打開文件結(jié)構(gòu)體的private_data域中,這樣,Ashmem驅(qū)動程序就可以在其它地方通過這個private_data域來取回這個ashmem_area結(jié)構(gòu)了。
?? ? ? ?到這里,設(shè)備文件/dev/ashmem的打開操作就完成了,它實際上就是在Ashmem驅(qū)動程序中創(chuàng)建了一個ashmem_area結(jié)構(gòu),表示一塊新的共享內(nèi)存。
?? ? ? ?再回到ashmem_create_region函數(shù)中,又調(diào)用了兩次ioctl文件操作分別來設(shè)備這塊新建的匿名共享內(nèi)存的名字和大小。在 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調(diào)用,它最終進入到Ashmem驅(qū)動程序的ashmem_ioctl函數(shù)中:
[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函數(shù)來進行實際操作:
[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;?? }?? ?? ? ? ?這個函數(shù)實現(xiàn)很簡單,把用戶空間傳進來的匿名共享內(nèi)存的名字設(shè)備到asma->name域中去。注意,匿名共享內(nèi)存塊的名字的內(nèi)容分兩部分,前一 部分是前綴,這是在open操作時,由驅(qū)動程序默認設(shè)置的,固定為ASHMEM_NAME_PREFIX,即"dev/ashmem/";后一部分由用戶 指定,這一部分是可選的,即用戶可以不調(diào)用ASHMEM_SET_NAME命令來設(shè)置匿名共享內(nèi)存塊的名字。
?? ? ? ?再來看ASHMEM_SET_SIZE命令的ioctl調(diào)用,它最終也是進入到Ashmem驅(qū)動程序的ashmem_ioctl函數(shù)中:
[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;?? }?? ?? ? ? ?這個實現(xiàn)很簡單,只是把用戶空間傳進來的匿名共享內(nèi)存的大小值保存在對應(yīng)的asma->size域中。
?? ? ? ?這樣,ashmem_create_region函數(shù)就執(zhí)先完成了,層層返回,最后回到應(yīng)用程序框架層提供的接口Memory的構(gòu)造函數(shù)中,整個匿名共 享內(nèi)存的創(chuàng)建過程就完成了。前面我們說過過,Ashmem驅(qū)動程序不提供read和write文件操作,進程若要訪問這個共享內(nèi)存,必須要把這個設(shè)備文件 映射到自己的進程空間中,然后進行直接內(nèi)存訪問,這就是我們下面要介紹的匿名共享內(nèi)存設(shè)備文件的內(nèi)存映射操作了。
?? ? ? ?二.?匿名共享內(nèi)存設(shè)備文件的內(nèi)存映射操作
?? ? ? ?在MemoryFile類的構(gòu)造函數(shù)中,進行了匿名共享內(nèi)存的創(chuàng)建操作后,下一步就是要把匿名共享內(nèi)存設(shè)備文件映射到進程空間來了:
[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);?? ????????......?? ????}?? }?? ?? ? ? ? 映射匿名共享內(nèi)存設(shè)備文件到進程空間是通過JNI方法native_mmap來進行的。這個JNI方法實現(xiàn)在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匿名設(shè)備文件/dev/ashmem獲得的,有個這個文件描述符后,就可以直接通過mmap來執(zhí)行內(nèi)存映射操作了。這個mmap系統(tǒng)調(diào)用最終進入到Ashmem驅(qū)動程序的ashmem_mmap函數(shù)中:
[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;?? }?? ?? ? ? ?這個函數(shù)的實現(xiàn)也很簡單,它調(diào)用了Linux內(nèi)核提供的shmem_file_setup函數(shù)來在臨時文件系統(tǒng)tmpfs中創(chuàng)建一個臨時文件,這個臨時 文件與Ashmem驅(qū)動程序創(chuàng)建的匿名共享內(nèi)存對應(yīng)。函數(shù)shmem_file_setup是Linux內(nèi)核中用來創(chuàng)建共享內(nèi)存文件的方法,而Linux 內(nèi)核中的共享內(nèi)存機制其實是一種進程間通信(IPC)機制,它的實現(xiàn)相對也是比較復(fù)雜,Android系統(tǒng)的匿名共享內(nèi)存機制正是由于直接使用了 Linux內(nèi)核共享內(nèi)存機制,它才會很小巧,它站在巨人的肩膀上了。關(guān)于Linux內(nèi)核中的共享內(nèi)存的相關(guān)知識,可以參考前面Android學(xué)習(xí)啟動篇一文中提到的一本參考書籍《Linux內(nèi)核源代碼情景分析》的第六章傳統(tǒng)的Unix進程間通信第七小節(jié)共享內(nèi)存。
?? ? ? ?通過shmem_file_setup函數(shù)創(chuàng)建的臨時文件vmfile最終就保存在vma->file中了。這里的vma是由Linux內(nèi)核的文 件系統(tǒng)層傳進來的,它的類型為struct vm_area_struct,它表示的是當前進程空間中一塊連續(xù)的虛擬地址空間,它的起始地址可以由用戶來指定,也可以由內(nèi)核自己來分配,這里我們從 JNI方法native_mmap調(diào)用的mmap的第一個參數(shù)為NULL可以看出,這塊連續(xù)的虛擬地址空間的起始地址是由內(nèi)核來指定的。文件內(nèi)存映射操作 完成后,用戶訪問這個范圍的地址空間就相當于是訪問對應(yīng)的文件的內(nèi)容了。有關(guān)Linux文件的內(nèi)存映射操作,同樣可以參考前面Android學(xué)習(xí)啟動篇一文中提到的一本參考書籍《Linux內(nèi)核源代碼情景分析》的第二章內(nèi)存管理第十三小節(jié)系統(tǒng)調(diào)用mmap。從這里我們也可以看出,Android系統(tǒng)的匿名共享內(nèi)存是在虛擬地址空間連續(xù)的,但是在物理地址空間就不一定是連續(xù)的了。
?? ? ? ?同時,這個臨時文件vmfile也會保存asma->file域中,這樣,Ashmem驅(qū)動程序后面就可以通過在asma->file來操作這個匿名內(nèi)存共享文件了。
?? ? ? ?函數(shù)ashmem_mmap執(zhí)行完成后,經(jīng)過層層返回到JNI方法native_mmap中去,就從mmap函數(shù)的返回值中得到了這塊虛擬空間的起始地 址了,這個起始地址最終返回到應(yīng)用程序框架層的MemoryFile類的構(gòu)造函數(shù)中,并且保存在成員變量mAddress中,后面,共享內(nèi)存的讀寫操作就 是對這個地址空間進行操作了。
?? ? ? ?三.?匿名共享內(nèi)存的讀寫操作
?? ? ? ?因為前面對匿名共享內(nèi)存文件進行內(nèi)存映射操作,這里對匿名內(nèi)存文件內(nèi)容的讀寫操作就比較簡單了,就像訪問內(nèi)存變量一樣就行了。
?? ? ? ?我們來看一下MemoryFile類的讀寫操作函數(shù):
[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的匿名共享內(nèi)存讀寫操作都是通過JNI方法來實現(xiàn)的,讀操作和寫操作的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參數(shù)就是我們在前面執(zhí)行mmap來映射匿名共享內(nèi)存文件到內(nèi)存中時,得到的進程虛擬地址空間的起始地址了,因此,這里就直接可以訪 問,不必進入到Ashmem驅(qū)動程序中去,這也是為什么Ashmem驅(qū)動程序沒有提供read和write文件操作的原因。
?? ? ? ?這里我們看到的ashmem_pin_region和ashmem_unpin_region兩個函數(shù)是系統(tǒng)運行時庫提供的接口,用來執(zhí)行我們前面說的 匿名共享內(nèi)存的鎖定和解鎖操作,它們的作用是告訴Ashmem驅(qū)動程序,它的哪些內(nèi)存塊是正在使用的,需要鎖定,哪些內(nèi)存是不需要使用了,可以它解鎖,這 樣,Ashmem驅(qū)動程序就可以輔助內(nèi)存管理系統(tǒng)來有效地管理內(nèi)存了。下面我們就看看Ashmem驅(qū)動程序是如果輔助內(nèi)存管理系統(tǒng)來有效地管理內(nèi)存的。
?? ? ? ?四.?匿名共享內(nèi)存的鎖定和解鎖操作 ?? ? ? ?前面提到,Android系統(tǒng)的運行時庫提到了執(zhí)行匿名共享內(nèi)存的鎖定和解鎖操作的兩個函數(shù)ashmem_pin_region和 ashmem_unpin_region,它們實現(xiàn)在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);?? }?? ?? ? ? 它們的實現(xiàn)很簡單,通過ASHMEM_PIN和ASHMEM_UNPIN兩個ioctl操作來實現(xiàn)匿名共享內(nèi)存的鎖定和解鎖操作。
?? ? ? 我們先看來一下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)?? ?? ? ? 它們的參數(shù)類型為struct ashmem_pin,它也是定義在kernel/common/include/linux/ashmem.h文件中:
[cpp] view plaincopy
struct?ashmem_pin?{?? ????__u32?offset;??? ????__u32?len;?? };?? ?? ? ? 這個結(jié)構(gòu)體只有兩個域,分別表示要鎖定或者要解鎖的內(nèi)塊塊的起始大小以及大小。
?? ? ? 在分析這兩個操作之前,我們先來看一下Ashmem驅(qū)動程序中的一個數(shù)據(jù)結(jié)構(gòu)struct ashmem_range,這個數(shù)據(jù)結(jié)構(gòu)就是用來表示某一塊被解鎖(unpinnd)的內(nèi)存:
[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表示這塊被解鎖的內(nèi)存所屬于的匿名共享內(nèi)存,它通過域unpinned連接在asma->unpinned_list表示的列表中;域 pgstart和paend表示這個內(nèi)存塊的開始和結(jié)束頁面號,它們表示一個前后閉合的區(qū)間;域purged表示這個內(nèi)存塊占用的物理內(nèi)存是否已經(jīng)被回 收;這塊被解鎖的內(nèi)存塊除了保存在它所屬的匿名共享內(nèi)存asma的解鎖列表unpinned_list之外,還通過域lru保存在一個全局的最近最少使用 列表ashmem_lru_list列表中,它的定義如下:
[cpp] view plaincopy
static?LIST_HEAD(ashmem_lru_list);?? ?? ? ? ?了解了這個數(shù)據(jù)結(jié)構(gòu)之后,我們就可以來看ashmem_ioctl函數(shù)中關(guān)于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;?? }?? ?? ? ? ?首先是獲得用戶空間傳進來的參數(shù),并保存在本地變量pin中,這是一個struct ashmem_pin類型的變量,這個結(jié)構(gòu)體我們在前面已經(jīng)見過了,它包括了要pin/unpin的內(nèi)存塊的起始地址和大小,這里的起始地址和大小都是以 字節(jié)為單位的,因此,通過轉(zhuǎn)換把它們換成以頁面為單位的,并且保存在本地變量pgstart和pgend中。這里除了要對參數(shù)作一個安全性檢查外,還要一 個處理邏輯是,如果從用戶空間傳進來的內(nèi)塊塊的大小值為0 ,則認為是要pin/unpin整個匿名共享內(nèi)存。
?? ? ? ?函數(shù)最后根據(jù)當前要執(zhí)行的是ASHMEM_PIN操作還是ASHMEM_UNPIN操作來分別執(zhí)行ashmem_pin和ashmem_unpin來進 一步處理。創(chuàng)建匿名共享內(nèi)存時,默認所有的內(nèi)存都是pinned狀態(tài)的,只有用戶告訴Ashmem驅(qū)動程序要unpin某一塊內(nèi)存時,Ashmem驅(qū)動程 序才會把這塊內(nèi)存unpin,之后,用戶可以再告訴Ashmem驅(qū)動程序要重新pin某一塊之前被unpin過的內(nèi)塊,從而把這塊內(nèi)存從unpinned 狀態(tài)改為pinned狀態(tài),也就是說,執(zhí)行ASHMEM_PIN操作時,目標對象必須是一塊當前處于unpinned狀態(tài)的內(nèi)存塊。
?? ? ? 我們先來看一下ASHMEM_UNPIN操作,進入到ashmem_unpin函數(shù):
[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);?? }?? ?? ? ? ?這個函數(shù)的主體就是在遍歷asma->unpinned_list列表,從中查找當前處于unpinned狀態(tài)的內(nèi)存塊是否與將要unpin的內(nèi) 存塊[pgstart, pgend]是否相交,如果相交,則要執(zhí)行合并操作,即調(diào)整pgstart和pgend的大小,然后通過調(diào)用range_del函數(shù)刪掉原來的已經(jīng)被 unpinned過的內(nèi)存塊,最后再通過range_alloc函數(shù)來重新unpinned這塊調(diào)整過后的內(nèi)存塊[pgstart, pgend],這里新的內(nèi)存塊[pgstart, pgend]已經(jīng)包含了剛才所有被刪掉的unpinned狀態(tài)的內(nèi)存。注意,這里如果找到一塊相并的內(nèi)存塊,并且調(diào)整了pgstart和pgend的大小 之后,要重新再掃描一遍asma->unpinned_list列表,因為新的內(nèi)存塊[pgstart, pgend]可能還會與前后的處于unpinned狀態(tài)的內(nèi)存塊發(fā)生相交。
?? ? ? ?我們來看一下range_before_page的操作,這是一個宏定義:
[cpp] view plaincopy
#define?range_before_page(range,?page)?\?? ??((range)->pgend?<?(page))?? ?? ? ? ?表示range描述的內(nèi)存塊是否在page頁面之前,如果是,則整個描述就結(jié)束了。從這里我們可以看出asma->unpinned_list列表是按照頁面號從大到小進行排列的,并且每一塊被unpin的內(nèi)存都是不相交的。
?? ? ? ?再來看一下page_range_subsumed_by_range的操作,這也是一個宏定義:
[cpp] view plaincopy
#define?page_range_subsumed_by_range(range,?start,?end)?\?? ??(((range)->pgstart?<=?(start))?&&?((range)->pgend?>=?(end)))?? ?? ? ? 表示range描述的內(nèi)存塊是不是包含了[start, end]這個內(nèi)存塊,如果包含了,則說明當前要unpin的內(nèi)存塊已經(jīng)處于unpinned狀態(tài),什么也不用操作,直接返回即可。
?? ? ? 再看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)))?? ?? ? ?它們都是用來判斷兩個內(nèi)存區(qū)間是否相交的。
?? ? ?兩個內(nèi)存塊相交分為四種情況:
?? ? ?|-------range-----|? ? ? ? |-------range------|?? ? ? |--------range---------|? ? ? ? ? ? ? ? ?|----range---|
?? ? ? |-start----end-| ? ? ? |-start-----end-| ?? ? ? ? ? ? ? ? ? ? ? |-start-------end-| ? ? ? ?|-start-----------end-| ?? ? ? ? ? ? ? ? (1) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(2) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(3) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(4) ?? ? ?第一種情況,前面已經(jīng)討論過了,對于第二到第四種情況,都是需要執(zhí)行合并操作的。
?? ? ?再來看從asma->unpinned_list中刪掉內(nèi)存塊的range_del函數(shù):
[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);?? }?? ?? ? ?這個函數(shù)首先把range從相應(yīng)的unpinned_list列表中刪除,然后判斷它是否在lru列表中:
[cpp] view plaincopy
#define?range_on_lru(range)?\?? ??((range)->purged?==?ASHMEM_NOT_PURGED)?? ?? ? ?如果它的狀態(tài)purged等于ASHMEM_NOT_PURGED,即對應(yīng)的物理頁面尚未被回收,它就位于lru列表中,通過調(diào)用lru_del函數(shù)進行刪除:
[cpp] view plaincopy
static?inline?void?lru_del(struct?ashmem_range?*range)?? {?? ????list_del(&range->lru);?? ????lru_count?-=?range_size(range);?? }?? ?? ? ? 最后調(diào)用kmem_cache_free將它從slab緩沖區(qū)ashmem_range_cachep中釋放。
?? ? ? 這里的slab緩沖區(qū)ashmem_range_cachep定義如下:
[cpp] view plaincopy
static?struct?kmem_cache?*ashmem_range_cachep?__read_mostly;?? ?? ? ? 它和前面介紹的slab緩沖區(qū)ashmem_area_cachep一樣,是在Ashmem驅(qū)動程序模塊初始化函數(shù)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函數(shù)中,我們再來看看range_alloc函數(shù)的實現(xiàn):
[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;?? }?? ?? ? ? 這個函數(shù)的作用是從slab 緩沖區(qū)中ashmem_range_cachep分配一個ashmem_range,然后對它作相應(yīng)的初始化,放在相應(yīng)的 ashmem_area->unpinned_list列表中,并且還要判斷這個range的purged是否是 ASHMEM_NOT_PURGED狀態(tài),如果是,還要把它放在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函數(shù):
[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的內(nèi)存塊,必須是在unpinned_list列表中的,如果不在,就什么都不用做。要判斷要pin的內(nèi)存塊是否在 unpinned_list列表中,又要通過遍歷相應(yīng)的asma->unpinned_list列表來找出與之相交的內(nèi)存塊了。這個函數(shù)的處理方法 大體與前面的ashmem_unpin函數(shù)是一致的,也是要考慮四種不同的相交情況,這里就不詳述了,讀者可以自己分析一下。
?? ? ? ?這里我們只看一下range_shrink函數(shù)的實現(xiàn):
[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);?? }?? ?? ? ? ?這個函數(shù)的實現(xiàn)很簡單,只是調(diào)整一下range描述的內(nèi)存塊的起始頁面號,如果它是位于lru列表中,還要調(diào)整一下在lru列表中的總頁面數(shù)大小。
?? ? ? ?這樣,匿名共享內(nèi)存的ASHMEM_PIN和ASHMEM_UNPIN操作就介紹完了,但是,我們還看不出來Ashmem驅(qū)動程序是怎么樣輔助內(nèi)存管理 系統(tǒng)來有效管理內(nèi)存的。有了前面這些unpinned的內(nèi)存塊列表之后,下面我們就看一下Ashmem驅(qū)動程序是怎么樣輔助內(nèi)存管理系統(tǒng)來有效管理內(nèi)存 的。
?? ? ? ?首先看一下Ashmem驅(qū)動程序模塊初始化函數(shù)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;?? }?? ?? ? ? ?這里通過調(diào)用register_shrinker函數(shù)向內(nèi)存管理系統(tǒng)注冊一個內(nèi)存回收算法函數(shù)。在Linux內(nèi)核中,當系統(tǒng)內(nèi)存緊張時,內(nèi)存管理系統(tǒng)就 會進行內(nèi)存回收算法,將一些最近沒有用過的內(nèi)存換出物理內(nèi)存去,這樣可以增加物理內(nèi)存的供應(yīng)。因此,當內(nèi)存管理系統(tǒng)進行內(nèi)存回收時,就會調(diào)用到這里的 ashmem_shrink函數(shù),讓Ashmem驅(qū)動程序執(zhí)行內(nèi)存回收操作:
[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;?? }?? ?? ? ? ?這里的參數(shù)nr_to_scan表示要掃描的頁數(shù),如果是0,則表示要查詢一下,當前Ashmem驅(qū)動程序有多少頁面可以回收,這里就等于掛在lru列 表的內(nèi)塊頁面的總數(shù)了,即lru_count;否則,就要開始掃描lru列表,從中回收內(nèi)存了,直到回收的內(nèi)存頁數(shù)等于nr_to_scan,或者已經(jīng)沒 有內(nèi)存可回收為止。回收內(nèi)存頁面是通過vm_truncate_range函數(shù)進行的,這個函數(shù)定義在kernel/common/mm /memory.c文件中,它是Linux內(nèi)核內(nèi)存管理系統(tǒng)實現(xiàn)的,有興趣的讀者可以研究一下。
?? ? ? ?這樣,Android系統(tǒng)匿名共享內(nèi)存Ashmem驅(qū)動程序源代碼就分析完了,在下一篇文章中,我們將繼續(xù)分析Android系統(tǒng)的匿名共享內(nèi)存機制,研究它是如何通過Binder進程間通信機制實現(xiàn)在不同進程程進行內(nèi)存共享的,敬請關(guān)注。
老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關(guān)注!
轉(zhuǎn)載于:https://www.cnblogs.com/Free-Thinker/p/4142112.html
總結(jié)
以上是生活随笔 為你收集整理的Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。