最近在优化mx51项目中内存的使用,512MB RAM的划分为:
64MB pmem_adsp
64MB pmem gpu
96MB DMA zone
96MB GPU memory
这样只剩下192M给kernel 的Normal zone,由于普通的alloc_pages无法使用DMA zone的空间,内存相当紧张,想从pmem中释放一些内存出来,因此花了点时间了解pmem.
pmem是android为DSP vpu gpu等设备提供的一种内存分配机制,我们都知道vpu gpu这一类设备需要大块的连续物理内存以便进行硬件解码,硬件显示加速。PMEM就像一个小型的buddy内存管理系统,独立于linux kernel内存管理模块管理,不会受到内存管理中的外碎片的影响,同时还可以灵活的提供额外功能。
当然在系统运行一段时间后,PMEM也同样面临着外碎片问题,因此PMEM内存区的使用者尽量分配大块的内存,而不是零星的小内存。
pmem内存区使用情况:
PMEM_DEBUG控制pmem驱动是否提供debugfs接口
进入开发板后,可以通过如下命令安装debugfs文件系统
# mount -t debugfs none /sys/kernel/debug
# ls /sys/kernel/debug/
asoc
binder
hid
pmem_gpu
pmem_adsp
bluetooth
mmc3
mmc2
mmc1
mmc0
usb
gpio
bdi
其中pmem_gpu是为GPU显示加速提供的内存分配区,pmem_adsp是为vpu解码提供的内存非配区
可以使用cat命令查看pmem的使用情况:
# cat /sys/kernel/debug/pmem_gpu
# cat /sys/kernel/debug/pmem_adsp
应用层使用方法
1. 分配的内存只在一个进程中使用
pmem_fd = open("/dev/pmem_gpu", O_RDWR, 0);
pmem_base = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, pmem_fd, 0);
2. 不同进程间共享。这利用了PMEM 驱动的Connect功能
进程1:
pmem_fd0 = open("/dev/pmem_gpu", O_RDWR, 0);
pmem_base = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, pmem_fd0, 0);
进程2:
pmem_fd1 = open("/dev/pmem_gpu", O_RDWR, 0);
ret = ioctl(pmem_fd1, PMEM_CONNECT, pmem_fd0);
ret = ioctl(pmem_fd1, PMEM_MAP, ®ion1);
一个进程打开PMEM设备,通过mmap操作映射内存到进程空间,该进程称为PMEM的master进程。其他进程可以再次打开该设备,然后通过PMEM_CONNECT操作,pmem_fd1就和pmem_fd0获得了相同的Pmem空间,这样该进程就称为PMEM的client进程。
Kernel空间PMEM driver分析
数据结构
图是网上偷来的
pmem[0]和pmem[1]
是pmem_info结构,每一个PMEM设备都有一个pmem_info结构
struct pmem_info {
struct miscdevice dev;
unsigned long base;
unsigned char __iomem *vbase;
unsigned long size;
unsigned long num_entries;
unsigned long garbage_pfn;
int garbage_index;
struct pmem_bits *bitmap;
unsigned no_allocator;
unsigned cached;
unsigned buffered;
unsigned allocated;
struct semaphore data_list_sem;
struct list_head data_list;
struct rw_semaphore bitmap_sem;
long (*ioctl)(struct file *, unsigned int, unsigned long);
int (*release)(struct inode *, struct file *);
};
@dev: PMEM设备被注册在misc设备下
@base: 是这个PMEM设备对应的物理内存地址
@vbase: 是物理内存remap后得到的内核虚拟地址
@size: pmem设备的总容量,从驱动本身来说并不要求2次幂大小,但是imx51强制为2次幂,否则android无法启动
@num_entries: pmem最小分配单位数目,最小分配单位大小至少要为page的2次幂倍
@bitmap: 每个pmem设备把该设备的mem空间划分为PMEM_MIN_ALLOC大小的分配单元,每个分配单元都对应着一个pmem_bits结构,这个pmem_bits用来描述分配单元的大小和使用情况。
@no_allocator: 这个pmem设备,是否有内存分配器,有的pmem设备只被一个进程使用一次,这种情况下,是不需要分配器的,整个pmem设备的内存一次性的全部分配给第一个进行映射的进程。
@cached:所谓cached,就是pmem内存是否使能cpu 缓冲。如果需要混合使用cached和uncached内存,那么可以设置这个标志,并且使用O_SYNC标志打开设备文件来获取uncached区域。
@allocaed:仅当no_allocator设置时有效,用来表示整个pmem空间是否被分配。
@data_list:系统的所有pmem 设备都通过data_list连接到一起。
PMEM设备接口
内核为每一个PMEM创建一个misc 设备节点,应用层通过这个设备节点的file_operations操作PMEM设备。
内存分配:
PMEM内存是供应用空间程序使用的,kernel和驱动并不会使用PMEM。应用程序首先打开相应PMEM设备节点,使用mmap系统调用或者PMEM_MAP PMEM_ALLOCATE ioctl申请pmem内存
内存释放:
应用程序可以显示的调用PMEM_UNMAP来释放分配的pmem内存,文件release操作会释放这个PMEM设备所有分配的内存。因此只要close了文件,那么就保证所有分配的内存都被回收,防止由于用户疏忽或者程序异常造成的内存泄漏。
获取物理内存地址
sometimes,应用空间需要获取物理地址
ioctl PMEM_GET_PHYS 获取pmem内存物理地址,我们知道pmem驱动维护着多个pmem设备,并且每个pmem设备还可能进行多次分配。那么这里是如何确定请求哪个pmem设备,哪一次分配的物理地址呢?
通过设备节点的从设备号,就可以确定这次请求的pmem设备;此外在file结构的private_data是一个pmem_data结构,pmem_data->index指向了最后一次分配的索引位置。因此PMEM_GET_PHYS获取的是这个pmem设备最后一次分配内存的起始物理地址。
PMEM_GET_SIZE, PMEM_GET_TOTAL_SIZE
PMEM_GET_SIZE用来获取给定PMEM设备最后一次分配的size;而PMEM_GET_TOTAL_SIZE则获取给定PMEM的整个空间size
PMEM_CACHE_FLUSH
刷新给定内存区的cache,dmac_flush_range包括writeback和invalidate操作,使得写cache写回,读cache无效。