Wednesday, April 29, 2009

一千零一夜之 eagle

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

之前的文章提到過,只要有適當的 loader,DRI driver 也可以用在 X server 以外的平台,eagle 就是一個例子。eagle 提供開發者接近於 EGL 的 API,並且可以運作在支援 DRI2 的 X server 與支援 GEM 的 intel drm 之上。這篇文章主要想討論後者。

eagle 與視窗系統無關的實作,行數落在大約 800 行左右。intel drm 的支援也只在 250 行左右。對它使用到的技術有些許了解後,只要花一個晚上就可以讀完,算是學習 linux 3D 實作不錯的途徑。也因為它的簡單,閱讀過程如果有難以理解的部份,也可以馬上修改跑跑看。如果這兩部份看完後想要再看 DRI2 的部份,eagle 也可以讓人滿足。

intel drm 的支援在 eagle 中稱為 backend,以 EagleBackend 表示。Backend 與視窗系統相關,要提供的主要功能有

  • 建立 EGLSurface

  • 提供 DRI driver 需要的 GEM buffer

  • 更新畫好的內容到螢幕上



intel drm backend 建立 EGLSurface 的函數是 intelCreateSurfaceForName。這邊的 name 型別為 uint32_t,指的是 GEM buffer 的 identifier。如果沒給的話,backend 會產生一塊新的 buffer。client 可以要求 double buffering,這時 surface 的 backBuffer 會設為 EGL_TRUE。

intelGetBuffers 負責提供 DRI driver 需要的 GEM buffer,像是 front buffer,back buffer 或 depth buffer 等。這邊要特別注意,在 double buffering 的情形,也就是 backBuffer 設為 EGL_TRUE 的時候,即便 DRI driver 要求了 front buffer,intelGetBuffers 還是會產生新的 buffer 回傳給 DRI driver,而不是回傳 intelCreateSurfaceForName 時給定或產生的 front buffer。這個新產生的 buffer 也被稱為 fake front buffer。

最後看到 intelSwapBuffers,也就是 eglSwapBuffers 會呼叫的函數。可以看到,當不是 double buffering 時,這個函數會直接 return,因為 DRI driver 本來就是畫在真正的 front buffer 上。若是 double buffering 的話,intelSwapBuffers 會把 DRI driver 以為的 (fake) front buffer 複製到真正的 front buffer 上。

Backend 大致上就處理這些事情。eagle 的核心則負責提供接近 EGL 的 API。它同時也是 DRI loader,選定適當的 EagleBackend 並載入適當的 DRI driver。除了選擇與 dlopen 適當的 .so 檔外,核心其餘的程式碼大多在處理 EGL 的型別與 DRI 型別的對映。這個部份很基礎,也是所有想寫 loader 的開發者都要熟悉的東西。這邊的程式碼比較直接,有興趣的人可以自行閱讀。

在初次研讀 eagle 的過程中,我最感興趣的其實是這篇文章沒提到的 glapi。這是 mesa 或 mesa 自動產生的程式碼,其中後者佔了絕大多數。在許多功用中,其中一項功用是提供 OpenGL (ES) 的 global symbols,讓 link 到 libeagle.so 的程式可以呼叫 gl* 等函數。這可以算是 mesa build system 的一部份,對熟悉 linux 3D 的組成有相當的幫助。

Friday, April 17, 2009

一千零一夜之 Android Binder

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

Binder 是 android 大量使用的 IPC 機制。當使用者在 launcher (home) 按了某個 App 時,這個動作會經由 ActivityManager 向 zygote 發出請求,並從 zygote fork 出新的 process 執行被選擇的 App。這個過程大致上有 4 個 process 參與:home、system server、zygote 和新產生的 appA。更仔細去看的話,

  1. system server 向 home 送出 touchscreen 事件

  2. home 向 system server 送出 start activity 事件

  3. system server 向 zygote 送出 appA 的啟動參數

  4. appA 向 system server 送出 attach 事件


除了第 3 項以外,其它跨 process 的溝通都是採用 binder。Zygote 是 android 上少數幾個聽 unix socket 的程式。驗證這個說法最簡單的方法是去看 process 的 memory maps 還有 opened fd,這個技巧在之前有用過,這邊暫且略過。

一個 IPC 機制的效能很大一部份取決於它資料交換的能力,從這點去切入是不錯的途徑。所有參與 binder 的 process 都會在一開始向 /dev/binder 裝置 mmap 一塊 read-only 的記憶體,大小在幾個 MB 左右。A process 傳送資料給 B process 的做法是,由 A 對 /dev/binder 做 ioctl 將資料先送給 binder driver。依資料大小,binder driver 動態配置足夠的 physical memory 存放來自 A 的資料,並在 B 一開始 mmap 得到的 address space 裡尋找一段足夠大且未使用的 address area。找到後,binder driver 會修改 B 的 page table,讓這段 address area 映射到剛剛動態配置的 physical memory。如此一來,B 可以直接存取 A 送過來的資料;等到 B 不需要這些資料時,再由 B 負責把這塊記憶體還回去給系統。這段實作可以參改 binder driver 的 binder_update_page_range 函數。

Process 對 binder 的操作只透過 ioctl 做 buffer 的讀寫。Buffer 的內容是一系列的 (command, argument) 組。每個 command 為 4 bytes,argument 的長度視 command 而定,可能是固定的,也可能是變動的。當 A 跟 B 交換資料時,通常會由一方送出 BC_TRANSACTION command,再由另一方回覆 BC_REPLY command。Transaction/reply 的 argument 除了一段 header 外,其餘的資料部份會使用上一段討論的方式做交換。資料的長度跟內容由 A、B 另行約定,可能是整數、浮點數或字串等。除了上述的基本資料型別外,還有三種用 struct flat_binder_object 表示的特殊型別:BINDER、HANDLE、FD。FD 型別讓 A、B 可以交換 file descriptor,也就是說,A 可以打開某個檔案,把 fd 傳給 B,讓 B 去讀寫。或者說,也是更重要的,surface manager 可以準備一塊 surface,把 surface 的 fd (一塊 ashmem 記憶體) 傳給一個 app,讓 app 可以在上面作畫。有了這層理解,我們對 android 可以有更自由的發揮,例如,可以不改一行 cairo 的程式碼,就利用 cairo 與 c++ 寫出一個會動的時鐘:



回到正題,我們知道 binder 還有 BINDER 與 HANDLE 這兩個特殊型別。它們的功用可以用 service port 跟 connection 做類比。就像 httpd 可以開 80 port 提供服務,client 可以跟它建立 connection 使用服務一樣,在 android 上頭,WindowManagerService 可能建立了一個 BINDER,提供了視窗操作的服務。其它程式可能會建立這個服務的 HANDLE。在 binder 之上,android 提供了 aidl 讓開發者可以把 remote service 當做 local object 操作,所以開發者可以直接呼叫 mWindowManager.setRotation 來旋轉螢幕,而幾乎可以不用知道 BINDER 跟 HANDLE 的存在。

當一個 process 收到 transaction 的時候,如果 binder driver 發現 process 沒有任何 thread 已經準備好等著要處理,等到下次有 thread 來處理時,它會收到 BR_SPAWN_LOOPER 的訊息,userspace 也會多產生一個 thread 專門對 /dev/binder 做 I/O,確保下次有 transaction 的時候,有比較高的機會是已經有 thread 準備好了。提個數字,在 android sdk 1.5 preview 的 emulator 剛開好機時,在我的機器上,Phone App 會有 11 個 thread,而其中有 6 個 thread 是跑在同一個迴圈裡等著對 /dev/binder 做 I/O。這項機制相信是為了縮短應用程式的反應時間。

Binder 再探討下去還有許多東西可以討論,例如 android native library 沒有 aidl 可以用,它如何建立操作 binder 的物件?或者 native code 裡面常常看到的 sp<>、wp<> 跟 binder 的 reference counting 關係為何?對 binder 的功能有初步認識後,再去理解這些實作會顯得直覺許多。

Thursday, April 9, 2009

Android with cursor

一般 PC 上面沒有 touchscreen 只有滑鼠,為了方便做事,晚上試著幫 Android 加上 cursor:



暫時是做在 WindowManagerService 裡面,髒髒的 patch 可以在這邊取得。目前 drag 有點問題,而且是 software cursor,歡迎有興趣的網友幫忙改進 :)

Monday, April 6, 2009

一千零一夜之 DRI2 Overview

這個系列是讀書筆記,作者可能沒有跟主題有關的開發經驗。

Linux 的圖形加速一直是個問題,不過這個問題在最近幾版的 kernel 隱約可以看到一些希望。從 2.6.28 的 GEM memory manager 到 2.6.29 的 kernel modesetting,由 Intel 與 RedHat 的開發者所提出的加速架構已經進入 kernel,雖然目前採用這些架構的只有 Intel 自身的驅動程式,但其它繪圖晶片的 open source 驅動程式勢必也得跟隨此架構。在 Intel 與 AMD 相繼釋出 datasheet 後,或許有天所有的使用者都可以享用這些成果,不再依賴私有的驅動程式。

去年年中 GEM 公佈後,許多架構迅速翻新,DRI2 最後也環繞著 GEM 在設計。DRI (Direct Rendering Infrastructure) 是各家 open source 驅動程式所依循的架構,雖然處處都可以看見這個名詞,但是要了解它背後的實作卻不太容易。困難的地方在於 DRI 的組成份子很多,而且大部份來頭都不小。以 Intel 的 945GM 為例,在 kernel 有 drm.ko 與 i915.ko 兩個模組,對映到 userspace,有 drm 提供的libdrm.so 與 libdrm_intel.so;在 xserver 有 libglx.so 與 libdri2.so 這兩個 extension,以及 xf86-video-intel 提供的 intel_drv.so;最後在 mesa 也有 libGL.so 以及 i915_dri.so。習慣上,intel_drv.so 被稱作 2D driver 或 DDX driver,i915_dri.so 則是 3D driver 或 DRI driver。從一個 3D 程式被執行時到顯示在螢幕上,參與這個看似普通的過程背後就是上面這些檔案。

DRI 最重要的內涵在於,一個 X client 可以向 X server 取得視窗的 buffer(s),並利用硬體加速在上面直接繪圖,不再透過 X server。Kernel 在 DRI 扮演的主要角色是讓多個 process 可以共享 (GEM) buffer,還有接受與執行 process 傳來的硬體指令。這邊困難的地方之一在於 CPU 跟 GPU 都有各自的 MMU 與 cache,所以有許多同步的問題要處理。

libGL.so 在 DRI 架構裡扮演了 loader 的角色,它跑在 client 這邊。就像 OpenGL 與平台無關一樣,DRI driver 也與平台無關。loader 一方面載入 DRI driver,一方面又提供適當的 hook 讓 DRI driver 呼叫,並實作了平台相關的部份。所以在 DRI driver 真的要用到 buffer 的時候,它會呼叫 loader 的 getBuffers 函數取得視窗的 buffer 與大小。libglx.so 跟 libdri2.so 實作了 GLX 與 DRI2 這兩個 X extensions。在 X 的平台上,對於 DRI driver 的要求,libGL.so 會利用 GLX 與 DRI2 協定取得所需的資訊,並傳回給 DRI driver。

一個快速驗證上面說法的方法是直接檢視系統的 /usr/lib/libGL.so 跟 /usr/lib/dri/i915_dri.so。libGL.so 只是 loader,檔案大小應該要比 i915_dri.so 小不少;而 i915_dri.so 與平台無關,所以 ldd 應該不會看到 X11 的函式庫。如果再進一步想,那是不是只要提供適當的 loader,DRI driver 也能用在 X 以外的平台?答案是肯定的。

在 compiz 剛出來的時候,有很多名詞跟著它一起冒出來。像是 XGL、GLX_EXT_texture_from_pixmap、還有 AIGLX 等。在 libGL.so 無法成功跟 X server 溝通使用 DRI 時,client 的 3D 繪圖指令會改經由 GLX 傳給 server 處理,這被稱作 indirect rendering。在早先,libglx.so 會利用純軟體的方式執行這些 3D 指令,所以 indirect rendering 跟 software rendering 是等價的。這個情形在 AIGLX 引入後有很大的改變。在 AIGLX 下,libglx.so 除了提供 GLX extension 實作外,它同時也是一個 DRI loader,可以利用 DRI driver 進行 3D 加速。因為 AIGLX,indirect rendering 不再直接被視為慢、無法接受,雖然它還是多了 client 到 server 的 overhead。AIGLX 即是 Accelerated Indirect GLX 的縮寫,非常直接。

因為我直接從 DRI2 開始看,無法比較它跟 DRI1 的差異。在 DRI2 作者 Kristian Høgsberg 的 blog 可以找到一些關鍵字。Kristian Høgsberg 同時也是之前文章提到的 Wayland 的作者。