The Android ION memory allocator


Linux next stage directory 에 Android kernel patch의 list 를 LWN 에서 review 했다. 이 staging directory에 driver 를 merge 하는데 그 중 PMEM 이라는 physical memory mapping feature가 있었다. 이 PMEM 은 잘 쓰이지 않고 각종 vendor 에서 PMEM-like 한 것을 새로 구현하여 사용하기 시작했다. 그래서 Android 진영에서 이런 fragmented memory manager 를 하나로 통합하고자 Android 4.0 ICS(Ice Cream Sandwich) 에서 ION memory manager 로 대체 하기로 결정했다. 각 vendor 들의 PMEM-like interface 는 대표적으로 NVIDIA Tegra 에는 "NVMAP", TI OMAP 에서는 "CMEM", Qualcomm MSM 에서는 "PMEM" 이라는 것을 사용했다. 근래에 이 3군데의 vendor들은 ION으로 교체를 했다.

이 Article은 ION을 살펴 보고, user-space, kernel-space 간의 interface 를 요약한다. ION은 memory pool manager가 되고 ION의 client 간의 buffer들을 공유할 수 있도록 한다. 원래 이 article 마지막에 the DMA buffer sharing framework from Linaro 와 ION을 비교한 내용이 있는데 이것은 skip 했다. 원본을 참조 하시길 바란다. 

ION heaps

ION 은 하나 이상의 memory pool을 관리한다. 이중 일부는 fragmentation을 방지하고 특별한(?) hardware의 요구사항에 맞춰 boot time에 미리 설정 해놓을 수도 있다. 예를 들면, GPU, display controller, camera 들이 있다. (이들은 대게 특별히 그 디바이스에 맞게 할당된 memory 영역이 있다. 그것을 ION을 통해서 관리 가능하다). 기본적으로 제공되는 heap type 이외에 특정 device를 위한 ion heap type을 설정해서 사용할 수 있는데 이런 경우 꼭 제공해야하는 callback 을 구현해줘야 한다. 그 callback 은 다음과 같다.

file location : drivers/gpu/ion/ion_priv.h


   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); 
   }; 


위의 callback 들을 간략 요약하면, allocate() 와 free()는 각각 ion_buffer object 를 heap으로 부터 할당 하고 해제한다.   phys()는 ion 에 할당된 buffer 의 physical address와 length를 반환한다. 하지만 연속적인 영역에서만 사용 가능하다. (ION_HEAP_TYPE_SYSTEM 의 경우는 불가). 만약 특정 heap type 이 물리적으로 연속적인 공간을 할당 해줄 수 없다면 이 interface는 제공할 필요 없다. 현재 phys()는 physical address 를 ion_phys_addr_t 로 제공이 되는데(이는 unsigned long의 typedef임) 향후 phys_addr_t 로 대체될 예정이다.(include/linux/typs.h). map_dma() 와 unmap_dma() callback 은 DMA를 위해 준비되는 buffer 를 만든다. map_kernel(), unmap_kernel() 은 physical memory 를 kernel virtual address로 map(or unmap) 한다. map_user()는 map_kernel() 과 다르게 user space에 map 한다. unmap_user() 는 없는데 이 mapping 이 user space 에서는 file descriptor 형태로 표현되기 때문이다. 그래서 user 에서는 이 file descritor를 closing 하면 자동으로 unmap 된다는 것이다. 


기본적인 ION driver 가 제공하는 3가지 heap type은,


  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_HEAP_TYPE_SYSTEM이 vmalloc interface 로 할당되는 것으로 나와 있으나 최근 code를 보면 alloc_page를 통해 할당되도록 수정 되어 있다. 


또한 개발자들은 새로운 ION heap type을 추가 할 수 있다. 예를 들면 NVIDIA 에서는 ION_HEAP_TYPE_IOMMU 라는 type을 추가 하여 사용한다. 


Using ION from user space

전형적으로, user space device 는 연속적인 media buffer 를 할당하기 위해 ION을 사용할 것이다. 예를 들면, camera library는  camera device에서 사용가능한 하나의 capture buffer 를 할당 할 것이다. 일단 이 buffer 에 video data 가 가득 차게 되면, library는 kernel을 통해 이 buffer를 JPEG encoder H/W 에 전달하여 처리하도록 한다. 


하나의 user space C/C++ program은 ION을 통해 memory 할당을 하기 위해서는 "/dev/ion"을 open하여 접근권한을 얻어야 한다.  user program에서 open("/dev/ion", O_RDONLY); 하게 되면 하나의 ION client를 표현하는 handle인 file descriptor 를 반환한다. open 할 때, O_RDONLY로 open 하더라도 쓰기 가능한 memory 를 얻을 수 있다. user program에서 buffer 를 할당 받기 위해서는 아래의 data struct 에서 handle을 제외하고 나머지는 채워줘야 한다. 



   struct ion_allocation_data {
        size_t len;
        size_t align;
        unsigned int flags;
        struct ion_handle *handle; 
   } 

handle 항목은 output parameter 가 되며, len, align, flags 는 input parameter 가 된다. 여기서 flags는 위에서 살펴 본 type의 mask 값들이(물론 추가적으로 들어간 type을 포함) 하나 혹은 하나 이상의 값으로 들어간다. 하나 이상으로 들어간 flag 중, booting 때 ion_device_add_heap() 을 통해 추가되었던 순서대로 먼저 할당이 된다. 기본 구현은 ION_HEAP_TYPE_CARVEOUT 은 ION_HEAP_TYPE_SYSTEM_CONTIG 전에 추가된다. ION_HEAP_TYPE_SYSTEM_CONTIG |  ION_HEAP_TYPE_CARVEOUT 로 flag를 지정 시, ION_HEAP_TYPE_SYSTEM_CONTIG 보다 ION_HEAP_TYPE_CARVEOUT에서 먼저 할당할 의도로 보인다. 


User-space client 는 ioctl system call로 ion 관련 control 한다. Buffer를 할당 하기 위해 아래와 같이 쓴다.


int ioctl(int client_fd, ION_IOC_ALLOC, struct ion_allocation_data *allocation_data) 


이 호출은, CPU 가 접근할 수 있는 buffer pointer 가 아닌 ion_handle로 전달된다.(ion_allocation_data struct 내부에 넣어져 전달됨) 그 handle은 단지 buffer sharing을 위한 file desciprtor를 얻어 사용되기 위함이다. 


 int ioctl(int client_fd, ION_IOC_SHARE, struct ion_fd_data *fd_data);


여기서 client_fd 는 /dev/ion 에 대응되는 file descriptor 이다. 그리고 fd_data structure는 handle(ion_handle) 을 input으로 받고 fd 를 output으로 준다. 


  struct ion_fd_data {
        struct ion_handle *handle;
        int fd;
   }


fd 는 sharing을 위해 전달된 file descriptor 이다. Android 에서는 Binder IPC 메커니즘을 이용해서 다른 process 와 공유하기 위해 fd 를 전달 하여 사용할 수 있다. 여기서 shared buffer를 얻으려면, second user process 에서 일단 open("/dev/ion", O_RDONLY) 를 통해 client를 얻어야 한다. ION은 process의 PID 를 갖고 user space의 client를 tracking 할 수 있다. 만약 같은 process 에서 open("/dev/ion", O_RDONLY)를 반복해서 호출하면 커널에 갖고 있는 같은 client struct 에 대응하는 다른 서로 다른 file descriptor 를 제공할 것이다. 


buffer 를 free하기 위해서는 second process 에서 munmap() 호출로 mmap() 을 되돌리는 것이 요구된다. 그리고 첫 ION_IOC_SHARE를 통해 file decriptor를 얻었던 process 에서 close를 해주어야 한다. 


 int ioctl(int client_fd, ION_IOC_FREE, struct ion_handle_data *handle_data);


여기서 ion_handle_data는 handle을 갖고 있어야 한다.


  struct ion_handle_data {
	     struct ion_handle *handle;
     }


ION_IOC_FREE command로 kernel에서 이 handle의 reference count 를 감소만 시킨다. 만약 이 reference count가 0이 되면 ion_handle object 를 free한다. 그리고 ION 을 reserve하고 있는 data structure를 업데이트 해준다.


이 일련의 과정을 예제 application으로 linaro git에 있는 것을 찾았다. 하지만 이것은 사용법에 준하는 소스 code이며 process 간 공유하는 예제는 아니라서 조금 아쉽다. 

참고 : http://git.linaro.org/gitweb?p=people/bgaignard/ion_test_application.git;a=commitdiff;h=424864b570e264f414145f987a48b23af5157813

일단은 처음 client를 만드는 process에서 어떻게 ion ioctl을 하는지 flow만큼은 볼 수 있을 것이다. 


Sharing ION buffers in the kernel


kernel 에서 ION 은 multiple client를 지원한다. Kernel driver 는 ION clien handle을 얻기 위해 아래의 함수를 호출한다.


  struct ion_client *ion_client_create(struct ion_device *dev, 
                   unsigned int heap_mask, const char *debug_name)


첫번째 argument "dev"는 /dev/ion 에 연결되어 있는 global ION device 이다. 왜 이 global device 가 필요하고 parameter로 넘겨줘야 하는지 명백하진 않다. 두 번째는 heap_mask 인데 ion_allocation_data를 사용하여 채워줬던 것 처럼 heap의 type을 하나 혹은 그 이상을 지정해서 넣어준다. (flags에 넣너준것이다.) 스마트 폰의 경우 multimedia middleware를 포함하여 사용되는 경우처럼 user process는 전형적으로 ION을 통해 buffer를 할당 하고 ION_IOC_SHARE로 file desciptor를 얻은 후 kernel로 file descriptor를 넘겨준다. 하지만 kernel은 ion_import_fd()함수를 이용해 file descriptro를 ion_handle object로 변경한다.


struct ion_handle *ion_import_fd(struct ion_client *client, int fd_from_user); 


ion_handle object 는 driver 의 shared buffer를 참조하는 client 이다. ion_import_fd() 호출은 이 parameter로 들어온 client가 기존에 이미 할당되고 다른 곳에서 사용되는지 확인한다. 확인하여 기존에 이미 사용되고 있는 handle이라면 단순히 reference count만 증가시킬 것이다. 


어떤 H/W는 물리적으로 연속적인 memory를 갖고(CARVEOUT type) 사용되어 질 수 있다. 그럴 경우에 ion_handle을 통해 physicall buffer 를 얻어올 수 있다. 


  int ion_phys(struct ion_client *client, struct ion_handle *handle,
	       ion_phys_addr_t *addr, size_t *len)


만약 물리적으로 연속적인 memory가 아니라면, 이 ion_phys() 호출은 실패 할 것이다.


client로 부터 hadling을 호출 할때, ION은 항상 input file descriptor, client 와 handle argument를 확인한다. 예를 들어 file descriptor를 import 할 때, ION은 ION_IOC_SHARE command에 의해 생성된 file descriptor인지 확인한다. ion_phys() 호출 때는 buffer handle이 갖고 있는 접근 가능한 client handle의 list에 들어있는지 확인하고 없다면 error를 return한다.


ION은 debugging을 위해 debugfs도 제공한다. debug 정보는 /sys/kernel/debug/ion에 있으며 이 정보는 연관된 heap 과 client 등이 있다. (PID 나 symbolic name으로 표시된다)


다음에는 driver와 user process 에서 ion buffer 를 sharing 하는 방법을 알아보도록 한다. 


+ Recent posts