原文出處——>[Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析](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:
~~~
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**文件中:
~~~
public class MemoryFile
{
......
private static native FileDescriptor native_open(String name, int length) throws IOException;
......
private FileDescriptor mFD; // ashmem file descriptor
......
private int mLength; // total length of our ashmem region
......
/**
* Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
* @throws IOException if the memory file could not be created.
*/
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**文件中:
~~~
/*
* ashmem_create_region - creates a new ashmem region and returns the file
* descriptor, or <0 on error
*
* `name' is an optional label to give the region (visible in /proc/pid/maps)
* `size' is the size of the region, in page-aligned bytes
*/
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**文件中:
~~~
/*
* ashmem_area - anonymous shared memory area
* Lifecycle: From our parent file's open() until its release()
* Locking: Protected by `ashmem_mutex'
* Big Note: Mappings do NOT pin this structure; it dies on close()
*/
struct ashmem_area {
char name[ASHMEM_FULL_NAME_LEN];/* optional name for /proc/pid/maps */
struct list_head unpinned_list; /* list of all ashmem areas */
struct file *file; /* the shmem-based backing file */
size_t size; /* size of the mapping, in bytes */
unsigned long prot_mask; /* allowed prot bits, as vm_flags */
};
~~~
域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函數中:
[cpp] view plain copy
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);
/* cannot change an existing mapping's name */
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
{
......
// returns memory address for ashmem region
private static native int native_mmap(FileDescriptor fd, int length, int mode)
throws IOException;
......
private int mAddress; // address of ashmem memory
......
/**
* Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
* @throws IOException if the memory file could not be created.
*/
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);
/* user needs to SET_SIZE before mapping */
if (unlikely(!asma->size)) {
ret = -EINVAL;
goto out;
}
/* requested protection bits must match our allowed protection mask */
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;
/* ... and allocate the backing shmem file */
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; // ashmem file descriptor
private int mAddress; // address of ashmem memory
private int mLength; // total length of our ashmem region
private boolean mAllowPurging = false; // true if our ashmem region is unpinned
......
/**
* Reads bytes from the memory file.
* Will throw an IOException if the file has been purged.
*
* @param buffer byte array to read bytes into.
* @param srcOffset offset into the memory file to read from.
* @param destOffset offset into the byte array buffer to read into.
* @param count number of bytes to read.
* @return number of bytes read.
* @throws IOException if the memory file has been purged or deactivated.
*/
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);
}
/**
* Write bytes to the memory file.
* Will throw an IOException if the file has been purged.
*
* @param buffer byte array to write bytes from.
* @param srcOffset offset into the byte array buffer to write from.
* @param destOffset offset into the memory file to write to.
* @param count number of bytes to write.
* @throws IOException if the memory file has been purged or deactivated.
*/
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文件中:
[cpp] view plain copy
struct ashmem_pin {
__u32 offset; /* offset into region, in bytes, page-aligned */
__u32 len; /* length forward from offset, in bytes, page-aligned */
};
~~~
這個結構體只有兩個域,分別表示要鎖定或者要解鎖的內塊塊的起始大小以及大小。
在分析這兩個操作之前,我們先來看一下Ashmem驅動程序中的一個數據結構struct ashmem_range,這個數據結構就是用來表示某一塊被解鎖(unpinnd)的內存:
~~~
/*
* ashmem_range - represents an interval of unpinned (evictable) pages
* Lifecycle: From unpin to pin
* Locking: Protected by `ashmem_mutex'
*/
struct ashmem_range {
struct list_head lru; /* entry in LRU list */
struct list_head unpinned; /* entry in its area's unpinned list */
struct ashmem_area *asma; /* associated area */
size_t pgstart; /* starting page, inclusive */
size_t pgend; /* ending page, inclusive */
unsigned int purged; /* ASHMEM_NOT or ASHMEM_WAS_PURGED */
};
~~~
域asma表示這塊被解鎖的內存所屬于的匿名共享內存,它通過域unpinned連接在asma->unpinned_list表示的列表中;域pgstart和paend表示這個內存塊的開始和結束頁面號,它們表示一個前后閉合的區間;域purged表示這個內存塊占用的物理內存是否已經被回收;這塊被解鎖的內存塊除了保存在它所屬的匿名共享內存asma的解鎖列表unpinned_list之外,還通過域lru保存在一個全局的最近最少使用列表ashmem_lru_list列表中,它的定義如下:
~~~
/* LRU list of unpinned pages, protected by ashmem_mutex */
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;
/* per custom, you can pass zero for len to mean "everything onward" */
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函數:
~~~
/*
* ashmem_unpin - unpin the given range of pages. Returns zero on success.
*
* Caller must hold ashmem_mutex.
*/
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) {
/* short circuit: this is our insertion point */
if (range_before_page(range, pgstart))
break;
/*
* The user can ask us to unpin pages that are already entirely
* or partially pinned. We handle those two cases here.
*/
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函數的實現:
~~~
/*
* range_alloc - allocate and initialize a new ashmem_range structure
*
* 'asma' - associated ashmem_area
* 'prev_range' - the previous ashmem_range in the sorted asma->unpinned list
* 'purged' - initial purge value (ASMEM_NOT_PURGED or ASHMEM_WAS_PURGED)
* 'start' - starting page, inclusive
* 'end' - ending page, inclusive
*
* Caller must hold ashmem_mutex.
*/
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函數:
~~~
/*
* ashmem_pin - pin the given ashmem region, returning whether it was
* previously purged (ASHMEM_WAS_PURGED) or not (ASHMEM_NOT_PURGED).
*
* Caller must hold ashmem_mutex.
*/
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) {
/* moved past last applicable page; we can short circuit */
if (range_before_page(range, pgstart))
break;
/*
* The user can ask us to pin pages that span multiple ranges,
* or to pin pages that aren't even unpinned, so this is messy.
*
* Four cases:
* 1. The requested range subsumes an existing range, so we
* just remove the entire matching range.
* 2. The requested range overlaps the start of an existing
* range, so we just update that range.
* 3. The requested range overlaps the end of an existing
* range, so we just update that range.
* 4. The requested range punches a hole in an existing range,
* so we have to update one side of the range and then
* create a new range for the other side.
*/
if (page_range_in_range(range, pgstart, pgend)) {
ret |= range->purged;
/* Case #1: Easy. Just nuke the whole thing. */
if (page_range_subsumes_range(range, pgstart, pgend)) {
range_del(range);
continue;
}
/* Case #2: We overlap from the start, so adjust it */
if (range->pgstart >= pgstart) {
range_shrink(range, pgend + 1, range->pgend);
continue;
}
/* Case #3: We overlap from the rear, so adjust it */
if (range->pgend <= pgend) {
range_shrink(range, range->pgstart, pgstart-1);
continue;
}
/*
* Case #4: We eat a chunk out of the middle. A bit
* more complicated, we allocate a new range for the
* second half and adjust the first chunk's endpoint.
*/
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函數的實現:
~~~
/*
* range_shrink - shrinks a range
*
* Caller must hold ashmem_mutex.
*/
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驅動程序執行內存回收操作:
~~~
/*
* ashmem_shrink - our cache shrinker, called from mm/vmscan.c :: shrink_slab
*
* 'nr_to_scan' is the number of objects (pages) to prune, or 0 to query how
* many objects (pages) we have in total.
*
* 'gfp_mask' is the mask of the allocation that got us into this mess.
*
* Return value is the number of objects (pages) remaining, or -1 if we cannot
* proceed without risk of deadlock (due to gfp_mask).
*
* We approximate LRU via least-recently-unpinned, jettisoning unpinned partial
* chunks of ashmem regions LRU-wise one-at-a-time until we hit 'nr_to_scan'
* pages freed.
*/
static int ashmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
struct ashmem_range *range, *next;
/* We might recurse into filesystem code, so bail out if necessary */
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進程間通信機制實現在不同進程程進行內存共享的,敬請關注。
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析