課程目標:
1. 匿名共享內存驅動ashmen.c
2. 匿名共享內存的框架結構
1. 匿名共享內存的驅動
首先我們應該找到匿名共享內存驅動的目錄,/home/linux/fspad-733/lichee/linux-3.4/drivers/staging/android/ashmem.c。
static struct miscdevice ashmem_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ashmem",
.fops = &ashmem_fops,
};
static int __init ashmem_init(void)
{
int ret;
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);
ashmem_range_cachep= kmem_cache_create("ashmem_range_cache",
sizeof(struct ashmem_range),
0, 0, NULL);
ret = misc_register(&ashmem_misc);
register_shrinker(&ashmem_shrinker);
return 0;
}
static void __exit ashmem_exit(void)
{
int ret;
unregister_shrinker(&ashmem_shrinker);
ret = misc_deregister(&ashmem_misc);
kmem_cache_destroy(ashmem_range_cachep);
kmem_cache_destroy(ashmem_area_cachep);
printk(KERN_INFO "ashmem: unloaded\n");
}
module_init(ashmem_init);
module_exit(ashmem_exit);
這里就是通過kmem_cache_create函數來創建一個名為"ashmem_area_cache"、對象大小為sizeof(struct ashmem_area)的緩沖區了。緩沖區創建了以后,就可以每次從它分配一個struct ashmem_area對象了。關于Linux內核的slab緩沖區的相關知識,slab分配內存請參考linux內存管理。我們可以看到這里的ashmem是采用的misc架構的。它采用動態分配次設備號。這里我們重點看它的操作方法集。
static const struct file_operations ashmem_fops = {
.owner = THIS_MODULE,
.open = ashmem_open,
.release = ashmem_release,
.read = ashmem_read,
.llseek = ashmem_llseek,
.mmap = ashmem_mmap,
.unlocked_ioctl = ashmem_ioctl,
.compat_ioctl = ashmem_ioctl,
};
當用戶層的open,read,ioctl函數別調用的時候,驅動中的相應的函數就會執行,所以接下來我們重點分析open,read,mmap,ioctl函數的執行過程。首先我們從open函數開始看
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
ret = generic_file_open(inode, file);
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);
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;
}
generic_file_open函數從注釋上我們就能夠看到,它是阻止在32位系統上打開一個過大的文件。注釋如下:
/*
* Called when an inode is about to be open.
* We use this to disallow opening large files on 32bit systems if
* the caller didn't specify O_LARGEFILE. On 64bit systems we force
* on this flag in sys_open.*/
kmem_cache_zalloc函數就是從剛才我們分配的緩沖區中分配一個結構體空間的大小,接下來就是對這個結構體信息的填充。這個結構體的名字被分配為如下這個宏。
#define ASHMEM_NAME_PREFIX "dev/ashmem/"
struct ashmem_area {
char name[ASHMEM_FULL_NAME_LEN]; /* optional name in /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_read函數,我們會發現它在函數內部調用自己,它的作用如注釋所說:
/*
* asma and asma->file are used outside the lock here. We assume
* once asma->file is set it will never be changed, and will not
* be destroyed until all references to the file are dropped and
* ashmem_release is called.
*/
只要程序開始運行那么它會一直運行下去(遞歸執行),知道asma中的file結構體被銷毀。它主要是保證file結構體不能被修改的。
ashmem_ioctl函數
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ashmem_area *asma = file->private_data;
long ret = -ENOTTY;
switch (cmd) {
case ASHMEM_SET_NAME:
ret = set_name(asma, (void __user *) arg);
break;
case ASHMEM_GET_NAME:
ret = get_name(asma, (void __user *) arg);
break;
case ASHMEM_SET_SIZE:
ret = -EINVAL;
if (!asma->file) {
ret = 0;
asma->size = (size_t) arg;
}
break;
case ASHMEM_GET_SIZE:
ret = asma->size;
break;
case ASHMEM_SET_PROT_MASK:
ret = set_prot_mask(asma, arg);
break;
case ASHMEM_GET_PROT_MASK:
ret = asma->prot_mask;
break;
case ASHMEM_PIN:
case ASHMEM_UNPIN:
case ASHMEM_GET_PIN_STATUS:
ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);
break;
case ASHMEM_PURGE_ALL_CACHES:
ret = -EPERM;
if (capable(CAP_SYS_ADMIN)) {
struct shrink_control sc = {
.gfp_mask = GFP_KERNEL,
.nr_to_scan = 0,
};
ret = ashmem_shrink(&ashmem_shrinker, &sc);
sc.nr_to_scan = ret;
ashmem_shrink(&ashmem_shrinker, &sc);
}
break;
}
return ret;
}
從這個ioctl函數中我們能清楚的看到,它里面有ASHMEM_SET_NAME,ASHMEM_GET_NAME,ASHMEM_SET_SIZE,ASHMEM_GET_SIZE等命令碼,這些命令碼就是來設置ashmem_area結構體中的變量的。通過ASHMEM_PIN,ASHMEM_UNPIN這兩個命令碼實現對匿名共享內存的鎖定和解鎖。
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列表中
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
case ASHMEM_PIN:
case ASHMEM_UNPIN:
case ASHMEM_GET_PIN_STATUS:
ret = ashmem_pin_unpin(asma, cmd, (void __user *) arg);
break;
}
static int ashmem_pin_unpin(struct ashmem_area *asma, unsigned long cmd,
void __user *p)
{
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;
case ASHMEM_GET_PIN_STATUS:
ret = ashmem_get_pin_status(asma, pgstart, pgend);
break;
}
mutex_unlock(&ashmem_mutex);
return ret;
}
函數后根據當前要執行的是ASHMEM_PIN操作還是ASHMEM_UNPIN操作來分別執行ashmem_pin和ashmem_unpin來進一步處理。創建匿名共享內存時,默認所有的內存都是pinned狀態的,只有用戶告訴Ashmem驅動程序要unpin某一塊內存時,Ashmem驅動程序才會把這塊內存unpin,之后,用戶可以再告訴Ashmem驅動程序要重新pin某一塊之前被unpin過的內塊,從而把這塊內存從unpinned狀態改為pinned狀態,也就是說,執行ASHMEM_PIN操作時,目標對象必須是一塊當前處于unpinned狀態的內存塊。
首先看一下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_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 & ~calc_vm_prot_bits(asma->prot_mask)) &
calc_vm_prot_bits(PROT_MASK))) {
ret = -EPERM;
goto out;
}
vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
if (!asma->file) {
char *name = ASHMEM_NAME_DEF;
struct file *vmfile;
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內核共享內存機制,它才會很小巧,它站在巨人的肩膀上了。
2. 匿名共享內存的框架
fspad-733/androidL/frameworks/base/core/java/android/os/MemoryFile.java在Java框架中匿名共享涉及到的類為MemoryFile類。
private static native FileDescriptor native_open(String name, int length) throws IOException;
// returns memory address for ashmem region
private static native long native_mmap(FileDescriptor fd, int length, int mode)
throws IOException;
private static native void native_munmap(long addr, int length) throws IOException;
private static native void native_close(FileDescriptor fd);
private static native int native_read(FileDescriptor fd, long address, byte[] buffer,
int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
private static native void native_write(FileDescriptor fd, long address, byte[] buffer,
int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
private static native int native_get_size(FileDescriptor fd) throws IOException;
public MemoryFile(String name, int length) throws IOException {
mLength = length;
if (length >= 0) {
mFD = native_open(name, length);
} else {
throw new IOException("Invalid length: " + length);
}
if (length > 0) {
mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
} else {
mAddress = 0;
}
}
當上層的應用程序實例化MemoryFile對象的時候,MemoryFile構造器就會執行,在構造方法中我們可以看到有native_open和native_mmap都是使用native聲明的。我們使用grep “native_open” * -nR來搜索它所對應的本地的框架。fspad-733/androidL/frameworks/base/core/jni/android_os_MemoryFile.cpp
static const JNINativeMethod methods[] = {
{"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open},
};
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);
}
上層的native_open向下層調用的時候,就會調到android_os_MemoryFile_open函數,在這個函數中我們能夠看到ashmem_create_region(namestr, length);它的實現在/system/core/libcutils/ashmem-dev.c目錄下
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] = {0};
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;
}
這個函數的實現也很簡單,首先在open(ASHMEM_DEVICE, O_RDWR);打開共享內存的驅動
#define ASHMEM_DEVICE "/dev/ashmem"
接著在ret = ioctl(fd, ASHMEM_SET_NAME, buf);函數中,調用ASHMEM_SET_NAME命令碼設置共享內存的名字,調用ioctl(fd, ASHMEM_SET_SIZE, size);函數分配共享內存的大小。這個函數的工作就完成了,是不是很簡單?native_open終得到的是打開驅動得到的文件描述符fd。
Native_mmap函數的實現過程如下:
native_mmap(mFD, length, PROT_READ | PROT_WRITE);
androidL/frameworks/base/core/jni/android_os_MemoryFile.cpp
static const JNINativeMethod methods[] = {
{"native_mmap", "(Ljava/io/FileDescriptor;II)J", (void*)android_os_MemoryFile_mmap},
};
static jlong android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
jint length, jint prot)
{
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
void* result = mmap(NULL, length, prot, MAP_SHARED, fd, 0);
if (result == MAP_FAILED) {
jniThrowException(env, "java/io/IOException", "mmap failed");
}
return reinterpret_cast<jlong>(result);
}
這個函數的實現過程也很簡單,首先通過jniGetFDFromFileDescriptor(env, fileDescriptor);函數獲取文件描述符fd,然后通過mmap函數進行內存的映射,result = mmap(NULL, length, prot, MAP_SHARED, fd, 0);終將映射的首地址通過return的方式返回給java代碼。這樣在java中就能夠拿到匿名共享內存的首地址了。當我們得到內存的首地址之后就可以對內存進行讀寫了。接下來我們分析匿名共享內存的讀寫操作。這里需要記住它不需要內存的拷貝。
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);
}
我們先來分析一下這個函數的參數。
Buffer:讀取到的內容會放大buffer緩沖區中
srcOffset:內存上的偏移
destOffset:緩沖區的偏移
count:要讀取內存字節的個數
它的參數我們已經分析完成,我們可以發現在這個函數中終會調用native_read函數來讀取數據,在分析這個本地函數之前我們需要知道readbytes方法我們一般不會直接調用,因為在這個文件中還有對這個函數的進一步封裝,封裝完之后我們的操作就更為簡單。我們看一下它的封裝。
private class MemoryInputStream extends InputStream {
private int mMark = 0;
private int mOffset = 0;
private byte[] mSingleByte;
@Override
public int available() throws IOException {
if (mOffset >= mLength) {
return 0;
}
return mLength - mOffset;
}
......
public int read() throws IOException {
if (mSingleByte == null) {
mSingleByte = new byte[1];
}
int result = read(mSingleByte, 0, 1);
return mSingleByte[0];
}
@Override
public int read(byte buffer[], int offset, int count) throws IOException {
int result = readBytes(buffer, mOffset, offset, count);
if (result > 0) {
mOffset += result;
}
return result;
}
}
從上述我們可以看到在MemoryInputStream類中它會繼承InputStream類,在這個類中有read的操作方法,其中由無參構造器和有三個參數的構造器,終都會調用readbytes方法。也就是說在Android的app應用程序中如果想使用匿名共享內存的讀寫只需要獲取一個輸入或者輸出流的對象調用里面的read或者write方法就行了。接下來我們看native_read函數的本地實現
base/core/jni/android_os_MemoryFile.cpp
static const JNINativeMethod methods[] = {
{"native_read", "(Ljava/io/FileDescriptor;J[BIIIZ)I", (void*)android_os_MemoryFile_read},
};
static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,
jobject fileDescriptor, jlong 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;
}
從上述的代碼我們能夠看到,它直接通過env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);這個方法將數據設置出去。寫函數的使用和這個讀是相同的,這里我們就不在查看了。