匿名共享内存

课程目标:

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 = http://www.gunmi.cn/v/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);这个方法将数据设置出去。写函数的使用和这个读是相同的,这里我们就不在查看了。

戳原文,参加1024程序员节活动,享万元在线全套视频课程~

【近期文章推荐】

1.Linux入门到精通,初学者的电子书推荐

2.大妈为女儿征婚只要程序员:收入高性格沉稳 还顾家

3.HTML5就业培训3步走:零基础也能咸鱼翻身月薪8k

4.物联网工资一般多少 看后直接惊呆了

5.推荐10部有关自学PHP的电子图书

6.嵌入式9月高薪就业榜6翻天 月薪过万比率超过50%

7.1024程序员节:向改变世界的程序员致敬

回复“0-71”任一数字,即可查看往期文章精选哦

长按二维码3秒

与10万程序高手做朋友

每天干货享不停

(记得识别二维码哟) 

 匿名共享内存

或微信搜索华清远见,即可关注我们

免费讲座 | 干货分享 | 程序员生活 | 就业招聘

高端IT就业培训专家

m.embedu.org