Android ION記憶體分配
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_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_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
使用場景
典型的,在使用者空間使用的裝置訪問庫(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來標誌。
比較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的非授權訪問。