當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > 匿名共享內(nèi)存
課程目標(biāo):
1. 匿名共享內(nèi)存驅(qū)動ashmen.c
2. 匿名共享內(nèi)存的框架結(jié)構(gòu)
1. 匿名共享內(nèi)存的驅(qū)動
首先我們應(yīng)該找到匿名共享內(nèi)存驅(qū)動的目錄,/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函數(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)知識,slab分配內(nèi)存請參考linux內(nèi)存管理。我們可以看到這里的ashmem是采用的misc架構(gòu)的。它采用動態(tài)分配次設(shè)備號。這里我們重點看它的操作方法集。
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,
};
當(dāng)用戶層的open,read,ioctl函數(shù)別調(diào)用的時候,驅(qū)動中的相應(yīng)的函數(shù)就會執(zhí)行,所以接下來我們重點分析open,read,mmap,ioctl函數(shù)的執(zhí)行過程。首先我們從open函數(shù)開始看
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函數(shù)從注釋上我們就能夠看到,它是阻止在32位系統(tǒng)上打開一個過大的文件。注釋如下:
/*
* 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函數(shù)就是從剛才我們分配的緩沖區(qū)中分配一個結(jié)構(gòu)體空間的大小,接下來就是對這個結(jié)構(gòu)體信息的填充。這個結(jié)構(gòu)體的名字被分配為如下這個宏。
#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 */
};
分配的這個結(jié)構(gòu)體的成員信息如下: 域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_read函數(shù),我們會發(fā)現(xiàn)它在函數(shù)內(nèi)部調(diào)用自己,它的作用如注釋所說:
/*
* 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.
*/
只要程序開始運行那么它會一直運行下去(遞歸執(zhí)行),知道asma中的file結(jié)構(gòu)體被銷毀。它主要是保證file結(jié)構(gòu)體不能被修改的。
ashmem_ioctl函數(shù)
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函數(shù)中我們能清楚的看到,它里面有ASHMEM_SET_NAME,ASHMEM_GET_NAME,ASHMEM_SET_SIZE,ASHMEM_GET_SIZE等命令碼,這些命令碼就是來設(shè)置ashmem_area結(jié)構(gòu)體中的變量的。通過ASHMEM_PIN,ASHMEM_UNPIN這兩個命令碼實現(xiàn)對匿名共享內(nèi)存的鎖定和解鎖。
struct ashmem_pin {
__u32 offset; /* offset into region, in bytes, page-aligned */
__u32 len; /* length forward from offset, in bytes, page-aligned */
};
這個結(jié)構(gòu)體只有兩個域,分別表示要鎖定或者要解鎖的內(nèi)塊塊的起始大小以及大小。
在分析這兩個操作之前,我們先來看一下Ashmem驅(qū)動程序中的一個數(shù)據(jù)結(jié)構(gòu)struct ashmem_range,這個數(shù)據(jù)結(jié)構(gòu)就是用來表示某一塊被解鎖(unpinnd)的內(nèi)存:
/*
* 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表示這塊被解鎖的內(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列表中
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;
}
函數(shù)后根據(jù)當(dāng)前要執(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操作時,目標(biāo)對象必須是一塊當(dāng)前處于unpinned狀態(tài)的內(nèi)存塊。
首先看一下Ashmem驅(qū)動程序模塊初始化函數(shù)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;
}
這里通過調(diào)用register_shrinker函數(shù)向內(nèi)存管理系統(tǒng)注冊一個內(nèi)存回收算法函數(shù)。在Linux內(nèi)核中,當(dāng)系統(tǒng)內(nèi)存緊張時,內(nèi)存管理系統(tǒng)就會進行內(nèi)存回收算法,將一些近沒有用過的內(nèi)存換出物理內(nèi)存去,這樣可以增加物理內(nèi)存的供應(yīng)。因此,當(dāng)內(nèi)存管理系統(tǒng)進行內(nèi)存回收時,就會調(diào)用到這里的ashmem_shrink函數(shù),讓Ashmem驅(qū)動程序執(zhí)行內(nèi)存回收操作,涉及到內(nèi)存分配的內(nèi)容我暫時不需要關(guān)心太多,這里只需要知道怎么分配的就行了。
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;
}
這個函數(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)存機制,它才會很小巧,它站在巨人的肩膀上了。
2. 匿名共享內(nèi)存的框架
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;
}
}
當(dāng)上層的應(yīng)用程序?qū)嵗疢emoryFile對象的時候,MemoryFile構(gòu)造器就會執(zhí)行,在構(gòu)造方法中我們可以看到有native_open和native_mmap都是使用native聲明的。我們使用grep “native_open” * -nR來搜索它所對應(yīng)的本地的框架。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向下層調(diào)用的時候,就會調(diào)到android_os_MemoryFile_open函數(shù),在這個函數(shù)中我們能夠看到ashmem_create_region(namestr, length);它的實現(xiàn)在/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;
}
這個函數(shù)的實現(xiàn)也很簡單,首先在open(ASHMEM_DEVICE, O_RDWR);打開共享內(nèi)存的驅(qū)動
#define ASHMEM_DEVICE "/dev/ashmem"
接著在ret = ioctl(fd, ASHMEM_SET_NAME, buf);函數(shù)中,調(diào)用ASHMEM_SET_NAME命令碼設(shè)置共享內(nèi)存的名字,調(diào)用ioctl(fd, ASHMEM_SET_SIZE, size);函數(shù)分配共享內(nèi)存的大小。這個函數(shù)的工作就完成了,是不是很簡單?native_open終得到的是打開驅(qū)動得到的文件描述符fd。
Native_mmap函數(shù)的實現(xiàn)過程如下:
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);
}
這個函數(shù)的實現(xiàn)過程也很簡單,首先通過jniGetFDFromFileDescriptor(env, fileDescriptor);函數(shù)獲取文件描述符fd,然后通過mmap函數(shù)進行內(nèi)存的映射,result = mmap(NULL, length, prot, MAP_SHARED, fd, 0);終將映射的首地址通過return的方式返回給java代碼。這樣在java中就能夠拿到匿名共享內(nèi)存的首地址了。當(dāng)我們得到內(nèi)存的首地址之后就可以對內(nèi)存進行讀寫了。接下來我們分析匿名共享內(nèi)存的讀寫操作。這里需要記住它不需要內(nèi)存的拷貝。
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);
}
我們先來分析一下這個函數(shù)的參數(shù)。
Buffer:讀取到的內(nèi)容會放大buffer緩沖區(qū)中
srcOffset:內(nèi)存上的偏移
destOffset:緩沖區(qū)的偏移
count:要讀取內(nèi)存字節(jié)的個數(shù)
它的參數(shù)我們已經(jīng)分析完成,我們可以發(fā)現(xiàn)在這個函數(shù)中終會調(diào)用native_read函數(shù)來讀取數(shù)據(jù),在分析這個本地函數(shù)之前我們需要知道readbytes方法我們一般不會直接調(diào)用,因為在這個文件中還有對這個函數(shù)的進一步封裝,封裝完之后我們的操作就更為簡單。我們看一下它的封裝。
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的操作方法,其中由無參構(gòu)造器和有三個參數(shù)的構(gòu)造器,終都會調(diào)用readbytes方法。也就是說在Android的app應(yīng)用程序中如果想使用匿名共享內(nèi)存的讀寫只需要獲取一個輸入或者輸出流的對象調(diào)用里面的read或者write方法就行了。接下來我們看native_read函數(shù)的本地實現(xiàn)
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);這個方法將數(shù)據(jù)設(shè)置出去。寫函數(shù)的使用和這個讀是相同的,這里我們就不在查看了。