文章轉載至CSDN社區羅昇陽的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554java
在上一文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃中, 咱們簡要介紹了Android系統的匿名共享內存機制,其中,簡要提到了它具備輔助內存管理系統來有效地管理內存的特色,可是沒有進一步去了解它是如何實 現的。在本文中,咱們將經過分析Android系統的匿名共享內存Ashmem驅動程序的源代碼,來深刻了解它是如何輔助內存管理系node
Android系統的匿名共享內存Ashmem機制並無自立山頭,從頭搞一套本身的共享內存機制,而是創建在Linux內核實現的共享內存的基礎上 的。與此同時,它又向Linux內存管理系統的內存回收算法註冊接口,告訴Linux內存管理系統它的某些內存塊再也不使用了,能夠被回收了,不過,這些不 再使用的內存須要由它的使用者來告訴Ashmem驅動程序。經過這種用戶-Ashmem驅動程序-內存管理系統三者的緊密合做,實現有效的內存管理機制, 適合移動設備小內存的特色。linux
Android系統的匿名共享內存Ashmem驅動程序利用了Linux的共享內存子系統導出的接口來實現本身的功能,所以,它的實現很是小巧,總共代 碼不到700行。雖然代碼不多,可是這裏不打算機械式地一行一行地閱讀和分析Ashmem驅動程序的源代碼,而是經過使用情景來分析,這樣能夠幫助咱們清 晰地理解它的實現原理。咱們這裏所說的使用情景,將從Android系統的應用程序框架層提供的匿名共享內存接口開始,通過系統運行時庫層,最終到達驅動 程序層,經過這樣一個完整的過程來理解Android系統的匿名共享內存Ashmem機制。這裏,咱們將從上一篇文章Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃介紹的Android應用程序框架層提供MemoryFile接口開始,分別介紹Android系統匿名共享內存的建立(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)四個使用情景。android
在進入到這個四個使用情景前,咱們先來看一下Ashmem驅動程序模塊的初始化函數,看看它給用戶空間暴露了什麼接口,即它建立了什麼樣的設備文件,以 及提供了什麼函數來操做這個設備文件。Ashmem驅動程序實如今kernel/common/mm/ashmem.c文件中,它的模塊初始化函數定義爲 ashmem_init:算法
- 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文件中:app
- 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文件中:框架
- 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文件中:ide
- 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文件中:
- 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緩衝區是在驅動程序模塊初始化函數建立的,咱們來看一個這個初始化函數的相關實現:
- 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;
- }
全局變量定義在文件開頭的地方:
- 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函數中。
首先是執行打開文件的操做:
- fd = open(ASHMEM_DEVICE, O_RDWR);
ASHMEM_DEVICE是一個宏,定義爲:
- #define ASHMEM_DEVICE "/dev/ashmem"
這裏就是匿名共享內存設備文件/dev/ashmem了。
從上面的描述咱們能夠知道,調用這個open函數最終會進入到Ashmem驅動程序中的ashmem_open函數中去:
- 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,這是一個 宏,定義爲:
- #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的定義爲:
- #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函數中:
- 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函數來進行實際操做:
- 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函數中:
- 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類的構造函數中,進行了匿名共享內存的建立操做後,下一步就是要把匿名共享內存設備文件映射到進程空間來了:
- 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文件中:
- 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函數中:
- 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類的讀寫操做函數:
- 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文件中:
- 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文件中:
- 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文件中找到:
- #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文件中:
- struct ashmem_pin {
- __u32 offset;
- __u32 len;
- };
這個結構體只有兩個域,分別表示要鎖定或者要解鎖的內塊塊的起始大小以及大小。
在分析這兩個操做以前,咱們先來看一下Ashmem驅動程序中的一個數據結構struct ashmem_range,這個數據結構就是用來表示某一塊被解鎖(unpinnd)的內存:
- 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列表中,它的定義以下:
- static LIST_HEAD(ashmem_lru_list);
瞭解了這個數據結構以後,咱們就能夠來看ashmem_ioctl函數中關於ASHMEM_PIN和ASHMEM_UNPIN的操做了:
- static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
- {
- struct ashmem_area *asma = file->private_data;
- long ret = -ENOTTY;
-
- switch (cmd) {
- ......
- case ASHMEM_PIN:
- case ASHMEM_UNPIN:
- ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);
- break;
- ......
- }
-
- return ret;
- }
它們都是經過ashmem_pin_unpin來進一步處理:
- static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd,
- void __user *p)
- {
- struct ashmem_pin pin;
- size_t pgstart, pgend;
- int ret = -EINVAL;
-
- 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函數:
- 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的操做,這是一個宏定義:
- #define range_before_page(range, page) \
- ((range)->pgend < (page))
表示range描述的內存塊是否在page頁面以前,若是是,則整個描述就結束了。從這裏咱們能夠看出asma->unpinned_list列表是按照頁面號從大到小進行排列的,而且每一塊被unpin的內存都是不相交的。
再來看一下page_range_subsumed_by_range的操做,這也是一個宏定義:
- #define page_range_subsumed_by_range(range, start, end) \
- (((range)->pgstart <= (start)) && ((range)->pgend >= (end)))
表示range描述的內存塊是否是包含了[start, end]這個內存塊,若是包含了,則說明當前要unpin的內存塊已經處於unpinned狀態,什麼也不用操做,直接返回便可。
再看page_range_in_range的操做,它也是一個宏定義:
- #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))
它用到的其它兩個宏分別定義爲:
- #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函數:
- 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列表中:
- #define range_on_lru(range) \
- ((range)->purged == ASHMEM_NOT_PURGED)
若是它的狀態purged等於ASHMEM_NOT_PURGED,即對應的物理頁面還沒有被回收,它就位於lru列表中,經過調用lru_del函數進行刪除:
- 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定義以下:
- static struct kmem_cache *ashmem_range_cachep __read_mostly;
它和前面介紹的slab緩衝區ashmem_area_cachep同樣,是在Ashmem驅動程序模塊初始化函數ashmem_init進行初始化的:
- 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函數的實現:
- 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列表中:
- 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函數:
- 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函數的實現:
- 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:
- 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驅動程序執行內存回收操做:
- 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,歡迎關注!