The Android ION memory allocator
- ION heaps
-
- ION設計的目标
- ION的實作
- user space ION API
- 在user space使用ION
-
- 使用場景
- 具體使用細節
- Demo
- 在kernel中share ION buffer
- kernel space ION API
-
- client申請與釋放
- buffer申請及釋放
- 位址映射
- 比較ION和DMABUF
- 參考
The Android ION memory allocator
英文原文
ION heaps
ION設計的目标
為了避免記憶體碎片化,或者為一些有着特殊記憶體需求的硬體,比如GPUs、display controller以及camera等,在系統啟動的時候,會為他們預留一些memory pools,這些memory pools就由ION來管理。通過ION就可以在硬體以及user space之間實作zero-copy的記憶體share。
ION的實作
ION通過ION heaps來展示presents它對應的memory pools。不同的Android硬體可能會要求不同的ION heaps實作,預設的ION驅動會提供如下三種不同的ION heaps實作:
- ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc_user()
- ION_HEAP_TYPE_SYSTEM_CONTIG: memory allocated via kzalloc
- ION_HEAP_TYPE_CARVEOUT: carveout memory is physically contiguous and set aside at boot.
開發者可以自己實作更多的ION heaps。比如NVIDIA就送出了一種ION_HEAP_TYPE_IOMMU的heap,這種heap帶有IOMMU功能。
不管哪一種ION heaps實作,他們都必須實作如下接口:
struct ion_heap_ops {
int (*allocate) (struct ion_heap *heap,
struct ion_buffer *buffer, unsigned long len,
unsigned long align, unsigned long flags);
void (*free) (struct ion_buffer *buffer);
int (*phys) (struct ion_heap *heap, struct ion_buffer *buffer,
ion_phys_addr_t *addr, size_t *len);
struct scatterlist *(*map_dma) (struct ion_heap *heap,
struct ion_buffer *buffer);
void (*unmap_dma) (struct ion_heap *heap,
struct ion_buffer *buffer);
void * (*map_kernel) (struct ion_heap *heap,
struct ion_buffer *buffer);
void (*unmap_kernel) (struct ion_heap *heap,
struct ion_buffer *buffer);
int (*map_user) (struct ion_heap *heap, struct ion_buffer *buffer,
struct vm_area_struct *vma);
};
簡單來說,接口的各個函數功能如下:
- allocate()和free()分别用來從heap中配置設定或者釋放一個ion_buffer對象對于實體連續的記憶體。
- phys()用來得到ion_buffer對象的實體記憶體位址及其大小。如果heap沒有提供實體連續的記憶體,那麼它也可以不用提供這個接口。其中,ion_phys_addr_t将來會被定義在/include/linux/types.h中的phys_addr_t替代。
- map_dma()和unmap_dma()分别來用使ion_buffer對象為DMA(Direct Memory Access,直接記憶體存取。顧名思義,不占用cpu資源,從一個硬體存儲區域把一部分連續的資料複制到另一個硬體存儲區域)做好準備或者取消做好準備
- map_kernel()和unmap_kernel()分别用來把physical memory映射(map)到核心虛拟位址空間(kernel virtual address space)或者取消映射
- map_user()用來把physical memory映射(map)到使用者記憶體空間(user space)。為什麼沒有對應的unmap_user()呢?因為,這個映射用一個file descriptor來表示,當這個file descriptor關閉的時候,這個映射關系就自動取消了。
user space ION API
定義了6種 ioctl 接口,可以與使用者應用程式互動。
- ION_IOC_ALLOC: 配置設定記憶體
- ION_IOC_FREE: 釋放記憶體
- ION_IOC_MAP: 擷取檔案描述符進行mmap
- ION_IOC_SHARE: 建立檔案描述符來實作共享記憶體
- ION_IOC_IMPORT: 擷取檔案描述符
- ION_IOC_CUSTOM: 調用使用者自定義的ioctl
ION_IOC_SHARE 及ION_IOC_IMPORT是基于DMABUF實作的,是以當共享程序擷取檔案描述符後,可以直接調用mmap來操作共享記憶體。mmap實作由DMABUF子系統調用ION子系統中mmap回調函數完成。
在user space使用ION
使用場景
典型的,在使用者空間使用的裝置通路庫(user space device access libraries)一般使用ION來配置設定大塊連續的media buffers。比如,still camera library配置設定一個capture buffer來供camera device使用。當這個buffer填滿video data的時候,這個library就能把這塊buffer傳遞給kernel,然後讓JPEG寫死子產品來處理。
具體使用細節
在user space 的C/C++程式能夠能夠配置設定ION記憶體之前,它必須獲得通路/dev/ion的權限。通過調用
open("/dev/ion", O_RDONLY)
就可獲得一個以handle形式傳回的file descriptor,這個file descriptor用來代表一個ION client。注意,雖然傳給open一個O_RDONLY參數,但是你仍然可對這塊memory進行寫操作。在一個user process中最多有一個client。當有了一個client之後,就可以開始配置設定ION記憶體。為了配置設定記憶體,client必須填滿下面的ion_allocation_data結構,handle除外,因為它是output參數。其他三個參數分别指明記憶體的大小、對齊方式以及flags。flags是一個bit mask,用來說明可以從哪些heaps中配置設定想要的記憶體。其決定順序由系統啟動時,通過ion_device_add_heap()添加的heap順來決定。比如,ION_HEAP_TYPE_CARVEOUT是在ION_HEAP_TYPE_CONTIG之前被add的,那麼如果flags = ION_HEAP_TYPE_CONTIG | ION_HEAP_TYPE_CARVEOUT,那麼就是先嘗試配置設定ION_HEAP_TYPE_CARVEOUT類型的heap,如果不行,再嘗試配置設定ION_HEAP_TYPE_CONTIG類型的heap。()
struct ion_allocation_data {
size_t len;
size_t align;
unsigned int flags;
struct ion_handle *handle;
}
user space通過ioctl()系統接口來與ION互動。在client填充ion_allocatoin_data結構之後,就可以通過調用
int ioctl(int client_fd, ION_IOC_ALLOC, struct ion_allocation_data *allocation_data)
來allocate a buffer。這個調用介紹之後,配置設定的buffer會通過ion_allocatoin_data的handle來傳回,但是CPU不可以通路這個buffer。這個handle隻可以通過調用int ioctl(int client_fd, ION_IOC_SHARE, struct ion_fd_data *fd_data);來獲得一個用來share的file descriptor。這裡,client_fd參數是前面通過open獲得的一個對應/dev/ion file descriptor,fd_data是如下的資料結構,其handle對應ion_allocation_data::handle,是input參數;fd則是output參數,可以用來share。
當一個user process中的client分享(share)了這個fd之後,在其他user process中(當然,也可share給建立這個fd的client自己),為了獲得這個shared buffer,先必須通過調用open("/dev/ion", O_RDONLY)獲得一個client。(注:ION通過線程的PID來track各個client, 尤其是process中的"group leader"線程的PID。在相同的process中重複調用open("/dev/ion", O_RDONLY)隻會獲得指向kernel同一個client的another file descriptor)。獲得client之後,然後再通過mmap()函數來把這個fd映射到address space of process(mmap函數參考1,參考2)。如果要釋放這個fd對應的buffer,在調用mmap()的process中,先要通過munmap()來取消mmap()的效果。然後在之前share這個fd的client中,需要通過int ioctl(int client_fd, ION_IOC_FREE, struct ion_handle_data *handle_data);來關閉這個fd對應的file descriptor。其中,ion_handle_data表示前面通過ION_IOC_ALLOC指令獲得的handle,其定義如下:
struct ion_handle_data {
struct ion_handle *handle;
}
這個ION_IOC_FREE指令會導緻對應的handle的計數減1。當handle計數為0的時候,其指向的ion_handle對象就會被銷毀,并且相關的ION bookkeeping資料結構也會更新。
Demo
在這個Demo中,fd在同一個client中被share使用:
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include "/home/developer/kernel3.4/goldfish/include/linux/ion.h"
void main()
{
struct ion_fd_data fd_data;
struct ion_allocation_data ionAllocData;
ionAllocData.len=0x1000;
ionAllocData.align = 0;
ionAllocData.flags = ION_HEAP_TYPE_SYSTEM;
int fd=open("/dev/ion",O_RDWR);
ioctl(fd,ION_IOC_ALLOC, &ionAllocData);
fd_data.handle = ionAllocData.handle;
ioctl(fd,ION_IOC_SHARE,&fd_data);
int *p = mmap(0,0x1000,PROT_READ|PROT_WRITE,MAP_SHARED,fd_data.fd,0);
p[0]=99;
perror("test");
printf("hello all %d\n",p[0]);
}
在kernel中share ION buffer
在kernel中支援multiple clients,每一個使用ION功能的driver都可以在kernel中對應一個client。一個kernel driver通過調用struct ion_client *ion_client_create(struct ion_device *dev, unsigned int heap_mask, const char *debug_name)來獲得一個ION client handle(注意,前面在user space中通過open("/dev/ion", O_RDONLY)傳回的client是int類型)。dev參數是一個和/dev/ion相關的global ION device,heap_mask參數和之前提到的ion_allocation_data的flags成員一樣的含義。
當在user space中通過ION_IOC_SHARE指令得到一個buffer的file descriptor并把它傳遞給kernel之後,kernel driver通過調用struct ion_handle *ion_import_fd(struct ion_client *client, int fd_from_user);來把這個fd變成一個ion_handle對象,這個對象就是這個driver中對相應的buffer一個client-local reference。ion_import_fd方法會根據這個buffer的實體位址來查找:在本client中是否已經obtained一個對應此buffer的ion_handle,如果是的話,那麼就可以簡單的增加這個ion_handle的引用計數即可。
有些硬體隻能通過physical addresses來操作physically-contiguous buffers,那麼,這些對應的drivers就需要通過調用int ion_phys(struct ion_client *client, struct ion_handle *handle, ion_phys_addr_t *addr, size_t *len)來把ion_handle轉變成一個physical buffer。當然,如果這個buffer不是physically contiguous,那麼這個調用就會失敗。
當處理一個來自client的調用時,ION會validates 輸入的 file descriptor, client and handle arguments。比如ION會確定 file descriptor是由ION_IOC_SHARE指令建立的;比如當ion_phys()調用時,ION會檢測這個buffer是否在這個client對應有通路權限list中,如果不是,那麼就會傳回錯誤。這樣的驗證機制能夠減少可能的unwanted accesses以及疏忽的記憶體洩露。
ION通過debugfs提供可視化的debug,它通過在/sys/kernel/debug/ion下面,使用stored files來記錄相應的heaps和clients,并使用symbolic names或者PIDs來标志。
kernel space ION API
核心驅動也可以注冊為一個ION的用戶端(client),可以選擇使用哪種類型的heap來申請記憶體。
client申請與釋放
- ion_client_create: 配置設定一個用戶端。
- ion_client_destroy: 釋放一個用戶端及綁定在它上面的所有ion handle.
ion handle: 這裡每個ion handle映射到一個buffer中,每個buffer關聯一個heap。也就是說一個用戶端可以操作多塊buffer。
buffer申請及釋放
- ion_alloc: 申請ion記憶體,傳回ion handle
- ion_free: 釋放ion handle
位址映射
ION 通過handle來管理buffer,驅動需要可以通路到buffer的位址。ION通過下面的函數來達到這個目的
- ion_phys: 傳回buffer的實體位址(address)及大小(size)
- ion_map_kernel: 給指定的buffer建立核心記憶體映射
- ion_unmap_kernel: 銷毀指定buffer的核心記憶體映射
- ion_map_dma: 為指定buffer建立dma 映射,傳回sglist(scatter/gather list)
- ion_unmap_dma: 銷毀指定buffer的dma映射
ION是通過handle而非buffer位址來實作驅動間共享記憶體,使用者空間共享記憶體也是利用同樣原理。
- ion_share: given a handle, obtain a buffer to pass to other clients
- ion_import: given an buffer in another client, import it
- ion_import_fd: given an fd obtained via ION_IOC_SHARE ioctl, import it
ION_IOC_SHARE 及ION_IOC_IMPORT是基于DMABUF實作的,是以當共享程序擷取檔案描述符後,可以直接調用mmap來操作共享記憶體。mmap實作由DMABUF子系統調用ION子系統中mmap回調函數完成。
比較ION和DMABUF
本節部分翻譯。
- ION和DMABUF都是通過傳遞一個匿名file descriptor對象,給其他client一個基于引用計數的通路權限,進而達到分享記憶體的目的。
- ION通過一個可分享和追蹤的方式從預留的memory pool中配置設定記憶體。
- DMABUF更多的專注于buffer導入、導出以及同步的方式來實作在NON-ARM架構上的buffer的分享。
- ION目前隻支援Android kernel
- ION所有的user-space program都可以通過/dev/ion接口來配置設定ION記憶體。但是在Android會通過驗證user和group IDs的方式來阻止對ION的非授權通路。
參考
https://www.cnblogs.com/willhua/p/10029280.html
https://blog.csdn.net/armwind/article/details/53454251
https://blog.csdn.net/av_geek/article/details/47664505