Android12 简单的共享内存驱动实现 参考Ashmem

Android12 共享内存驱动实现

SOC:RK3568

system:Android12

概述:

1. 概述
Ashmem(Anonymous Shared Memory,Android 匿名共享内存),它基于 mmap 系统调用,可以让不同进程将同一段物理内存映射到各自的虚拟地址中,从而实现内存共享。

它以驱动程序的形式在内核空间中实现,并通过文件描述符来传递共享内存的句柄。

工作上要使用Ashmem,但是对于C++来讲使用不方便,因为涉及到fd文件描述符传递 。于是想参考google 的Ashmem,设计一个简单的进程只通过操作文件即可获取共享的驱动

逻辑原理如下:

主要代码:

 //结构体MyAshmem_area 用来描述一块匿名共享内存

struct MyAshmem_area {
	char name[MYASHMEM_FULL_NAME_LEN];//匿名
	struct list_head unpinned_list;//暂时无用
	//每一块匿名共享内存都会在临时文件系统tmpfs中对应一个文件,也就是file
    struct file *file;
	size_t size;//文件的大小 size
	unsigned long prot_mask;//    prot_mask是访问保护位
};

struct MyAshmem_area *asma;

开机启动Myshmem驱动:

static int __init MyAshmem_init(void)
{
	int ret = -ENOMEM;
	 //创建一个使用slap缓存 用于分配 MyAshmem_area 的分配器
	MyAshmem_area_cachep = kmem_cache_create("ashmem_area_cache",
					       sizeof(struct MyAshmem_area),
					       0, 0, NULL);
	if (!MyAshmem_area_cachep) {
		pr_err("failed to create slab cache\n");
		goto out;
	}
    //注册匿名共享内存设备
	ret = misc_register(&MyAshmem_misc);
	if (ret) {
		pr_err("failed to register misc device!\n");
		goto out_free1;
	}
	pr_info("initialized\n");
	return 0;

out_free1:
	kmem_cache_destroy(MyAshmem_area_cachep);
out:
	return ret;
}
注册时调用
device_initcall(MyAshmem_init);

 匿名内存设备是一个misc设备类型,所以它使用一个miscdevice类型的结构体结构体MyAshmem_misc进行注册,定义如下:

static struct miscdevice MyAshmem_misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "MyAshmem",
	.fops = &MyAshmem_fops,
};

文件操作/dev/MyAshmem,MyAshmem_fops 表示其操作方法列表。:

static const struct file_operations MyAshmem_fops = {
	.owner = THIS_MODULE,
	.open = MyAshmem_open,//当应用open时调用
	.release = MyAshmem_release,
	.mmap = MyAshmem_mmap,当应用mmap 时调用
};

open具体实现方法 :

static int MyAshmem_open(struct inode *inode, struct file *file)
{
	int ret;
	//当open计数器大于1时返回
	if(op > 0){
		printk("already MyAshmem_open");
	    return 0;
	}
	printk("MyAshmem_open");
	asma = kmem_cache_zalloc(MyAshmem_area_cachep, GFP_KERNEL);
	if (!asma)
		return -ENOMEM;
    //初始化链表 ,暂时没完善
	INIT_LIST_HEAD(&asma->unpinned_list);

	memcpy(asma->name, MYASHMEM_NAME_PREFIX, MYASHMEM_NAME_PREFIX_LEN);

    //设置默认匿名
	strcpy(asma->name + MYASHMEM_NAME_PREFIX_LEN, "MyAshmemDrive");
    //设置默认长度
	asma->size = 4096;
    //设置默认保护位置
	asma->prot_mask = PROT_MASK;
	//open计数器加一
    op++;
	
	return 0;
}

mmap实现:

static int MyAshmem_mmap(struct file *file, struct vm_area_struct *vma)
{
	static struct file_operations vmfile_fops;
	int ret = 0;
	mutex_lock(&MyAshmem_mutex);
    printk("MyAshmem_mmap");
	/* user needs to SET_SIZE before mapping */
	if (!asma->size) {
		ret = -EINVAL;
		goto out;
	}

	/* requested mapping size larger than object size */
	//比较size 设置大小,否则直接失败

	if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {
		ret = -EINVAL;
		goto out;
	}

	/* requested protection bits must match our allowed protection mask */
	//检测需要映射的虚拟内存vma的保护权限是否超过了匿名共享内存的保护权限
     //比如vma除了允许读之外还允许写,但是asma只允许读,这就算超过了,会mmap失败,直接返回。
	if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
	    calc_vm_prot_bits(PROT_MASK, 0)) {
		ret = -EPERM;
		goto out;
	}
	vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
    //第一次mmap会创造一个临时文件用来映射共享内存,
	//第二次打开直接从这个映射文件上mmap
	if (!asma->file) {
		char *name = MYASHMEM_NAME_DEF;
		struct file *vmfile;
		struct inode *inode;

		if (asma->name[MYASHMEM_NAME_PREFIX_LEN] != '\0')
			name = asma->name;

		/* ... and allocate the backing shmem file */
		//在tmpfs中创建一个临时文件。
		vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
		if (IS_ERR(vmfile)) {
			ret = PTR_ERR(vmfile);
			goto out;
		}
		vmfile->f_mode |= FMODE_LSEEK;
		inode = file_inode(vmfile);
		lockdep_set_class(&inode->i_rwsem, &backing_shmem_inode_class);
		//记录临时文件
		asma->file = vmfile;
		/*
		 * override mmap operation of the vmfile so that it can't be
		 * remapped which would lead to creation of a new vma with no
		 * asma permission checks. Have to override get_unmapped_area
		 * as well to prevent VM_BUG_ON check for f_ops modification.
		 */
		if (!vmfile_fops.mmap) {
			vmfile_fops = *vmfile->f_op;
			vmfile_fops.mmap = MyAshmem_vmfile_mmap;
			vmfile_fops.get_unmapped_area =
					MyAshmem_vmfile_get_unmapped_area;
		}
		vmfile->f_op = &vmfile_fops;
	}
	get_file(asma->file);

	/*
	 * XXX - Reworked to use shmem_zero_setup() instead of
	 * shmem_set_file while we're in staging. -jstultz
	 */
	  //判断映射虚拟内存vma是否需要在不同进程间共享,
	if (vma->vm_flags & VM_SHARED) {
		ret = shmem_zero_setup(vma);
		if (ret) {
			fput(asma->file);
			goto out;
		}
	} else {
		vma_set_anonymous(vma);
	}

	if (vma->vm_file)
		fput(vma->vm_file);
	vma->vm_file = asma->file;

out:
	mutex_unlock(&MyAshmem_mutex);
	//返回地址
	return ret;
}

全部代码drivers\staging\android\MyAshmem.c:




#include <linux/init.h>
#include <linux/export.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/falloc.h>
#include <linux/miscdevice.h>
#include <linux/security.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/uaccess.h>
#include <linux/personality.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/shmem_fs.h>

#define PROT_MASK		(PROT_EXEC | PROT_READ | PROT_WRITE)

#define MYASHMEM_NAME_LEN		256
#define MYASHMEM_NAME_PREFIX "dev/MyAshmem/"
#define MYASHMEM_NAME_PREFIX_LEN (sizeof(MYASHMEM_NAME_PREFIX) - 1)
#define MYASHMEM_FULL_NAME_LEN (MYASHMEM_NAME_LEN + MYASHMEM_NAME_PREFIX_LEN)
#define MYASHMEM_NAME_DEF "/dev/MyAshmem"
static int op = 0;
struct MyAshmem_area {
	char name[MYASHMEM_FULL_NAME_LEN];
	struct list_head unpinned_list;
	struct file *file;
	size_t size;
	unsigned long prot_mask;
};
struct MyAshmem_area *asma;

static DEFINE_MUTEX(MyAshmem_mutex);
static struct kmem_cache *MyAshmem_area_cachep __read_mostly;



static int MyAshmem_open(struct inode *inode, struct file *file)
{
	int ret;
	//当open计数器大于1时返回
	if(op > 0){
		printk("already MyAshmem_open");
	    return 0;
	}
	printk("MyAshmem_open");
	asma = kmem_cache_zalloc(MyAshmem_area_cachep, GFP_KERNEL);
	if (!asma)
		return -ENOMEM;
    //初始化链表 ,暂时没完善
	INIT_LIST_HEAD(&asma->unpinned_list);

	memcpy(asma->name, MYASHMEM_NAME_PREFIX, MYASHMEM_NAME_PREFIX_LEN);

    //设置默认匿名
	strcpy(asma->name + MYASHMEM_NAME_PREFIX_LEN, "MyAshmemDrive");
    //设置默认长度
	asma->size = 4096;
    //设置默认保护位置
	asma->prot_mask = PROT_MASK;
	//open计数器加一
    op++;
	
	return 0;
}

static int MyAshmem_release(struct inode *ignored, struct file *file)
{
	if (asma->file)
		fput(asma->file);
	kmem_cache_free(MyAshmem_area_cachep, asma);
    op--;
	return 0;
}
static int MyAshmem_vmfile_mmap(struct file *file, struct vm_area_struct *vma)
{
	/* do not allow to mmap ashmem backing shmem file directly */
	return -EPERM;
}

static unsigned long
MyAshmem_vmfile_get_unmapped_area(struct file *file, unsigned long addr,
				unsigned long len, unsigned long pgoff,
				unsigned long flags)
{
	return current->mm->get_unmapped_area(file, addr, len, pgoff, flags);
}

static inline vm_flags_t calc_vm_may_flags(unsigned long prot)
{
	return _calc_vm_trans(prot, PROT_READ,  VM_MAYREAD) |
	       _calc_vm_trans(prot, PROT_WRITE, VM_MAYWRITE) |
	       _calc_vm_trans(prot, PROT_EXEC,  VM_MAYEXEC);
}

static struct lock_class_key backing_shmem_inode_class;
static int MyAshmem_mmap(struct file *file, struct vm_area_struct *vma)
{
	static struct file_operations vmfile_fops;
	int ret = 0;
	mutex_lock(&MyAshmem_mutex);
    printk("MyAshmem_mmap");
	/* user needs to SET_SIZE before mapping */
	if (!asma->size) {
		ret = -EINVAL;
		goto out;
	}

	/* requested mapping size larger than object size */
	//比较size 设置大小,否则直接失败

	if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {
		ret = -EINVAL;
		goto out;
	}

	/* requested protection bits must match our allowed protection mask */
	//检测需要映射的虚拟内存vma的保护权限是否超过了匿名共享内存的保护权限
     //比如vma除了允许读之外还允许写,但是asma只允许读,这就算超过了,会mmap失败,直接返回。
	if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
	    calc_vm_prot_bits(PROT_MASK, 0)) {
		ret = -EPERM;
		goto out;
	}
	vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
    //第一次mmap会创造一个临时文件用来映射共享内存,
	//第二次打开直接从这个映射文件上mmap
	if (!asma->file) {
		char *name = MYASHMEM_NAME_DEF;
		struct file *vmfile;
		struct inode *inode;

		if (asma->name[MYASHMEM_NAME_PREFIX_LEN] != '\0')
			name = asma->name;

		/* ... and allocate the backing shmem file */
		//在tmpfs中创建一个临时文件。
		vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);
		if (IS_ERR(vmfile)) {
			ret = PTR_ERR(vmfile);
			goto out;
		}
		vmfile->f_mode |= FMODE_LSEEK;
		inode = file_inode(vmfile);
		lockdep_set_class(&inode->i_rwsem, &backing_shmem_inode_class);
		//记录临时文件
		asma->file = vmfile;
		/*
		 * override mmap operation of the vmfile so that it can't be
		 * remapped which would lead to creation of a new vma with no
		 * asma permission checks. Have to override get_unmapped_area
		 * as well to prevent VM_BUG_ON check for f_ops modification.
		 */
		if (!vmfile_fops.mmap) {
			vmfile_fops = *vmfile->f_op;
			vmfile_fops.mmap = MyAshmem_vmfile_mmap;
			vmfile_fops.get_unmapped_area =
					MyAshmem_vmfile_get_unmapped_area;
		}
		vmfile->f_op = &vmfile_fops;
	}
	get_file(asma->file);

	/*
	 * XXX - Reworked to use shmem_zero_setup() instead of
	 * shmem_set_file while we're in staging. -jstultz
	 */
	  //判断映射虚拟内存vma是否需要在不同进程间共享,
	if (vma->vm_flags & VM_SHARED) {
		ret = shmem_zero_setup(vma);
		if (ret) {
			fput(asma->file);
			goto out;
		}
	} else {
		vma_set_anonymous(vma);
	}

	if (vma->vm_file)
		fput(vma->vm_file);
	vma->vm_file = asma->file;

out:
	mutex_unlock(&MyAshmem_mutex);
	//返回地址
	return ret;
}

static const struct file_operations MyAshmem_fops = {
	.owner = THIS_MODULE,
	.open = MyAshmem_open,
	.release = MyAshmem_release,
	.mmap = MyAshmem_mmap,
};

static struct miscdevice MyAshmem_misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "MyAshmem",
	.fops = &MyAshmem_fops,
};

static int __init MyAshmem_init(void)
{
	int ret = -ENOMEM;
	 //创建一个使用slap缓存 用于分配 MyAshmem_area 的分配器
	MyAshmem_area_cachep = kmem_cache_create("ashmem_area_cache",
					       sizeof(struct MyAshmem_area),
					       0, 0, NULL);
	if (!MyAshmem_area_cachep) {
		pr_err("failed to create slab cache\n");
		goto out;
	}
    //注册匿名共享内存设备
	ret = misc_register(&MyAshmem_misc);
	if (ret) {
		pr_err("failed to register misc device!\n");
		goto out_free1;
	}
	pr_info("initialized\n");
	return 0;

out_free1:
	kmem_cache_destroy(MyAshmem_area_cachep);
out:
	return ret;
}
device_initcall(MyAshmem_init);

drivers/staging/android/Makefile 

ccflags-y += -I$(src)                   # needed for trace events
  
obj-y                                   += ion/
obj-$(CONFIG_FIQ_DEBUGGER)              += fiq_debugger/

obj-$(CONFIG_ASHMEM)                    += ashmem.o
obj-$(CONFIG_ANDROID_VSOC)              += vsoc.o
+++obj-y                           += MyAshmem.o

进程write:

#include <stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<unistd.h>
#define ASHMEM_DEVICE  "/dev/MyAshmem"
int main(void){

   int fd = open(ASHMEM_DEVICE, O_RDWR);
   char *addr = (char*)mmap(NULL, 4096 , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
   int i =0;
   while(1){
      *addr = i++;
      printf("write:%d\n",*addr);
      sleep(1);
   }
   return 0;
}

进程read:

#include <stdio.h>
#include <stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<unistd.h>
#define ASHMEM_DEVICE  "/dev/MyAshmem"
int main(void){
   int fd2 = open(ASHMEM_DEVICE, O_RDWR);
   char *addr2 = (char*)mmap(NULL, 4096 , PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0);
   int i =0;
   while(1){
      printf("read:%d\n",*addr2);
      sleep(1);
   }
   return 0;
}

编译/mnt/h/android-ndk-r21e/android-ndk-r21e/install/bin/aarch64-linux-android-g++ write.cpp -o write.out

/mnt/h/android-ndk-r21e/android-ndk-r21e/install/bin/aarch64-linux-android-g++ read.cpp  -o read.out

结果:

BUG:1.目前只能使用一次,得重新开机才能使用

2.不支持多组,只支持一组应用使用 

下一章节改进,觉得有用喜欢的话就给个点赞+收藏

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Android C的共享内存是一种高效的IPC机制,它允许不同的进程共享内存区域,从而实现数据共享和数据传输。在Android系统中,使用共享内存有两种基本方法:POSIX共享内存Ashmem。 POSIX共享内存(shm_open系统调用)是基于文件的IPC机制,它可以在不同的进程间共享文件系统中的内存块。在使用该方法时,首先创建并打开一个共享内存对象以便其他进程能够在其中写入或读取数据。与普通文件不同的是,该对象可以被多个进程同时访问,从而实现内存共享和数据传输。 AshmemAndroid专有的共享内存机制,它通过匿名内存映射(mmap系统调用)来创建共享内存,使多个进程可以共享相同的内存区域。在使用Ashmem时,首先在一个进程中分配一块内存区域,并将其标记为共享内存。其他进程可以通过Binder接口来获取该内存区域所对应的Ashmem文件描述符,并进一步映射内存区域,以便共享数据。 正如所见,Android C的共享内存机制提供了一种高效的IPC方法,可以在不同的进程之间实现数据共享和数据传输。但是由于共享内存存在并发访问、内存泄露等问题,因此在应用中使用时需要格外小心。 ### 回答2: Android C共享内存是一种在Android系统中用于不同进程间共享数据的机制。在多进程应用程序中,进程之间共享数据允许各个进程共同访问数据,从而提高系统的整体性能。C共享内存实现了这种数据共享的方式,允许多个进程可以同步地访问相同的内存区域,从而实现数据共享。 C共享内存操作需要用到管道和信号量等Linux中的IPC技术。进程可以通过信号量来控制对共享内存区域的访问,从而实现数据同步。同时,通过管道机制,同步地向共享内存区域写入和读出数据。在Android开发中,通常会使用NDK库和底层C语言来实现共享内存操作,可以对共享内存区域进行读写操作和管理。 通常情况下,在Android的多进程应用程序中,可以使用C共享内存实现不同进程之间的数据共享,从而提高应用程序的整体性能和响应速度。C共享内存也可以被用于进程间的通信,例如在游戏和音视频应用程序中,可以使用共享内存实现不同进程的交互与协作。总的来说,Android C共享内存提供了一种能够优化应用程序性能和提高用户体验的底层机制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hmbbPdx_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值