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

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

相关推荐
cxr8281 天前
SPARC方法论在Claude Code基于规则驱动开发中的应用
人工智能·驱动开发·claude·智能体
sukalot2 天前
window显示驱动开发—显示适配器的子设备
驱动开发
Evan_ZGYF丶2 天前
【RK3576】【Android14】如何在Android14下单独编译kernel-6.1?
linux·驱动开发·android14·rk3576
sukalot3 天前
window显示驱动开发—视频呈现网络简介
驱动开发
sukalot3 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
驱动开发
zwhSunday3 天前
Linux驱动开发(1)概念、环境与代码框架
linux·运维·驱动开发
sukalot4 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot4 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr8285 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体
zwhSunday5 天前
Linux驱动开发(2)进一步理解驱动
linux·驱动开发