2012年8月14日 星期二

探討 Android gralloc HAL – part2

gralloc HAL有幾個比較重要的函式, 分別是 gralloc_alloc, gralloc_alloc_buffer,  init_pmem_area,  gralloc_free, 另外, 還有一個class : SimpleBestFitAllocator.
這些函式的程式代碼在


  /hardware/libhardware/modules/gralloc/Gralloc.cpp
  /hardware/libhardware/modules/gralloc/Gralloc_priv.cpp
  /hardware/libhardware/include/hardware/Gralloc.h
class SimpleBestFitAllocator 的程式碼在
  /hardware/libhardware/modules/gralloc/Allocator.cpp
  /hardware/libhardware/modules/gralloc/Allocator.h

gralloc_alloc() 函式

Source3.cpp
01 static int 
02 gralloc_alloc(alloc_device_t* dev, int w, int h, int format, int usage,
03               buffer_handle_t* pHandle, int* pStride)
04 {
05     if (!pHandle || !pStride)
06         return -EINVAL;
07  
08     size_t size, stride;
09  
10     int align = 4;
11     int bpp = 0;
12     switch (format) {
13         case HAL_PIXEL_FORMAT_RGBA_8888:
14         case HAL_PIXEL_FORMAT_RGBX_8888:
15         case HAL_PIXEL_FORMAT_BGRA_8888:
16             bpp = 4;
17             break;
18         case HAL_PIXEL_FORMAT_RGB_888:
19             bpp = 3;
20             break;
21         case HAL_PIXEL_FORMAT_RGB_565:
22         case HAL_PIXEL_FORMAT_RGBA_5551:
23         case HAL_PIXEL_FORMAT_RGBA_4444:
24             bpp = 2;
25             break;
26         default:
27             return -EINVAL;
28     }
29     size_t bpr = (w*bpp + (align-1)) & ~(align-1);
30     size = bpr * h;
31     stride = bpr / bpp;
32  
33     int err;
34  
35   private_module_t* m = reinterpret_cast<private_module_t*>(
36                 dev->common.module);
37  
38   // 
39   //
40   //
41   //
42  
43     if (usage & GRALLOC_USAGE_HW_FB) {
44         err = gralloc_alloc_framebuffer(dev, size, usage, pHandle);
45     }
46      else {
47           g_w = w;  g_h = h; g_fmt = format;
48         err = gralloc_alloc_buffer(dev, size, usage, pHandle);
49     }
50     if (err < 0) {
51         return err;
52     }
53     *pStride = stride;
54     return 0;
55 }

gralloc_alloc() 函式 使用 color format (format參數) 決定 byte per pixel(bpp), 以及 byte per row(bpr), 使用的是 4 bytes alignment. 並且根據 傳入的高度 h, 決定 buffer size.
Android 定義buffer usage 的種類如下:
/* buffer will be used as an OpenGL ES texture */
GRALLOC_USAGE_HW_TEXTURE      = 0x00000100,
/* buffer will be used as an OpenGL ES render target */
GRALLOC_USAGE_HW_RENDER        = 0x00000200,
/* buffer will be used by the 2D hardware blitter */
GRALLOC_USAGE_HW_2D                  = 0x00000C00,
/* buffer will be used with the framebuffer device */
GRALLOC_USAGE_HW_FB                  = 0x00001000,
/* mask for the software usage bit-mask */
GRALLOC_USAGE_HW_MASK           = 0x00001F00,

gralloc_alloc() 函式中, 除了 usage==GRALLOC_USAGE_HW_FB 時是調用gralloc_alloc_framebuffer()函式之外, 其餘 usage 的種類都會調用gralloc_alloc_buffer()函式. gralloc_alloc_framebuffer() 會調用到mapFrameBufferLocked(), 這個函式在 part1 的時候介紹過了, 現在來看 gralloc_alloc_buffer(). 完整的程式碼如下
Source3.cpp
01 static int 
02 gralloc_alloc_buffer(alloc_device_t* dev,
03                      size_t size, int usage, buffer_handle_t* pHandle )
04 {
05     int err = 0;
06     int flags = 0;
07 
08     int fd = -1;
09     void* base = 0;
10     unsigned long phys_base = 0 ;
11     int offset = 0;
12     LOGD_IF(my_debug,"----->> %s , size = %d , usage = 0x%x\n",
13              __FUNCTION__,size,usage);
14     size = roundUpToPageSize(size);
15    
16 #if HAVE_ANDROID_OS // should probably define HAVE_PMEM somewhere
17 
18     if (usage & GRALLOC_USAGE_HW_TEXTURE) {
19         flags |= private_handle_t::PRIV_FLAGS_USES_PMEM;
20     }
21    
22     if (usage & GRALLOC_USAGE_HW_2D) {
23         flags |= private_handle_t::PRIV_FLAGS_USES_PMEM;
24     }
25 
26     if ((flags & private_handle_t::PRIV_FLAGS_USES_PMEM) == 0) {
27 try_ashmem:
28         fd = ashmem_create_region("gralloc-buffer", size);
29         if (fd < 0) {
30             LOGE("couldn't create ashmem (%s)", strerror(-errno));
31             err = -errno;
32         }
33     } else {
34         private_module_t* m = reinterpret_cast<private_module_t*>(
35                 dev->common.module);
36 
37         err = init_pmem_area(m);
38         if (err == 0) {
39           // PMEM buffers are always mmapped
40           base = m->pmem_master_base;
41           // New added
42           phys_base = m->pmem_master_physbase ;
43 
44           LOGI("ERR0 : sAllocator.allocate : size=%d\n", size);
45           offset = sAllocator.allocate(size);
46           if (offset < 0) {
47             // no more pmem memory
48             LOGW("ERR01 : OUT OF MEMORY : sAllocator.allocate : size=%d\n", size);
49               err = -ENOMEM;
50           } else {
51                 struct pmem_region sub = { offset, size };
52                
53                 // now create the "sub-heap"
54                 fd = open("/dev/pmem", O_RDWR, 0);
55                 err = fd < 0 ? fd : 0;
56                
57                 // and connect to it
58                 if (err == 0)
59                     err = ioctl(fd, PMEM_CONNECT, m->pmem_master);
60 
61                 // and make it available to the client process
62                 if (err == 0)
63                     err = ioctl(fd, PMEM_MAP, &sub);
64 
65                 if (err < 0) {
66                     err = -errno;
67                     close(fd);
68                     sAllocator.deallocate(offset);
69                     fd = -1;
70                 }
71                 memset((char*)base + offset, 0, size);
72             }
73         } else {
74             if ((usage & GRALLOC_USAGE_HW_2D) == 0) {
75                // the caller didn't request PMEM, so we can try something else
76                 flags &= ~private_handle_t::PRIV_FLAGS_USES_PMEM;
77                 err = 0;
78                 goto try_ashmem;
79             } else {
80                 LOGE("couldn't open pmem (%s)", strerror(-errno));
81             }
82         }
83     }
84 
85 #else // HAVE_ANDROID_OS
86    
87     fd = ashmem_create_region("Buffer", size);
88     if (fd < 0) {
89         LOGE("couldn't create ashmem (%s)", strerror(-errno));
90         err = -errno;
91     }
92 
93 #endif // HAVE_ANDROID_OS
94 
95     if (err == 0) {
96         private_handle_t* hnd = new private_handle_t(fd, size, flags);
97         if (base == 0) {
98             gralloc_module_t* module = reinterpret_cast<gralloc_module_t*>(dev->common.module);
99             err = mapBuffer(module, hnd);
100             if (err == 0) {
101                     *pHandle = hnd;
102             }
103     } else {
104       hnd->offset = offset;
105       hnd->base = int(base)+offset;
106       phys_base += offset ;
107       hnd->sign_bit = 0x80000000 & phys_base ;
108       hnd->phys_base = 0x7FFFFFFF & phys_base ;
109       LOGD_IF(my_debug,"->> PMEM phys : 0x%x, fd=%d , virt=0x%x, pid=%d\n",
110     hnd->phys_base,hnd->fd,hnd->base,hnd->pid) ;
111       *pHandle = hnd;
112     }
113     }
114      return err;
115 }

18~34行
如果 usage 的設定有包含 GRALLOC_USAGE_HW_TEXTURE 或是 GRALLOC_USAGE_HW_2D則會使用 PMEM, 否則使用 ashmem.
PMEMAshmem都通過mmap實現共用,區別是PMEM的共用區域是一段連續的實體記憶體,GPU 或是 DSP使用的記憶體必須是連續的實體記憶體, 另外, PMEM不受 linux MM 的管理而是由 Android 提供了一個簡單的管理機制, 這一點後面會說明. Ashmem則在虛擬位址空間是連續的,但實體記憶體卻不需要連續. Ashmem 的管理由 linux MM 管理機制負責.
回到 gralloc_alloc_buffer()函式, page alignment計算 buffer size , 第一個重要的函式是 init_pmem_area(), 以下是 init_pmem_area() 函式的程式碼.
Source3.cpp
01 static int init_pmem_area(private_module_t* m)
02 {
03     pthread_mutex_lock(&m->lock);
04     int err = m->pmem_master;
05     if (err == -1) {
06         // first time, try to initialize pmem
07         err = init_pmem_area_locked(m);
08         if (err) {
09             m->pmem_master = err;
10         }
11     } else if (err < 0) {
12         // pmem couldn't be initialized, never use it
13     } else {
14         // pmem OK
15         err = 0;
16     }
17     pthread_mutex_unlock(&m->lock);
18     return err;
19 }

第 4 行, m->pmem_master 中會紀錄是否第一次執行, 若是第一次執行則調用init_pmem_area_locked() 函式.此函式會 open PMEME device, query PMEM total size, 並取得 virtual address physical address. 另外一個關鍵就是調用 SimpleBestFitAllocator::setSize(), 調用此函式效用等同初始化SimpleBestFitAllocator 物件, 這部分請參考 /hardware/libhardware/modules/gralloc/Allocator.cpp.

至於 gralloc_alloc() 又是被哪個模組在什麼樣的狀況下調用的呢? 這個問題和 Android surfaceflinger 的運作有關係, 比較複雜, 需要另闢文章說明. 這裡先簡單點到即可 .Android surfaceflinger 負責管理Android 顯示系統, 包括 Layer buffer 更新的同步控制. 計算layer 之間的 Z-order, 裁剪(crop), 底層 HAL 的初始化, 以及更新 framebuffer 等等. Surfaceflinger 採用的是 client-server 的架構. Surfaceflinger 需同時面對許多的客戶(client), 像是luncher, Android system process 或是其它應用程式並且服務這些客戶的需求. 客戶端透過 binder(一種IPC機制) 和 surfaceflinger 互相通信, 每個客戶開始使用 surfaceflinger 的服務之前, 需和 surfaceflinger 建立 connection, 也就是在 surfaceflinger端建立代理者. 所謂的代理者BClient 物件. 我們打開 \frameworks\base\libs\surfaceflinger\SurfaceFlinger.cpp, 這裡定義了一個 class : BClient , 看到BClient:: createSurface() 函式, 我們可以把這個函式當作 gralloc_alloc 被呼叫的起源. BClient::createSurface() 會調用 surfaceflinger::createSurface() , 此函式根據不同的 surface type 調用不同的函式, 我們先看到 SurfaceFlinger::createNormalSurfaceLocked(), 此函式會產生 Layer 物件並調用 Layer::setBuffers() 函式, Layer::setBuffers() 函式產生 GraphicBuffer 物件並調用 GraphicBuffer::initSize(), 請看 \frameworks\base\libs\ui\GraphicBuffer.cpp,  GraphicBuffer::initSize()會調用
GraphicBufferAllocator::alloc() 函式, 此函式會調用到 gralloc::gralloc_alloc().

PMEM 的記憶體管理

Android 中透過SimpleBestFitAllocator 物件為 PMEM 提供管理機制, 程式碼在
/hardware/libhardware/modules/gralloc/Allocator.cpp
/hardware/libhardware/modules/gralloc/Allocator.h
class SimpleBestFitAllocator 比較關鍵的函式是 alloc() 以及 dealloc()
SimpleBestFitAllocator::alloc() 函式如下
Source3.cpp
01 ssize_t SimpleBestFitAllocator::alloc(size_t size, uint32_t flags)
02 {
03     if (size == 0) {
04         return 0;
05     }
06     size = (size + kMemoryAlign-1) / kMemoryAlign;
07     chunk_t* free_chunk = 0;
08     chunk_t* cur = mList.head();
09 
10     size_t pagesize = getpagesize();
11     while (cur) {
12         int extra = ( -cur->start & ((pagesize/kMemoryAlign)-1) ) ;
13 
14         // best fit
15         if (cur->free && (cur->size >= (size+extra))) {
16             if ((!free_chunk) || (cur->size < free_chunk->size)) {
17                 free_chunk = cur;
18             }
19             if (cur->size == size) {
20                 break;
21             }
22         }
23         cur = cur->next;
24     }
25 
26     if (free_chunk) {
27         const size_t free_size = free_chunk->size;
28         free_chunk->free = 0;
29         free_chunk->size = size;
30                 // Richard
31                 free_chunk->w = g_w;
32                 free_chunk->h = g_h;
33                 free_chunk->fmt = g_fmt;
34                
35         if (free_size > size) {
36             int extra = ( -free_chunk->start & ((pagesize/kMemoryAlign)-1) ) ;
37             if (extra) {
38                 chunk_t* split = new chunk_t(free_chunk->start, extra, 0,0,0);
39                 free_chunk->start += extra;
40                 mList.insertBefore(free_chunk, split);
41             }
42 
43             LOGE_IF(((free_chunk->start*kMemoryAlign)&(pagesize-1)),
44                     "page is not aligned!!!");
45 
46             const ssize_t tail_free = free_size - (size+extra);
47             if (tail_free > 0) {
48                 chunk_t* split = new chunk_t(
49                 free_chunk->start + free_chunk->size, tail_free, 0, 0, 0);
50                 mList.insertAfter(free_chunk, split);
51             }                      
52         }
53         statistic_alloc( size*kMemoryAlign ); // Richard statistic.
54         return (free_chunk->start)*kMemoryAlign;
55     }
56     return -ENOMEM;
57 }

SimpleBestFitAllocator::alloc() 函式使用 best fit 演算法管理 PMEM, 11~24行, best fit 演算法會尋找最接近需求大小的可用記憶體區塊. 因為選擇出來的記憶體區塊大小一定大於且最接近需求, 因此在第 40 和 50 行 best fit 演算法會將選到的記憶體區塊分割成兩塊, 一塊符合需求大小的區塊供做使用, 另一塊分割後則變成新的未使用記憶體區塊.  best fit 演算法的缺點是比較容易產生因為較小而無法被使用的小區塊.
圖一是 reboot, 進入 home screen 後 PMEM 使用狀況, 屏的尺寸是WVGA(800x480), PMEM size =24MB, 尚未被使用的記憶體區塊標示為 FREE, 被佔用的記憶體區塊標示為 OCCU.
圖一
另外我們來看 fragmentation rate,  這裡計算fragmentation rate 的方式是:
同樣的, 從 reboot 開始到進入 home screen, 每次有 buffer allocate/free , 就 log 一次 fragmentation rate.  
fragmentation : 3.059894 % 
fragmentation : 6.119794 % 
fragmentation : 6.412763 % 
fragmentation : 11.897784 %
fragmentation : 17.724609 %
fragmentation : 23.209637 %
fragmentation : 28.694660 %
fragmentation : 28.694660 %
fragmentation : 28.694660 %
fragmentation : 28.694660 %
fragmentation : 31.754559 %
fragmentation : 34.814453 %
fragmentation : 34.814453 %
fragmentation : 34.814453 %
fragmentation : 34.814453 %
fragmentation : 40.641277 %
fragmentation : 40.641277 %
fragmentation : 43.701172 %
fragmentation : 46.761066 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %
fragmentation : 52.246094 %

所以,完成開機後, fragment rate 大約是 52.2%.

以下圖表是 HVGA 以及 WVGA 搭配 PMEM size 分別設定成 16MB, 20MB, 24MB 時的 fragmentation rate 狀況. 開機後, 運行瀏覽器然後旋轉到橫屏並立刻點選準備輸入網址. 若是使用 Android 原生的 best fit 演算法, 16MB, 20MB 都會發生 out of memory.



沒有留言:

張貼留言