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.不支持多组,只支持一组应用使用

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

相关推荐
人生苦短,菜的抠脚5 小时前
RK628 Linux 内核驱动开发指南
linux·驱动开发
路溪非溪7 小时前
Linux下wifi子系统的数据流
linux·arm开发·驱动开发
阿昭L10 小时前
NT驱动程序和WDM驱动程序
驱动开发·windows内核
Freak嵌入式11 小时前
效率升级!uPyPi 支持 GitHub URL 直传,驱动发布一步到位
驱动开发
weiyvyy1 天前
嵌入式硬件接口开发的流程
人工智能·驱动开发·单片机·嵌入式硬件·硬件架构·硬件工程
weiyvyy1 天前
嵌入式硬件接口开发的核心原则
驱动开发·单片机·嵌入式硬件·fpga开发·硬件架构·硬件工程
春日见1 天前
自动驾驶的四个演进阶段
开发语言·人工智能·驱动开发·matlab·docker·计算机外设
索西引擎1 天前
Spec-Driven Development(SDD,规格驱动开发)
驱动开发·ai coding
芯芯点灯2 天前
LIS2DW12驱动,功耗,数据可视化
驱动开发·单片机
帐篷Li2 天前
AI 编程时代的规范驱动开发:OpenSpec 实践指南
人工智能·驱动开发