在android系统需要使用到pmem物理空间及ashmem匿名空间,其实pmem就是一块连续的物理内存空间,可以建立一个设备结点给用户空间进行访问。
1、内核驱动编写:
struct android_pmem_platform_data
{
const char* name;
unsigned long start;
unsigned long size;
unsigned no_allocator;
unsigned cached;
unsigned buffered;
};
/* Structure that declares the usual file access functions */
struct file_operations memory_fops = {
open: memory_open,
mmap: memory_mmap
};
/* alloc pmem space */
struct android_pmem_platform_data android_pmem_data;
/* Set up a pmem area to match the frame buffer */
pmem_size = (pmemsize+PAGE_SIZE-1)/PAGE_SIZE;
FBPart = bpa2_find_part ("bigphysarea");
android_pmem_device.name = "android_pmem";
android_pmem_device.id = 0;
android_pmem_device.dev.platform_data = &android_pmem_data;
memset(&i->android_pmem_data, 0, sizeof(android_pmem_data));
android_pmem_data.name = kstrdup(pname, GFP_KERNEL);
android_pmem_data.start = bpa2_alloc_pages (FBPart, pmem_size, 0, GFP_KERNEL);
android_pmem_data.size = i->pmem_size * PAGE_SIZE;
android_pmem_data.no_allocator = 1;
android_pmem_data.cached = 0;
android_pmem_data.buffered = 0;
platform_device_register(&android_pmem_device);
/* Registering device */
register_chrdev(memory_major, NAME_DEVICE , &memory_fops);
实现两个重要的ioctl函数:
int memory_open(struct inode *inode, struct file *filp) {
/* Success */
return 0;
}
#define io_remap_page_range(vma, vaddr, pfn, size, prot) io_remap_pfn_range(vma, vaddr, (pfn) >> PAGE_SHIFT, size, prot)
int memory_mmap(struct file *filp, struct vm_area_struct *vma) { int ret = 0; pgprot_t pgprot; unsigned long offset; /* To avoid warnings */ filp = 0; offset = vma->vm_pgoff << PAGE_SHIFT; pgprot = pgprot_noncached(vma->vm_page_prot); if ((ret=io_remap_pfn_range(vma, vma->vm_start, offset, vma->vm_end - vma->vm_start, pgprot)) != 0) { printk("remap_pfn_range error: (0x%.8x - %d)", ret, ret); return -EAGAIN; } return ret; }
简要说明一下:
在mmap函数里,使用remap_pfn_range函数。代码如下。注意要设置一下vma->vm_pgoff为你要map的io空间的物理地址对应的页。arm IO/ 内存统一编址 所以#define io_remap_pfn_range(vma,from,pfn,size,prot) / remap_pfn_range(vma, from, pfn, size, prot)vm_start, vm_end 在系统调用时算好了,使用时找个空闲的空间,然后根据size算出vm_endmap过程:
用户空间map(start,offset, len , fd ,...) start 映射到的用户空间地址,0就是自己去找,offset 文件的偏移。
static inline unsigned long do_mmap2(unsigned long addr, size_t len, unsigned long prot, unsigned long flags, unsigned long fd, unsigned long off, int shift) { unsigned long ret = -EINVAL; if (!arch_validate_prot(prot)) goto out; if (shift) { if (off & ((1 << shift) - 1)) goto out; off >>= shift; } ret = sys_mmap_pgoff(addr, len, prot, flags, fd, off); out: return ret; } unsigned long sys_mmap(unsigned long addr, size_t len, unsigned long prot, unsigned long flags, unsigned long fd, off_t offset) { return do_mmap2(addr, len, prot, flags, fd, offset, PAGE_SHIFT); }
可能合并VMA,否则新分配一个VMA
然后error = file->f_op->mmap(file, vma);
就调用了驱动里的mmap
这时候vma->vm_pgoff = pgoff (这个pgoff就是map(offset, len , fd ,...) 中的 offset ,当然经过处理了,PAGE_SHIFT移来移去)
2、用户空间用法:
int master_fd = open("/dev/pmem", O_RDWR, 0); if (master_fd >= 0) { ioctl(master_fd, PMEM_GET_TOTAL_SIZE, ®ion) void* base = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, master_fd, 0); ... // 访问base地址即可读写数据 }
如此即可将内核空间映射到用户进程空间