1. 程式人生 > >Android ION記憶體分配

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 heaps實現:

  1. ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc_user()
  2. 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_datahandle來返回,但是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 deviceheap_mask引數和之前提到的ion_allocation_dataflags成員一樣的含義。
當在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

本節部分翻譯。

  • IONDMABUF都是通過傳遞一個匿名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的非授權訪問。

參考