最近在最佳化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裝置,是否有記憶體 Clerk,有的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無效。