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

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

相关推荐
7yewh35 分钟前
嵌入式硬件实战提升篇(一)-泰山派RK3566制作多功能小手机
linux·arm开发·驱动开发·嵌入式硬件·物联网·智能手机·硬件架构
7yewh1 天前
嵌入式硬件杂谈(一)-推挽 开漏 高阻态 上拉电阻
驱动开发·stm32·嵌入式硬件·mcu·物联网·硬件架构·pcb工艺
TeYiToKu2 天前
笔记整理—linux驱动开发部分(8)framebuffer类设备
linux·驱动开发·笔记·嵌入式硬件·arm
三菱-Liu3 天前
三菱MR-J4-B伺服连接器和信号排列
网络·驱动开发·硬件工程·制造·mr
挨踢小明3 天前
arm64架构的linux 配置vm_page_prot方式
驱动开发
lishing63 天前
Linux驱动开发(4):Linux的设备模型
linux·运维·驱动开发
TeYiToKu4 天前
笔记整理—linux驱动开发部分(10)input子系统与相关框架
linux·驱动开发·笔记·嵌入式硬件·arm·1024程序员节
TMC_LAI4 天前
采用 EtherCAT 的磁场定向控制 (FOC) 伺服运动控制器 IC-TMC8670-BI
人工智能·驱动开发·嵌入式硬件
三菱-Liu5 天前
三菱MR-J4伺服绝对位置检测系统
运维·驱动开发·嵌入式硬件·硬件工程·制造·mr
这个菠萝好咸5 天前
内核源码+vscode+bear+clang实现函数任意跳转,无缝跳转,无缝阅读,无缝开发
linux·c语言·驱动开发·后端·arm单片机