2015年8月27日 星期四

error: manifest required for this command -- please run init

在做repo sync時,遇過一次這樣的問題 (error: manifest required for this command -- please run init),如果這個message有特別顯示在某一行的話,像是 (301/665),可能在checkout第301個project出問題,可以將那個project的目錄整個砍到,再重做一次repo sync,但是如果所有的project都fetch完了才出現這問題,也沒特別指明是哪個project,則可以把所有的project目錄砍掉(不要動到.repo目錄),再重做一次repo sync即可。.repo/project.list裡有所有project的列表。

2015年8月23日 星期日

Disk encryption process

在Android L裡,有兩種方式可以啟動Disk Encryption,一種是系統第一次開機時自動做encryption,這種方式是由OEM所設定,另一種則是經由Settings UI,這種方式是由end user決定是否要做encryption。當然,你也可以使用vdc直接要求vold將disk做encryption。目前Android只支援一個partition的encryption,也就是/data這個partition,/system partition的encryption目前並不支援,但其實要支援好像也不是什麼難事,只是有沒有這個需要而己。

並不是每個partition都可以做encryption,基本上,OEM必須在fstab裡對該partition標註forceencrypt或encryptable,該partition才能做encryption。如果標註forceencrypt的話,在Android第一次開機時就會將該partition做encrypt,如果是encryptable的話,則交由user決定。

針對被encrypt的partition,系統必須記錄一些資料(像是key相關資料)才能正常的管理它,這些資料稱meta data。Meta data可以存放在該Partition裡,也可以儲存在不同的partition。前者Android稱為Footer,因為這些資料是存放在該partition的最後16K,也就說user可使用的空間必須扣掉16K。

下面是fstab裡的UDA partition的描述,Android在第一次開機時,會強制對UDA這個partition做encrypt, meta data會存放在MD1這個partition。這個UDA partition的file system是f2fs

/dev/block/platform/sdhci-tegra.3/by-name/UDA  /data  f2fs  noatime,nosuid,nodev,errors=recover wait,check,forceencrypt=/dev/block/platforom/sdhci-tegra.3/by-name/MD1

forceencrpyt可以使用encryptable取代,encryptable代表這個partition可以被encrypt,但是不會在第一次開機就強制執行,而是交由使用者去選擇。如果OEM不想用另一個partition儲存meta data,可以用"footer"這個字(forceencrypt=footer),Android會將UDA partition的最後16K保留,用來存放meta data.

Service Class

在開始了解Encryption process之前,我們要先知道Init process,因為不管是Encryption Process或Encryption Booting,都跟Init Process有很大的關係。大部份用來Customize Init Process的設定都是寫入rc file。rc file可以定義Service,而每一個Service可以屬於某個class。在Android裡,定義了三種class,分別是core, main與late_start。一般來講, 屬於core class的service在啟動之後就不會被stop或restart,屬於main或late_start這兩個class的service則是有可能視情況需要而stop或restart。像是healthd, vold或surfaceflinger是屬於core class,所以除非這些service自己crash,Android並不會故意將它stop或restart,但是main與late_start這兩個class就不一定,像是encryption process或encryption booting就會將這兩個class停止或重啟。至於一個service應該定義在哪個class,則是應該視該service的用途來做判斷。以目前Android的設定,當main class啟動完成之後,重要的service都己經啟動了,user也可以看到launcher的畫面。稍後會看到encryption或encryption booting時為了要顯示UI,會先將main class啟動,而late_start則是在/data己經mount好之後才啟動。

Triggering Encryption & Encryption Booting

Encryption Process的觸發可分別兩種,一種是OEM在fstab裡指定forceencrypt參數,這是由Init Process在第一次開機(或factory reset後的第一次開機)後自動觸發。另一種則是開機後由App或User從Settings裡去觸發。不管是哪一種,最後Encryption都是由vold, init process與app/framework來共同完成。init process與vold及framework之間的溝通是透過property。

當init process執行mount_all時,它會fork一個process根據fstab內容去做mount,這是為了避免mount出現任何問題以致於init本身crash。child process做完mount之後就會結束,並將mount結果傳回給init process, init本身在fork之後則是會等child process結束才繼續。child process的結果有以下幾種

FS_MGR_MNTALL_DEV_NEEDS_ENCRYPTION
表示某個partition (其實就是指/data)被標註forceencryption,但是還沒被encrypt,init process會將vold.decrypt這個property內容設為trigger_encryption,表示要啟動Encryption process。

FS_MGR_MNTALL_DEV_MIGHT_BE_ENCRYPTED
表示Encryption process己經做過了,init process會設定下面幾個property的內容進行Encryption booting的程序

ro.crypto.state = encrypted
vold.decrypt = trigger_default_encryption


FS_MGR_MNTALL_DEV_NOT_ENCRYPTED
表示沒有任何一個partition被encrypt,而且也沒有任何一個partition被標註forceencrypt (有可能有標註encryptable)。這就是一般沒做partition encryption的開機流程。init process會將ro.crypto.state設為unencrypted,然後觸發nonencrypted的action。nonencrypted的action就是啟動main與late_start這兩個class的service。

< from rc file >
on nonencrypted
    class_start main
    class_start late_start

FS_MGR_MNTALL_DEV_NEEDS_RECOVERY
這表示child process沒辦法將partition正常mount上,有可能是encryption發生不正常的現象,init process必須wipe partition (將partition內容清除,也就是factory reset),系統會進入recovery mode。

除了由init process觸發的encryption (或factory reset)之外,也可以由Setting裡的CryptKeeperConfirm來觸發。User可以從Setting選擇要做disk encryption,最後會由CryptKeeperConfirm呼叫MountService.encryptStorage(),MountService再要求vold執行"cryptfs enablecrypto inplace "命令開始做disk encryption。

如果我們比較forceencrypt與user從Setting觸發的encryption command,可以發現一點差異,稍後看到encryption的過程再來討論。
  • forceencrypt - "cryptfs enablecrypto inplace default"
  • Settings - "cryptfs enablecrypto inplace "

Encryption Process

前面提到Encryption process可以由init process觸發,也可以由Settings觸發,兩者其都是要求vold去執行"cryptfs enablecrypto inplace" command,差別在於參數略有不同而己。

下圖是一張粗略的Encryption流程,過程大致了解再來深入幾個比較有意思的地方。星星標註的地方代表可以觸發Encryption,也就是可以從Settting與開機時由Init process觸發 (forceencrypt)。不管哪一個,都是下command到vold。Vold command分成幾個類別,cryptfs這一類用來處理跟encryption相關的。



vold在做encryption時主要是有下面幾個步驟
    1. shutdown framework
    2. unmount all asec
    3. unmount sdcard
    4. unmount /data
    5. mount tmpfs on /data
    6. initialize /data through init post_fs section
    7. set encryption progress property to 0
    8. Show progress UI
    9. encrypt each sector while updating progress property
   10. Reboot system when all sectors encrypted

Encryption的做法在概念上很簡單,以ext4來講,只要將/data這個partition的每個sector加密過就完成了,簡單講就是像下面這個樣子,也就是上面step 9。

for (int i = 0; i < sector_count; ++i) {
    encrypt(sector[i]);
}

之所以要有step1 ~ step8主要就是要避免有process正在使用/data,因為/data這個partition就是我們要做encryption的對像。另一個原因就是希望將Encryption的進度以UI的方式顯示,讓user知道系統還活著,Encryption會花點時間,Step 5~9主要就是為了UI的關係。

Step 1 - Shutdown framework
Shutdown framework是將vold.decrypt設為trigger_shutdown_framework,init process收到後會將main與late_start這兩個class services停止,這是為了避免framework或app正在使用/data partition。

Step 2 - Unmount all asec
Asec是android secure container,這是加密過的資料,當user將app移到sdcard時,系統會在sdcard上產生一個asec檔案,framework會將它mount在/data裡,如果不將asec unmount的話,最後/data也無法unmount,encryption就無法進行。

Step 3 - Unmount sdcard
Sdcard的access是透過FUSE的架構,有一個sdcard service用來處理sdcard files的access,基本上user process是access /storage/emulated/,透過bind mount的方式,這些access會被導引到/mnt/shell/emulated/,而這是/dev/fuse的一個mount point,因此這些access會由/dev/fuse的driver處理,而/dev/fuse會將這些access再導引至sdcard service。sdcard service會再將這些access導引到真正sdcard的mount point,也就是/data/media。因此,如果sdcard沒有unmount的話,/data也沒辦法unmount。

Step 4 - Unmount /data
做encryption前,/data必須被unmount,避免一邊使用一邊Encryption的事情發生。

Step 5 - Mount tmpfs on /data
tmpfs是用在RAM的file system,也就是割出一塊RAM,initialize成tmpfs format,並mount在/data,這主要是為了顯示UI做準備。目前顯示UI的方式是啟動framework,執行一個特殊的Launcher,它的用途就是顯示目前Encryption的進度,當然它還有其它用途,但以Encryption而言,這是它其中一個用途。可是framework的運作是需要/data存在,而且還必須有一些資料設定好,因此,vold才會產生一個暫時的/data供framework使用。

Step 6 - Initialize /data through post_fs
上面提到vold將一塊RAM mount在/data供framework使用,但是不是只要提供/data就好了,/data裡面還是要有一些設定,這些設定就由rc file的post_fs section來處理,因此,vold會將vold.decrypt設為trigger_post_fs_data,init process就會開始執行rc file的post_fs section,進而觸發其它section的命令。

Step 7 - Set encryption progress to 0
由於Encryption還沒開始,所以它的progress是0,這個progress要能讓framework app/launcher可以看到,目前是使用一個property (vold.encrypt_progress)當作彼此溝通的媒介。

Step 8 - Show progress UI
vold會將vold.decrypt設為trigger_restart_min_framework,init process會將main class services啟動,launcher也會被啟動,但被啟動的launcher是一個特殊的launcher,在Encryption Booting這個section,會有比較詳細的討論。簡單的講,這個Launcher會根據vold.encrypt_progress內容顯示encryption進度。

Step 9 - Encryption for each sector
這個步驅主要就是產生一個crypto device (dm-crypt driver),並將它map到原本的/data partition,把原本的/data partition的每個sector讀出來,寫到crypto device,dm-crypt driver會將寫入的sector做encryption,再寫入原來的/data partition相同的sector,這樣就完成了一個sector的encryption,每個sector都按照一樣的方式處理過就完成了encryption,稍後我們會再進一步討論這部份。在Encrypt每個sector的時候,vold也會持續更新vold.encrypt_progress內容,因此Launcher也可以讓user知道最新的進度。

Step 10 - Reboot system
當encryption完成後,vold會直接reboot system,它會留個幾秒讓Launcher顯示到100%才reboot system。


Encryption Password

在KK,password有兩種,一個是screen password,一個是encryption password,前者用來解除screen lock,後者用在開機時驗證encryption password。但是到的L的時候,這兩個password被合而為一。在KK,當user啟動disk encryption時,framework會要求user提供一組encryption password,但是在L的時候,就不再詢問user了,它會直接使用usr設定的screen password,如果user也沒設定screen password,就會直接用預設的password,可是當user更改screen password時,encryption password也會跟著更改。

有一個容易造成誤解的是encryption password並不是用來做為disk encryption的key。當disk encryption開始時,framework會產生一個key,這個key才是disk encryption時所要用的,我們稱為master key,user所輸入的password是用來加密master key而己。而加密後的master key會儲存在Meta data裡,因此,每次開機,framework都需要向user詢問encryption key,這樣它才能從meta data裡讀出加密過的key,再將它解密,然後交給dm-crypt,系統才能正常booting。


dm-crypt

dm-crypt是Linux device mapper的一種target,它可以做到transparent encryption/decryption,device mapping是一種mapping的架構,它會產生一個virtual device,並設定一個mapping table,所以當user在access這個virtual device時,它會將這個access轉介到另一個device,並根據mapping table改變要access的區域。以dm-crypt來講,假設真正儲存/data的device是B,我們會產生一個dm-crypt device,假設名字是dm-0,而mapping table會設定為dm-0的第N個sector對應到B的第N的sector。Disk encryption就會變成,將B的每個sector讀出,並寫入dm-0的每個sector。B的內容讀出後是還沒做過encryption的,但是寫到dm-0之後,dm-crypt會對寫入的資料做encryption,並寫入B的同樣sector,這樣一來,B的內容就是加密過的,如果這個時候我們直接從B再讀取一次,就會得到加密過的資料,但是如果我們透過dm-0去讀,dm-crypt會先讀B的內容,再將它解密,然後再回傳。如果我們寫入資料到dm-0,dm-crypt就會將寫入的資料加密,再寫到B的相同sector。所以,在encryption過的系統,/data是mount在dm-crypt的device,而不是原始的device,我們可以透過adb shell mount來觀察。

下面這段code是vold cryptfs的function,vold將dm-crypt的device產生出來後,會將它的path儲存在ro.crypto.fs_crypto_blkdev這個property裡,這個function再將它讀出,mount到/data下。

static int cryptfs_restart_internal(int restart_main) {
   ...
     property_get("ro.crypto.fs_crypto_blkdev", crypto_blkdev, "");
     if (strlen(crypto_blkdev) == 0) {
         SLOGE("fs_crypto_blkdev not set\n");
         return -1;
     }

     if (! (rc = wait_and_unmount(DATA_MNT_POINT, true)) ) {
         /* If that succeeded, then mount the decrypted filesystem */
         int retries = RETRY_MOUNT_ATTEMPTS;
         int mount_rc;
         while ((mount_rc = fs_mgr_do_mount(fstab, DATA_MNT_POINT,
                                            crypto_blkdev, 0))
                != 0) {
             ....
         }
         ....
     }
}


Encryption Booting

下面這張圖描述Encryption Booting的過程。Init process根據fstab在mount所有的partition時,如果disk己經被encrypt過,它會設定以下兩個property開始Encryption Booting。
  • ro.crypto.state = encrypted
  • vold.decrypt = trigger_default_encryption
trigger_default_encryption會啟動defaultcrypto這個service (one-shot service),而defaultcrypto則是純粹的使用vdc要求vold去執行"cryptfs mountdefaultencrypted"這個command。如果當初在做Encryption時,沒有指定password的話,vold這邊的動作比較單純,vold都會產生一個crypto device(dm-0),使用同樣的mapping table對應到userdata partition,並將dm-0 mount在/data目錄。因此,在Encryption之後,所有對/data partition的access都會在dm-0 device上,而dm-0則會將access導引之原本的data partition,並做encryption/decryption的動作。對user mode的process之言,並不會感覺有所變化,當然,performance或許有變,但使用上跟disk encryption之前沒有差別。

當crypto device被mount到/data後,vold會通知init process將persistent properties讀取進來(將vold.decrypt設為trigger_load_persistent_. Persistent properties (persist.*)是儲存在/data裡,因此,在Encryption Booting時,一直要等到/data被mount後才能將它讀出來。有一點要注意,這個時core class service都己經被啟動,如果core class service有用到persistent properties,它就讀不到真正的property內容。

接下來,vold會通知init process開始執行rc file裡post_fs這個section裡的command(將vold.decrypt設為trigger_post_fs_data),最後才要求init process啟動framework(將vold.decrypt設為trigger_restart_framework),而所謂啟動framework就是將main與late_start這兩個class裡的service啟動,main啟動完後,user就可以看到launcher的畫面了。




但是如果當初在做Encryption時,如果user有設定password的話,那狀況會比較複雜一點,因為必須將framework啟動,顯示UI詢問password。所以在vold收到cryptfs mountdefaultencrypted這個command時,它會要求init process先將main class service啟動(將vold.decrypt設為trigger_restart_min_framework)。前面有提到當main class啟動完成後,user就會看到Launcher畫面,也就是說user會看到UI詢問password,得到password後,UI會直接使用MountService.decryptStorage()來與vold確認password是否正確。decryptStorage()這裡會做兩件事,它會利用cryptfs checkpw這個command要求vold確認password是否正確,如果正確的的話,它會利用cryptfs restart這個command要求vold繼續Encryption Booting的程序。

這裡有一個比較tricky的地方,當main class啟動時,理論上user看到的第一個畫面是launcher,那為何在trigger_restart_min_framework時,會出現問user password的畫面? 原因在於這個系統裡其實至少有二個Launcher,一個是一般user使用的launcher,像是Launcher3或GoogleLauncher,另一個Launcher就是用來顯示詢問Password畫面,這個Launcher就是Settings app裡其中一個activity (CryptKeeper)




CryptKeeper有HOME這個category,所以它會被系統認定為Launcher類別,而且它的priority是10,高於一般的Launcher。因此,如果沒有特別的處理,CryptKeeper會被當做系統預設的Launcher,但事實上,除了在Encryption Booting時你會看到它詢問密碼,在其它時刻它並不會出現。原因在於這個Activity在第一次開機時就將自己本身Disable,所以framework會去使用第二順位的Launcher。但是為何它又能被執行顯示詢問password的畫面?因為當component被disable時,這個資料會儲存在/data裡,而framework被restart來顯示password晝面時,/data還沒被mount起來,因此這個時候相當於這個component從沒被disable過。

         final String state = SystemProperties.get("vold.decrypt");
         if (!isDebugView() && ("".equals(state) || DECRYPT_STATE.equals(state))) {
             // Disable the crypt keeper.
             PackageManager pm = getPackageManager();
             ComponentName name = new ComponentName(this, CryptKeeper.class);
             pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                     PackageManager.DONT_KILL_APP);
             // Typically CryptKeeper is launched as the home app.  We didn't
             // want to be running, so need to finish this activity.  We can count
             // on the activity manager re-launching the new home app upon finishing
             // this one, since this will leave the activity stack empty.
             // NOTE: This is really grungy.  I think it would be better for the
             // activity manager to explicitly launch the crypt keeper instead of
             // home in the situation where we need to decrypt the device
             finish();
             return;
         }


cryptfs restart這個command會要求vold將main class裡的service停止(reset main),因為這時候/data還沒mount起來,所以,可能有些service讀取到的設定並不正確(像Launcher就不是user所期待的),因此這個時候將main class reset,稍後等/data mount起來,再重新啟動main class才會讓main class的service讀取到正確的設定。reset完main class後,就跟前面提到的順序一樣,mount /data,讀取persistent properties,執行rc檔的post_fs命令,然後啟動framework。

Uncrypt (OTA Update)

在KK的時候,OTA package是download到/cache裡,因此在recovery mode時,只要從/cache讀出OTA package內容,就可以直接做OTA update。/cache並不會被encrypt,所以即使/data被encrypt,OTA update也不受影響。但是這會有一個問題,/cache的size必須夠大,要不然可能無法容納OTA package。OTA package內容主要就是/system的內容,因此,在最差的情況下,OTA package的大小可能會跟/system partiton的大小類似,其實這是有點浪費空間的,畢竟除了OTA package之外,/cache也用不了那麼多的空間。

在L之後,OTA package可以被放在/data裡,在recovery mode時再將它讀出做OTA update。這似乎沒什麼大問題,但其實有兩件事值得思索一下
     1. 由於OTA package放在/data,因此,recovery mode必須將/data mount起來。
     2. 如果/data被encrypt,recovery mode就得想辦法將/data decrypt才能讀出OTA package。

第一件事似乎沒什麼大不了,反正就mount /data就好,我想也是,但似乎Google不怎麼想在recovery mode將/data mount起來,或許有什麼原因我們不知道。第二件事就比較麻煩,因為,recovery mode必須知道encrypt key的內容並將dm-crypt的device mount起來,並設定好mapping table,但這些事都是在vold做的,Google大概不想再搬一份相同的code到recovery mode裡。因此,在L做OTA update時,它會產生一個map file放在/cache裡,把OTA package (zip file)所使用到的block記錄下來,內容類似下面這個樣子

      /dev/block/platform/msm_sdcc.1/by-name/userdata     # block device
      49652 4096                        # file size in bytes, block size
      3                                 # count of block ranges
      1000 1008                         # block range 0
      2100 2102                         # ... block range 1
      30 33                             # ... block range 2

因此,在做OTA update時,只要根據這個map file,直接讀取/data所屬的block device內容就可以了。但是/data如果被encrypt過怎麼辦?當系統要reboot到recovery mode之前,它會執行一個做uncrypt的service,這個service就是用來產生map file,如果/data是被encrypt的,它會先從/data將OTA package所使用的block讀出(這個時候/data是mount在dm-crypt device上,所以讀出來的內容是decrypt過的),再將讀出的block直接寫入原本/data所屬的block device的相同block。所以在recovery mode時,系統只要按照這個map file就可以讀出OTA package的內容,即使/data己經被encrypt過。

這樣還是有一個問題,它會造成OTA update在reboot到recovery mode前有一些delay,目前framework預設是5分鐘,但是,如果這個platform decrypt的速度不快的話,可能會造成uncrypt還沒處理完,系統就會被shutdown (因為ShutdownThread.rebootOrShutdown會呼叫PowerManagerService.lowLevelReboot來reboot系統,但是如果timeout的話,它會認為reboot失敗,它就會將系統shutdown),然後OTA update就會失敗。下面這段code是PowerManagerService.lowLelvelReboot,當它被要求要reboot到recovery mode,它會要求init process啟動uncrypt,並開始計時,它最多會等個5分鐘讓uncrypt把事情做完,如果做不完,系統會被shutdown。

     public static void lowLevelReboot(String reason) {
         if (reason == null) {
             reason = "";
         }
         long duration;
         if (reason.equals(PowerManager.REBOOT_RECOVERY)) {
             // If we are rebooting to go into recovery, instead of
             // setting sys.powerctl directly we'll start the
             // pre-recovery service which will do some preparation for
             // recovery and then reboot for us.
             //
             // This preparation can take more than 20 seconds if
             // there's a very large update package, so lengthen the
             // timeout.  We have seen 750MB packages take 3-4 minutes
             SystemProperties.set("ctl.start", "pre-recovery");
             duration = 300 * 1000L;
         } else {
             SystemProperties.set("sys.powerctl", "reboot," + reason);
             duration = 20 * 1000L;
         }
         try {
             Thread.sleep(duration);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
     }

啟動uncrypt的方法是將sys.powerctl設為pre-recovery,在rc file裡,pre-recovery是一個oneshot service,它會直接執行uncrypt這個命令。uncrypt產生完map file後,它會直接reboot系統。

service pre-recovery /system/bin/uncrypt
    class main
    disabled
    oneshot

2015年4月28日 星期二

Android External Storage

Android裡有兩種external storage, 一種是模擬出來的, 它是使用internal storage的空間, 另一種是真正的external storage, 像是sdcard與usb drive. 不管是哪一種, 它們都是使用一個service透過FUSE來管理的, 這個service叫做sdcard. 為了避免與external sdcard造成混淆, 我把它叫做sdcard service, external sdcard叫做sdcard storage. 如果你的系統支援sdcard與usb drive, 通常在rc檔裡會發現這三個service

    service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated
        class late_start

    service fuse_sdcard1 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard1 /storage/sdcard1
        class late_start
        disabled
   
    service fuse_usbdrive /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/usbdrive /storage/usbdrive
        class late_start
        disabled

這三個都是sdcard service, 使用同一個執行檔。每一個負責一個external stroage, 像是sdcard service負責模擬的external storage, fuse_sdcard1負責第一個實體的sdcard, fuse_usbdrive負責usb storage.

sdcard service
這個service的source code放在$TOP/system/core/sdcard下, 啟動這個service需要幾個參數
    -u:   service執行時所擁有的user id權限
    -g:   service執行時所擁有的groupd id權限
    -w: 擁有寫入storage空間的app id
    -d:  設定為UNIFIED derive permission.
    -l:   設定為LEGACY derive permission
    source: 原始的storage的路徑
    target: app存取storage的路徑

以emulated external storage為例, user id與groupd id都設為1023, 也就是media_rw. source是在/data/media下, 而target則是在/mnt/shell/emulated.

    service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated

sdcard service是利用FUSE架構, 它會將/mnt/shell/emulated mount在/dev/fuse上, 所以任何在/mnt/shell/emulated上的IO都會交給/dev/fuse的driver, 這個driver會將這些IO request交給sdcard service, sdcard service再將這些request, 利用API去存取/data/media. 對/data/media的存取又會交給它底層的device driver去完成, 如下圖所示.



但並不是所有的系統一定要支援emulated external storage, 如果EMULATED_STORAGE_TARGET這個環境變數不存在, 代表emulated external storage不支援.

一般而言, 在rc file會有以下幾個變數設定
     export EXTERNAL_STORAGE /storage/emulated/legacy
     export EMULATED_STORAGE_SOURCE /mnt/shell/emulated
     export EMULATED_STORAGE_TARGET /storage/emulated
     export SECONDARY_STORAGE /storage/sdcard1

EMULATED_STORAGE_TARGET代表的是App看到的路徑, EMULATED_STORAGE_SOURCE是mount在/dev/fuse上的file system, 這也是Vold所看到的路徑. 現在問題來了, APP所存取的路徑如何對應到EMULATED_STORAGE_TARGET ? 答案就在Zygote裡. 在Zygote裡, 當一個child process產生出來之後, 它會將EMULATED_STORAGET_TARGET指的到位置mount到EMULATED_STORAGE_TARGET裡 (利用mount binding), 所以當這個App去存取/storage/emulated時, 相當於去存取/mnt/shell/emulated. 這中間還有一點值得特別注意, 在做mount的時候, 也會把user id的值放到路徑裡. 因為App只能存取啟動它的user目錄. 假設現在有兩個user, id分別是0與1. 兩個user都啟動同一個App, user 0所啟動的App只能存取/storage/emulated/0, user 1所啟動的App只能存取/storage/emulated/1. 所以, 當Zygote在做mount binding時, 只會根據user id來做mount, 因此, user 1啟動App時, Zygote只會mount /storage/emulated/1, 而不會mount /storage/emulated/0, 所以, 即使App故意更改路徑去存取/storage/emulated/0也不會成功.

在上圖中,user 0與user 1各啟動同一個App, Zygote會在user 0的process裡,將/storage/emulated/0 binding到/mnt/shell/emulated/0,Zygote也會在user 1的process裡,將/storage/emulated/1 binding到/mnt/shell/emulated/1裡。在上面有提到,/mnt/shell/emulated是mount在/dev/fuse上,sdcard service會polling /dev/fuse得知是否有任何對/mnt/shell/emulated的取存,如果有,會利用API從/data/media裡讀寫裡要求的檔案或目錄。

下面這段是Zygote裡截取出來的,大家可以做個參考

>>>
The default platform implementation of this feature leverages Linux kernel namespaces to create isolated mount tables for each Zygote-forked process, and then uses bind mounts to offer the correct user-specific primary external storage into that private namespace.
At boot, the system mounts a single emulated external storage FUSE daemon at EMULATED_STORAGE_SOURCE, which is hidden from apps. After the Zygote forks, it bind mounts the appropriate user-specific subdirectory from under the FUSE daemon to EMULATED_STORAGE_TARGET so that external storage paths resolve correctly for the app. Because an app lacks accessible mount points for other users' storage, they can only access storage for the user it was started as.
This implementation also uses the shared subtree kernel feature to propagate mount events from the default root namespace into app namespaces, which ensures that features like ASEC containers and OBB mounting continue working correctly. It does this by mounting the rootfs as shared, and then remounting it as slave after each Zygote namespace is created.
<<<


Emulated external storage的access要經過3層的轉換(/storage/emulated -> /mnt/shell/emulated -> /data/media),而真正的external storage (sdcard storage或usbdrive)則是只需要二層的轉換 (/storage/sdcard1 -> /mnt/media_rw/sdcard1與/storage/usbdrive -> /mnt/media_rw/usbdrive),external storage的轉換完全由sdservice一手包辦,不需要在Zygote做手腳。



2015年4月11日 星期六

Android Brightness

Brightness有兩種模式, 一種是App根據使用者或Window Manager Service(WMS)的要求去設定, 另一種則是透過Ambient Light Sensor(ALS)測得環境的亮度算出適當的Brightness值, 前者稱為手動模式, 後者又稱為自動模式. 在自動模式下, WMS/APP提供的Brightness不會被採用, PMS會根據ALS讀出的值, 算出要設定的Brightness, 最後再經由Light HAL設定到硬體裡.



上圖顯示的是WMS與APP如何通知PMS新的Brightness值. PMS提供兩個API給WMS與APP使用. 之所有用兩個API是因為優先權的問題. 如果WMS與APP同時要求改變Brightness, WMS的要求被會採用. 對APP而言, 除了setTemporaryScreenBrightnessSettingOverride之外, 其實還有一個方法是透過SettingProvider. 為何要提供兩個途徑供APP使用呢? 原因是setTemporaryScreenBrightnessSettingOverride是希望提供APP一個暫時改變Brightness的方式(像是提供Slider暫時改變Brightness讓user知道效果), 最後的設定是將值寫入SettingProvider裡的screen_brightness裡. PMS有設定Setting Observer, 所以當screen_brightness改變時, 它會收到通知, 並將screen_brightness的值反應到硬體裡. 我個人看不大出為何要提供APP兩個方式去設定Brightness, 感覺似乎只要是使用SettingProvider就好, 不過或許透過這兩種方式Brightness反應的時間有所不同吧.

不管WMS或APP所要求的Brightness是什麼, 在自動模式底下, PMS會根據ALS的值算出適當的Brightness, 並忽略WMS/APP的要求. 這種做法是否比較好, 見人見智. 或許應該要考慮將自動模式改成以user的選擇為基準, 再根據ALS的值做調整.

Code Flow
PMS會將WMS/APP的要求, 用一個DisplayPowerRequest的結構描述, 並交由DisplayManagerService (DMS)的DisplayPowerController來處置. DisplayPowerController的updatePowerState就是主要處理的function. 我們可以將updatePowerState分成兩個部份來看, 一個是screen的state update, 另一個則是brightness update. 這兩個state的update是同時進行的, 並以Animation的形態進行, 這樣user才不會覺得畫面的改變太突兀. 在updatePowerState裡, 會決定display state與brightness值, 並啟動這兩個animation, user就會慢慢看到screen state與brightness改變.

Screen State Animation
Screen state有以下四種, 分別代表不同的意義
  • STATE_ON
  • STATE_OFF
  • STATE_DOZE
  • STATE_SOZE_SUSPEND
monitor的狀態不是ON就是OFF, 突然由ON->OFF或OFF->ON會讓user覺得不夠smooth, 因此, CodeFade是一個Software的方式, 藉由改變畫面讓user覺得monitor漸漸關掉或漸漸打開. 所謂的screen state animation其實是利用另一個View (ColorFade)蓋住其它的Window, 然後一直改變Fade Level (0.0 -> 1.0或1.0 -> 0.0), 最後將monitor的打開或關掉. 這種漸漸改變的方式很適合利用Android Animation架構. animateScreenStateChange()就是用來啟動這個Animation.

Brightness Animation
一般來講, Brightness的值是介於0~255之間, 如果一個一個Level改變, user會覺得畫面亮度的改變不會太突兀, 但是user可能從80直接調到200, 因此, 當一個新的brightness值要改變時, AOSP也是利用animation的方式慢慢改變. animateScreenBrightness會觸發brightness animation.

DisplayPowerController算出screen state與brightness後, 就會啟動這兩個animation. 這兩個animation的執行沒有dependency, 但由於都利用Animation的架構, 所以當一個Animation進行一個frame時, 另一個Animation也會在很接近的時間處理下一個frame. 但這兩個animation也是有需要才會使用, 比方說, 如果只是brightness改變, 只要使用brightness animation即可, 不需要啟動screen state animation. 但 是當monitor變成OFF時, brightness會被設成0, 因此在screen off時, 這兩個animation同時會啟動.


Settings
Brightness有一些default值的設定, OEM可以利用這些設定預設的brightness值

config_screenBrightnessSettingMinimum
config_screenBrightnessDoze
config_screenBrightnessDim
config_screenBrightnessDark
config_animateScreenLights


APP可以將brightness的值或相關的設定寫入SettingProvider的database裡, 這個database位於
/data/data/com.android.providers.settings/databases/settings.db裡, 下面這3個跟brightness有關
  • screen_brightness - APP所設定的brightness值
  • screen_off_timeout - Monitor自動關掉的時間, 單位是milliseconds
  • screen_brightness_mode - 自動模式或手動模式
在Debug時, 我們可以利用adb讀取這些值, 下面可以讀取APP所設定的brightness值
adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "select value from system where name='screen_brightness';"

下面則可以寫入monitor timeout時間
adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "update system set value='600000' where name='screen_off_timeout';"

有時候Debug時, 我們也可以用下面的command來調整brightness的值, 而不用真的去操作系統.
    adb shell input keyevent KEYCODE_BRIGHTNESS_DOWN
    adb shell input keyevent KEYCODE_BRIGHTNESS_UP

要注意一點, 當我們使用上面兩個command時, SystemUI會將brightness模式改成手動模式.

FYI. 可以用下面的command打開monitor, 並將系統lock或unlock
    adb shell input keyevent 26 --> power key
    adb shell input keyevent 82 --> unlock
    adb shell input keyevent 82 --> lock

2015年3月20日 星期五

BufferQueue Address Space

Android因為Binder的關係, 使得IPC的使用變得很直覺, 但有時也會讓Debug變得令人困擾. BufferQueue是其中一種. BufferQueue通常用來做Producer與Consumer兩個Process之間的溝通. AOSP提供一些helper class簡化developer的工作. 一般而言, BufferQueue是這樣產生的

這會產生一個BufferQueue以及producer與consumer, 但是要注意的是producer與consumer是代表BufferQueue兩端使用的interface (AOSP有兩個class實作這兩個interface), 並不是代表真正的Producer與Consumer物件. 簡單的說, 真正的Producer object只能使用producer這個interface來操作BufferQueue, 而真正的Consumer object則只能使用consumer這個interface來操作BufferQueue. 它的用意是希望developer可以專注在真正Producer與Consumer的部份. 由於AOSP提供大部份的implementation, 所以Producer與Consumer的實作就可以大大簡化. 實作是簡化了, 但是Debug並不見就簡單.


一般而言, BufferQueue的產生是在Consumer的Process裡, Consumer再將producer interface傳回給Producer process, 兩者的溝通就開始了, Producer是透過Binder使用producer interface, Consumer則是與consumer helper執行在同一個process裡. 從上圖來看, 大部份的BufferQueue的code都執行在Consumer process裡, 之所以要強調這件事是因為當我們在debug的時候, 有時候會搞不清楚某些code在哪個process執行, 再加上AOSP裡, 有些class的名字其實很類似, 尤其是trace到BufferQueue以及它的helper class裡.

2015年3月15日 星期日

Factory Reset Protection

Android L MR1新增了一個功能叫做Factory Reset Protection. 這個功能是為了避免手機或平板被偷走, 然後做Factory Reset, 將機器據為己用. 這個功能概念其實很簡單, 只要想辦法得知做Factory Reset的人是否是機器的主人或被機器的主人授權即可. 問題來了, 要怎麼知道呢? 很簡單, 只要使用者能夠解除Lock Screen, 就代表他被授權, 它就能從Settings裡啟動factory reset. 這種reset稱為trusted reset.  可是系統可不是只有這條路徑可以做factory reset, 利用fastboot一樣可以將/data清除掉, 跟factory reset有一樣效果. fastboot在清除/data之前必須先做OEM unlock的動作. 因此, 必須避免未經授權的oem unlock被執行. 如何避免呢?

在MR1裡, 要啟動factory reset partition的功能, OEM必須設定一個partition, 並mount起來, 將它的路徑寫在ro.frp.pst這個property裡. Framework新增一個PersistenceDataBlockService, 用來管理這個Partition. 它的layout如下圖所示.


Byte 0~31是利用SHA-256計算的checksum, marker是一個4 byte的magic number (0x19901873), size則是payload的大小, Partition最後一個byte稱為oem unlock. 如果這個是1, 代表這個機器的主人允許fastboot是做oem unlock, 如果是0則不允許. 所以, 當使用者是fastboot裡要執行oem unlock時, OEM必須檢查這個byte, 確認它的值是1才能做oem unlock. 在Developer option裡有一個選項是允許oem unlock. 如果使用者可以解除lock screen, 進到developer option打開這個選項, 就表示oem unlock是被授權的.

另一個問題來了, 如果機器的主人忘了解除unlock screen的密碼, 而且也沒在developer option裡允許oem unlock怎麼辦? 別擔心, Google有Android Device Manager, 透過網路, 它可以鎖定你的手機或平板, 使用者可以遠端將他的機器做factory reset. 基本上要啟動這樣的功能, 使用者的機器必須裝有Google GMS的軟體, 並有Gmail的帳號登錄. 不過, 對大部份的Android 機器而言, 這應該都不是問題.

在上圖的layout中, payload的用途主要是GMS在使用的, 它如何使用不得而知. PersistenceDatatBlockService有提供interface允許GMS讀寫這塊區域, 但是不是只用GMS可以寫, OEM也可以設計APP去讀寫這一塊, 但要避免和GMS同時使用.

2015年2月24日 星期二

Android Layer && LayerStack

In SurfaceFlinger, the basic logic unit to show is a Layer (which corresponds to one ViewRootImpl in app process). The contents users see is a set of Layers with depth information. With depth info, some Layer displayed in front, while some in back, and this depth info is the z value. The larger, the nearer to user. WindowManagerService can adjust the z value through SurfaceControl.setLayer, which conveys this info to SurfaceFlinger so create different effect. For example, user can enable StrictMode violation flash in developer option, once there is violation, WMS creates a Layer, and make it visible for a while, then make it invisible. To prevent other Layer from hiding the flash effect, the z value of StrictMode Layer is adjusted so that it would be displayed on top to get attention.

The above works perfect for Layers showing on single display. When multiple displays are supported, we need a way to know which Layer to show on which display, hence the LayerStack. LayerStack is an attribute for both a Layer object and a display. When a Layer has same LayerStack as a display, it would be showed on that display. Effectively, LayerStack creates an depth space for a display, so that each display has its own depth space. One immediate use case for LayerStack is mirror mode. When a user plugin a HDMI to device, both displays shows the same content (mirror mode). WMS just set the LayerStack for HDMI display to the same value as panel, so that when SF composes contents for HDMI, it picks up same set of Layers as primary display.

Display LayerStack
In framework, a display device is described by a subclass of DisplayDevice. There are several different subclass of it, let's make it simple, using LocalDisplayDevice as an example. A LocalDisplayDevice means a physical device that can connect to the tablet or phone. Internal panel and HDMI are examples of LocalDisplayDevice. When a display is connected, framework creates an object of LocalDisplayDevice, and enumerates a LayerStack for this device (now LayerStack is same as display id, but they do not need to be the same), then using this LayerStack, it creates an associated LogicalDisplay object. One LogicalDisplay associates with one LocalDisplayDevice, and LogicalDisplay decides the LayerStack to use, then tell SurfaceFlinger through LocalDisplayDevice, LocalDisplayDevice probably can be think of an interface to SF for display related setting. So, basically if you connect HDMI on your device, you'll have 2 LogicalDisplay, each with its own LayerStack. But in mirror mode, LayerStack of both display is the same, what happened ? The trick is in DisplayManagerService.configureDisplayInTransactionLocked()

    private void configureDisplayInTransactionLocked(DisplayDevice device) {
        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
        final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;

        // Find the logical display that the display device is showing.
        // Certain displays only ever show their own content.
        LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
        if (!ownContent) {
            if (display != null && !display.hasContentLocked()) {
                // If the display does not have any content of its own, then
                // automatically mirror the default logical display contents.
                display = null;
            }
            if (display == null) {
                display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
            }
        

        }
        ....
        display.configureDisplayInTransactionLocked(device, info.state == Display.STATE_OFF);        ....

    }

When mirror mode is in use, FLAG_OWN_CONTENT_ONLY is not set for LogicalDisplay of HDMI, so the LogicalDisplay used to configure HDMI monitor is that of internal panel (see above code), that's why in that case, the LayerStack in both display in SF are the same, hence showing the same contents.

LayerStack@Layer
Layer is the concept used in SurfaceFlinger. It has different face in different places. A Layer in SF has a corresponding WindowState in WMS, and a corresponding ViewRootImpl object in app process. When a ViewRootImpl object is created, it has an associated display, and this display decides the LayerStack value to be assigned by WMS and used in SF. In WMS, each WindowState object has its own WindowStateAnimator object, which ask SF to create a Layer object for it. Below code is from WindowStateAnimator.

    SurfaceControl createSurfaceLocked() {
        ....
        // Start a new transaction and apply position & offset.
        SurfaceControl.openTransaction();
        try {

            ....
            try {

                ....
                final DisplayContent displayContent = w.getDisplayContent();
                if (displayContent != null) {           

                mSurfaceControl.setLayerStack(displayContent.getDisplay().getLayerStack());
            }

            ....
        } finally {
            SurfaceControl.closeTransaction();
        }

       }

WMS maintains a DisplayContent object to store the list of WindowState object to show in this display. From DisplayContent, it can get the display object, then get the LayerStack of it, and assign this to the Layer object in SF.

2015年2月4日 星期三

SurfaceFlinger State Update

SurfaceFlinger主要將State分成兩種, 一種是ComposerState, 另一種是DisplayState. ComposerState是跟Layer有關, DisplayState則是跟Display (Monitor)有關. 這兩種State會被儲存在SurfaceFlinger::State的物件裡.

    struct State {
        LayerVector layersSortedByZ;
        DefaultKeyedVector< wp, DisplayDeviceState> displays;
    };

layersSortedByZ儲存的是排序過的所有Layer物件, displays則是所有Display的State. layersSortedByZ是先以LayerStack排序(由小到大), 如果LayerStack相同, 再以Layer的Z做排序(由小到大).

State的變化由client的request開始, client必須透過ISurfaceComposerClient的interface與SF溝通. client可以是NDK app, 也可以是Java app. client在下command時是這樣子的, 它必須將request包在openGlobalTransaction與closeGlobalTransaction裡, 中間的每一個set function會更新ComposerState或DisplayState, 然後統一在closeGlobalTransaction時全部交給SurfaceFlinger::setTransactionState. 所以, 嚴格的來說, SurfaceFlinger::setTransactionState是SF對client的唯一interface.

    SurfaceComposerClient::openGlobalTransaction();
    surfaceControl->setXXX(...);
    surfaceControl->setYYY(...);
    SurfaceComposerClient::closeGlobalTransaction();

這裡有一個Threading Model要注意一下. SurfaceFlinger::setTransactionState是run在client所使用的binder thread. 讓我多解釋一下這句話. client與SF通常是分屬不同Process, 當client呼叫ISurfaceComposerClient的function時, 它是執行在SF的process裡, 但是這是透過binder做到的, SF裡有binder thread pool, 所以當client呼叫ISurfaceComposerClient時, binder會從SF的binder thread pool取得一個thread來執行, client本身的thread這個時候通常是被block的. 所以, SurfaceFlinger::setTransactionState是執行在SF的binder thread, 而SF本身state的update, 以至於後續的動作是在SF的main thread裡執行, 而main thread通常是在VSYNC-sf開始的時候才執行, 下面這張圖或許可以讓你比較清楚一點.


在VSYNC-sf發生時, SF的main thread會讀取內部的Layer與DisplayDeviceState物件, 為了避免SF在讀的時候, client正在更新state, SurfaceFlinger::mStateLock會用來避免這件事.

SurfaceFlinger裡有好幾個thread, 其中一個是main thread, 也就是SurfaceFlinger主要執行的thread, 另外有兩個VSYNC thread, 一個用來產生VSYNC-sf, 另一個產生VSYNC-app, 在這裡我們主要著重在VSYNC-sf, 簡單稱為VSYNC-sf thread. VSYNC-sf thread的requestNextVSync必須被呼叫, VSYNC-sf thread才會在VSYNC-sf發生時, queue一個INVALIDATE的message給main thread, main thread才會被叫醒, SF的state才會更新, 反應在使用者面前.


VSYNC-sf的requestNextVsync是在MessageQueue::invalidate()中被呼叫到的, Client (SF Client, 透過ISurfaceComposerClient)或main thread是使用setTransactionFlags()-->signalTransaction()-->MessageQueue::invalidate()這條路徑.

Double State Tracking
SurfaceFlinger有兩個State物件, 分別是mCurrentState與mDrawingState. mCurrentState指的是新要求的State, 但還沒反應在使用者面前, mDrawingState則是指目前已經反應在使用者面前. 但是在某個時間點(commitTransaction), mCurrentState的內容會被複製到mDrawingState. 利用這兩個State, 就可以知道有什麼需要更新. 除了SF本身外, 每一個Layer也有mCurrentState與mDrawingState, 具有相同的意義.

Layer
Layer代表Activity裡一個ViewRootImpl的畫面, 每一個ViewRootImp都有相對應的Layer, Android的畫面就是由多個Layer所組合而成, 這些Layer有前後關係(用Z值作區分), 因此, 有些Layer會蓋在某些Layer之上, 有些Layer是透明或半透明, SF將這些Layer組成一個畫面顯示在使用者面前.

當一個Layer產生時, 它會產生一個handle, 這個Handle是要傳回給client, 當client要調整這個Layer屬性時, 需要將這個handle傳到SF, SF才有辦法找到相對的Layer物件. ($TOP/frameworks/base/core/java/android/view/SurfaceControl.java). handle是一個簡單BBinder物件


當這個handle被destroy後, LayerCleaner::~LayerCleaner()會被執行, 再去呼叫SurfaceFlinger::onLayerDestroyed()做後續處理.

每一個ViewRootImp的畫面都是由client所繪製的, SF只是負責將它組合顯示在使用者面前, 因此, 一個Layer產生後第一次被使用時, SF會產生一個BufferQueue, Producer是MonitorProducer物件, Consumer則是SurfaceFlingerConsumer物件, Producer的interface會傳回給client, client可以利用producer interface將畫好的GraphicBuffer放入BQ, 然後由SF使用.

    void Layer::onFirstRef() {
        ...
        // Creates a custom BufferQueue for SurfaceFlingerConsumer to use
        sp producer;
        sp consumer;
        BufferQueue::createBufferQueue(&producer, &consumer);
        mProducer = new MonitoredProducer(producer, mFlinger);
        mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName);
        mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
        mSurfaceFlingerConsumer->setContentsChangedListener(this);
        mSurfaceFlingerConsumer->setName(mName);
        ....
    }

MonitoredProducer
MonitoredProducer是一個很簡單的Pass-Through class, 所有的Producer interface都是直接呼叫createBufferQueue()所產生的producer object. 它有一個cleanup的功能, 當MonitoredProducer被destroy時, 它會post一個message給SF去做clean的工作, 其實只是將Producer從SurfaceFlinger::mGraphicBufferProducerList中移除而已.

SurfaceFlingerConsumer
SurfaceFlingerConsumer是GLConsumer的subclass, 這個consumer的用途可以將MonitoredProducer所放入的GraphicBuffer取出當做Texture使用, 當它的updateTexImage()被呼叫時, 它會從BQ裡取出Buffer供SurfaceFlinger::RenderEngine使用. 一般而言, 當Producer將Buffer放入BQ時, Consumer的onFrameAvailable()會被執行, 但是在上面的code裡有一行

    mSurfaceFlingerConsumer->setContentsChangedListener(this);

這一行會使得Layer::onFrameAvailable()被執行, 而不是SurfaceFlingerConsumer::onFrameAvailable. 當Layer::onFrameAvailable()被執行時, 會對Layer::mQueuedFrames累加, 這個變數代表這個Layer有幾個畫面已經更新, 但還沒被SF處理.

    void Layer::onFrameAvailable() {
        android_atomic_inc(&mQueuedFrames);
        mFlinger->signalLayerUpdate();
    }

SurfaceFlinger::signalLayerUpdate()會通知在下一個VSYNC-sf時, 將SF的main thread叫起來更新畫面, 稍後會看到.

ComposerState
ComposerState包含client的pointer與一layer_state_t的結構. layer_state_t::surface存的是Layer的handle, 也就是上面提到, 當Layer被create時, 它會產生一個Handle物件傳回給client. Client要對這個Layer進行操作必須將這個handle傳回SF, SF會利用Client::getLayerUser()從client所有的layer裡找出來. 一個client可以有多個layer, 最常見的case就是一個Activity叫出Dialog, 這個Dialog也是一個Layer, 因此這個client就有2個layer.

    struct ComposerState {
        sp client;
        layer_state_t state;
        status_t    write(Parcel& output) const;
        status_t    read(const Parcel& input);
    };


ComposerState裡主要的是layer_state_t, 這個class會攜帶client所要改變的屬性. layer_state_t是一個public interface, 用來做為client與SF的溝通, SF有自己的data structure (Layer class), 因此, 當SF收到新的ComposerClient (經由setTransactionState), 會將它的內容反應在Layer物件裡, SurfaceFlinger::setClientStateLocked()負責將layer_state_t轉換到Layer, 然後在VSYNC開始時, 才由SurfaceFlinger::handleTransaction()做進一步的處理.

Display
在SurfaceFlinger裡, Display可以分成兩種, 一種是Physical Display, 也就是一個實體的Monitor或Panel接在機器上, 另一種是Virtual Display, 像是WifiDisplay或Chromecast, 它不是一個實體的Monitor連接著你的機器.

SurfaceFlinger利用幾個data structure來管理Display.

SurfaceFlinger::mBuiltinDisplays
這是一個table, 每個entry都是一個BBinder物件, 每一個BBinder代表一個Physical Display的Token, Virtual Display的Token不會存在這裡.

SurfaceFlinger::State::displays
這是一個mapping table, 利用Display的Token, 可以取得該Display的DisplayDeviceState, 所有的Display的DisplayDeviceState (包含Virtual Display)都可以從這裡取得. DisplayDeviceState記錄該Display的狀態, 它的內容是根據client傳進來的DisplayState而設定的.

SurfaceFlinger::mDisplays
這也是一個mapping table, 利用Display的Token, 可以取得該Display的DisplayDevice物件.


在SF裡, 每一個Display, 不管是Physical或Virtual都有一個Token, 可以把它想成是個ID用來辨識不同的Display. Physical Display的Token是BBinder class, Virtual Display則是DisplayToken class. DisplayToken是BBinder的subclass, 所以它也可以以BBinder的形態儲存.

Token只是一個類似ID的作用, 真正在記錄或處理Display的則是一個叫DisplayDevice的class. SurfaceFlinger::mDisplays記錄著每一個Display(包含Virtual)的DisplayDevice物件, 利用Display的Token可以找到相對應的DisplayDevice.

DisplayState
DisplayState是client跟SF溝通的一個結構, SF會將這個結構的內容反應在每個Display的DisplayDeviceState裡, 當SF main thread執行時, 它會比較mCurrentState與mDrawingState來決定是否有display hot plug/unplug發生, 也會更新一些屬性, 像是width, height, projection matrix等等.


DisplayDevice
不管前面的Flow如何, SF最終要將相關的State更新到DisplayDevice物件裡. 針對每一個Display, 除了會有一個DisplayDevice外, SF還會為每一個Display產生一個BufferQueue. DisplayDevice物件在被create時, 會利用eglCreateWindowSurface產生一個EGLSurface當作BQ的Producer. Physical與Virtual Display在這方面有點不同, 請見下圖.



Producer是EGLSurface, 因為它主要是用在GLES composition. EGLContext是使用SurfaceFlinger的RenderEngine.

DisplayDevice比較像是一個PlaceHolder放Display相關的資料, SF在稍後更新Layer或HWComposer的狀態時, 會用到裡面的資料.

Wrap Up
在看SF的code, 最好能掌握一個原則, SF的main thread才是主導State的關鍵, 而且它只有在VSYNC-sf發生時才會被叫醒, 其餘時間它是在sleep的狀態. 另外, client主要是透過setTransactionState將client state (ComposerState && DisplayState)轉換成SF內部的state (Layer && DisplayDeviceState). SF main thread只從內部的state來比較是否有任何新的request, 然後才去處理. 它的流程大概是像下面這個樣子

  • Client (binder thread)呼叫setTransactionState傳入ComposerState與DisplayState, setTransactionState會做幾件事
    • 利用setClientStateLocked()與setDisplayStateLocked()將ComposerState與DisplayState反應在Layer與DisplayDeviceState裡.
    • 利用setTransactionFlag將SF main thread叫醒.
    • 如果client要求synchronous mode, setTransactionState就會一直等到main thread將state處理完才return.因此, client使用的binder thread會被block住直到main thread執行完handleTranslactionLocked.
  • SF main thread被叫醒後, 會利用handleTransactionLocked()檢查各個Layer與DisplayDeviceState. 接下來我們會看handleTransactionLocked()

handleTransactionLocked()
這個function看起來似乎比較長, 但實際上它的邏輯不複雜, 我們可以將它分成幾個部份來看

Layer Update
檢查每一個Layer是否有任何的更新, 如果有, 就利用Layer::doTransaction()對該Layer做更新.



Layer::doTransaction()跟handleTransactionLocked()的意義是一樣的, 差別在於doTranaction是對Layer做state的檢查, 但handleTransactionLocked()是針對整個SF做state的檢查. 把它想成一層一層的管理, 就會比較容易理解.

Display Update
在這裡會比較mCurrentState與mDrawingState裡的displays, 從這兩個State裡display的不同, 可以得知是否有hot plug/unplug的發生.
  • 如果一個Display在mDrawingState.displays, 但不在mCurrentState.displays, 代表該Display發生unplug. 需要呼叫EventThread::onHotplugReceived()通知其它component有display unplug.並將DisplayDevice物件從SurfaceFlinger::mDisplays中移除.
  • 如果一個Display在mCurrentState.displays, 但不在mDrawingState.displays裡, 代表該Display發生hot plug. 除了要利用EventThread::onHotplugReceived()通知其它componenet外, 還必須產生一個新DisplayDevice放到SurfaceFlinger::mDisplays裡. 而且必須為這個Display產生BufferQueue, 並更新DisplayDevice的一些屬性, 像是layerStack, projection matrix等.
  • 如果一個Display同時出現在mCurrentState.displays與mDrawingState.displays, 表示這個Display可能有些屬性改變, 像LayerStack, projection matrix等.

Dirty Region Update
由於Activity可能會結束, 因此Layer也會被刪除, 這有可能會影響到畫面上可看見的區域. 透過mCurrentState與mDrawingState的比較, 可以知道哪些Layer被刪除了, 這些被刪除的Layer的顯示區域必須設成Dirty, 要重畫.

上面紅色區域就是用來計算被移掉的Layer的可見區域, 然後交給invalidateLayerStack()來更DisplayDevice裡的dirty region. 如果我們看一下invalidateLayerStack(), 它會檢查被移除Layer的LayerStack是否與DisplayDevice的LayerStack相同, 才會更新DisplayDevice的dirty region. LayerStack是用來決定一個Layer是否要在一個Display上顯示出來. 我們會再另外討論這個東西.


在handleTransactionLocked()的最後會有一個commitTransaction()的動作, 這個很重要, 因為它會將mCurrentState的內容複製到mDrawingState裡, 這代表在這之後, 如果要知道哪些State有更新, 要去參考mDrawingState, 而不是mCurrentState. 因為從這之後, mCurrentState就會繼續接收client新的state update.

2015年1月28日 星期三

DispSync

如果你曾經打開過systrace的檔案並觀察SurfaceFlinger的process, 你可能看過下圖中用紅色標起來的區域. 這四個代表的是VSync相關的information.


HW_VSYNC_ON_0代表Primary Display的VSync被enable或disable. 0這個數字代表的是display的編號, 0是Primary Display, 如果是External monitor, 就會是HW_VSYNC_ON_1. 當SF要求HWComposer將Display的VSync打開或關掉時, 這個event就會記錄下來.

HW_VSYNC_0代表Primary Display的VSync發生時間點, 0代表display編號. 以下圖為例, A, B, C, D分別代表VSync發生的時間, 這個event並沒有記錄VSync結束的時間, 所以, 不能把它解讀為在A時間點VSync開始, 然後在B結束, C開始, D結束. 正確的解讀是是A-B代表的是一個frame, A是VSync發生點, B是下一個VSync開始的時間. 如果refresh rate是60Hz的話, A-B, B-C, C-D的長度應該在16.6ms左右 (會有latency), 之所以有0與1的變化是為了讓人比較容易讀, 但有時候也會造成解讀錯誤.


通常為了避免Tearing的現象, 畫面更新(Flip)的動作通常會在VSync開始的時候才做, 因為在VSync開始到它結束前, Display不會抓framebuffer資料顯示在display上, 所以在這段時間做Flip可以避免使用者同時看到前後兩個部份畫面的現象.

目前user看到畫面呈現的過程是這樣子的, app更新它的畫面後, 它需要透過BufferQueue通知SF, SF再將更新過的app畫面與其它的App或Status Bar/Navigation Bar組合後, 再顯示在使用者面前. 在這個過程裡, 有3個component牽涉進來, 分別是App, SF, 與Display. 以目前Android的設計, 這三個Component都是在VSync發生的時候才開始做事. 我們將它們想成一個有3個stage的pipeline, 這個pipeline的clock就是HW_VSYNC_0.
  1. T = 0時, App正在畫N, SF與Display都沒資料可用
  2. T = 1時, App正在畫N+1, SF組合N, Display沒Buffer可顯示
  3. T = 2時, App正在畫N+2, SF組合N+1, Display顯示N
  4. T = 3時, App正在畫N, SF組合N+2, Display顯示N+1
  5. ...

如果按照這個步驟, 當user改變一個畫面時, 要等到2個VSync後, 畫面才會顯示在user面前, latency大概是33ms (2個frame的時間). 但是對大部份的操作來講, 可能app加SF畫的時間一個frame (16.6ms)就可以完成. 因此, Android就從HW_VSYNC_0才產生出兩個VSync, VSYNC-app是給App用的, VSYNC-sf是給SF用的, Display則是使用HW_VSYNC_0. VSYNC-app與VSYNC-sf可以分別給定一個phase, 簡單的說
  • VSYNC-app = HW_VSYNC_0 + phase_app
  • VSYNC-sf = HW_VSYNC_0 + phase_sf


也就是說, 如果phase_app與phase_sf設定的好的話, 可能大部份user使用的狀況, App+SF可以在一個frame裡完成, 然後在下一個HW_VSYNC_0來的時候, 顯示在display上.
理論上透過VSYNC-sf與VSYNC-app的確是可以有機會在一個frame裡把App+SF做完, 但是實際上不是一定可以在一個frame裡完成. 因為有可能因為CPU被搶走, 或memory不夠, 以致App或SF沒辦法及時做完. 如果我們觀察systrace可以發現不是每次Render的時間都很穩定. 下圖是systrace裡Launcher3在畫一個frame所花的時間, 從每一段的長度可以看出時間其實每個frame有某種程度的變動. 但是除非有不正常的狀況, 不然其實變動的範圍不會太大.



如果不能在一個frame裡完成, 就會變成latency可能會有比較大的變化, 一旦latency不是很穩定, 對人的視覺系統而言, 就容易感覺不舒服, 可能比delay兩個VSync還差. 所以在調整這個參數時, 只要能將latency維持在一個固定的範圍, 應該可以改善user experience, 比方說將latency調整到1.5個frame.

如果你想使用這個功能, 只要在Makefile裡設定VSYNC_EVENT_PHASE_OFFSET_NS和SF_VSYNC_EVENT_PHASE_OFFSET_NS就可以了. Google是建議放在BoardConfig.mk裡. 前者代表VSYNC-app, 後者代表VSYNC-sf.

這樣子看起來在SF裡, 只要收到硬體VSYNC, 再等一些offset, 就可以觸發VSYNC-app與VSYNC-sf了. 但實際並不是這樣. 除非使用者在玩game或播影片, 在一般使用情況下, 畫面並不會一直更新, 比方我們在看一篇文章, 大部份時間, 畫面是維持一樣的, 如果我們在每個硬體VSYNC來的時候都把SF或APP叫起來, 就會浪費時間與增加Power的使用. 所以在Android裡, 通常SF與APP都會利用requestNextVsync()來告訴系統我要在下一個VSYNC被叫起來做事情. 也就是說, 雖然系統可能每秒有60個硬體VSYNC, 但不代表Software每個VSYNC都要做事情. 因此, 在Android裡, 我們是根據Software VSYNC來做事. Software VSYNC是根據硬體VSYNC過去發生的時間, 推測未來會發生的時間. 因此, 當APP或SF利用requestNextVsync時, Software VSYNC才會觸發VSYNC-sf或VSYNC-app. DispSync這個class就是Android使用的Software VSYNC.

底下是SurfaceFlinger裡EventThread::requestNextVsync(), 做的事很簡單, 只是檢查connection的counter, 小於0就將它設成0, 代表只要下一個VSYNC通知它就好 (one-shot)
     void EventThread::requestNextVsync(
             const sp& connection) {
         Mutex::Autolock _l(mLock);
         if (connection->count < 0) {
             connection->count = 0;
             mCondition.broadcast();
         }
     }

因為它connection的counter >= 0時, EventThread的threadLoop就會認為這個connection需要SW_VSYNC, 相關的code可以參考EventThread::waitForEvent()
     Vector< sp > EventThread::waitForEvent(
             DisplayEventReceiver::Event* event)
     {
         ...
         do {
             size_t count = mDisplayEventConnections.size();
             for (size_t i=0; i
                 if (connection != NULL) {
                     bool added = false;
                     if (connection->count >= 0) {
                         waitForVSync = true;
                         ...
                         if (connection->count == 0) {
                             connection->count = -1;
                             signalConnections.add(connection);
                             added = true;
                         }
                         ...
                     }
                 }
             }
         } while (signalConnections.isEmpty());
     }


我們可以用下圖來了解DispSync的架構, DispSync像是一個PLL, 它的input是硬體VSYNC的時間, 它的feedback是Present Fence的時間, 它的輸出就是SW_VSYNC, SW_VSYNC再根據SF與APP的phase offset做調整, 分別輸出VSYNC-sf與VSYNC-app.


當SF從HWComposer收到VSYNC時, 它會利用DispSync::addResyncSample將新的VSYNC時間交給DispSync. addResyncSample會決定是否還需要HW_VSYNC的輸入, 如果不需要, 就會將硬體VSYNC關掉.

     void SurfaceFlinger::onVSyncReceived(int type, nsecs_t timestamp) {
         bool needsHwVsync = false;

         { // Scope for the lock
             Mutex::Autolock _l(mHWVsyncLock);
             if (type == 0 && mPrimaryHWVsyncEnabled) {
                 needsHwVsync = mPrimaryDispSync.addResyncSample(timestamp);
             }
         }

         if (needsHwVsync) {
             enableHardwareVsync();
         } else {
             disableHardwareVsync(false);
         }
     }

在SurfaceFlinger::postComposition()裡, 會將Present Fence的時間透過addPresentFence交給DispSync, 這用來檢查SW_VSYNC是否需要校正, 如果需要, 就會將硬體VSYNC打開.
     void SurfaceFlinger::postComposition()
     {
         ....
         const HWComposer& hwc = getHwComposer();
         sp presentFence = hwc.getDisplayFence(HWC_DISPLAY_PRIMARY);

         if (presentFence->isValid()) {
             if (mPrimaryDispSync.addPresentFence(presentFence)) {
                 enableHardwareVsync();
             } else {
                 disableHardwareVsync(false);
             }
         }
         ...
     }

DispSync是利用最近的硬體VSYNC來做預測, 最少要3個, 最多是32個, 實際上要用幾個則不一定, DispSync拿到3個VSYNC後就會計算出SW_VSYNC, 只要收到的Present Fence沒有超過誤差, 硬體VSYNC就會關掉, 不然會繼續接收硬體VSYNC計算SW_VSYNC的值, 直到誤差小於threshold. 它計算的Model是這樣的 (DispSync::updateModelLocked)

  1. 計算目前收到VSYNC間隔, 取平均值 (AvgPeriod)
  2. 將每個收到的VSYNC時間與AvgPeriod算出誤差. (Delta = Time % AvgPeriod)
  3. 將Delta轉換成角度(DeltaPhase), 如果AvgPeriod是360度, DeltaPhase = 2*PI*Delta/AvgPeriod.
  4. 從DeltaPhase可以得到DeltaX與DeltaY (DeltaX = cos(DeltaPhase), DeltaY = sin(DeltaPhase))
  5. 將每個收到的VSYNC的DeltaX與DeltaY取平均, 可以得到AvgX與AvgY
  6. 利用atan與AvgX, AvgY可以得到平圴的phase (AvgPhase)
  7. AvgPeriod + AvgPhase就是SW_VSYNC.


當DispSync收到addPresentFence時 (最多記錄8個sample), 每一個fence的時間算出 (Time % AvgPeriod)的平方當作誤差, 將所有的Fence誤差加總起來如果大於某個Threshold, 就表示需要校正 (DispSync::updateErrorLocked). 校正的方法是呼叫DispSync::beginResync()將所有的VSYNC清掉, 等至少3個VSYNC再重新計算.










2015年1月23日 星期五

BufferQueue

Producer與Consumer是一個常見的design pattern, Producer負責產生資料, Consumer負責利用產生好的資料做進一步的處理. 很常見的一種case是video codec做bitstream的decode, decode完的資料利用3D(像是OpenGL或Direct3D)顯示出來, 這種use case在Windows與Android都可以看的到. BufferQueue是Android針對Producer/Consumer的一種Implementation. 在Android裡, 還有另一種是每個使用者都會看到的, 當電源打開時, 每個App, 甚至Status Bar或Navigation Bar都是透過BufferQueue將它所畫的東西(放在GraphicBuffer)傳送到SurfaceFlinger, SF再將它組合到使用者面前. Producer與Consumer可以在同一個process裡, 也可以在不同process裡, BQ這個設計基本可以同時使用在這兩個case裡, 而且Producer與Consumer並不需要知道對方是否與它是同一個process, 因為Android Binder搞定這件事.


Basic Use Model

BQ一開始的use model很直接, Producer利用dequeueBuffer從BQ要一塊GraphicBuffer, 資料寫入後, 利用queueBuffer將該Buffer放進BQ. Consumer需要資料時, 利用acquireBuffer從BQ取後一塊GraphicBuffer做處理, 處理完後, 利用releaseBuffer將該Buffer還給BQ.

更進一步想, Producer與Consumer兩個產生與消耗Buffer的速度不一樣, 而且Buffer的數量也是有限, 不可能需要就Allocate, 因此有些限制就出現. 因此當Producer產生資料的速度比較快時, 就會遇到全部的Buffer都寫完資料, 但是Consumer還沒消耗完任何一個, 正常的情況Producer必須等Consumer釋放出Buffer才能繼續做事. 如果是Consumer比較消耗速度比較快, Consumer就必須等Producer. 我們可以用下面的圖來說明BQ基本的use model.




每一個GraphicBuffer在BQ裡都有一個Slot用來儲存該Buffer的位置, 而每一個Slot都有一個state, state有以下4種
  • FREE
這個Slot是沒有在使用, Producer可以利用dequeueBuffer取得該Slot的GraphicBuffer, 一旦dequeueBuffer成功, 該Slot的狀態會變成DEQUEUED.
  • DEQUEUED
Slot裡面的Buffer已經交給Producer, 當Producer使用完後, 它會利用queueBuffer將該Buffer還給BQ, 而該Slot的狀態會變成QUEUED. 但是Producer也可以不使用這塊Buffer, 利用cancelBuffer將該Buffer還給BQ, 但該Slot的狀態會設成FREE.
  • QUEUED
在這個狀態下, BQ擁有這個Slot與Buffer, 只有Consumer可以利用acquireBuffer取得這個Slot與Buffer的所有權, 然後使用它. 當Consumer取得所有權後, 狀態會變成ACQUIRED.
  • ACQUIRED
Consumer用完這個Buffer後, 必須將它還給BQ, 稍後Producer才能再利用這塊Buffer寫入資料. Consumer可以利用releaseBuffer將它的所有權交還給BQ, 它的狀態也會變成FREE供Producer稍後使用.
 
 

Sync Model

Basic Sync Model的運作有一個問題, 也就是當Producer或Consumer將GraphicBuffer還給BQ時, 必須確保Buffer已經處理完了, 但是其實這會造成performance的問題. 以GLES(OpenGL ES)當作Producer為例, CPU下完GLES command後, 不代表GPU完成了所有的動作, 從CPU端, 我們可以利用glFinish或其它方式等GPU做完才利用queueBuffer將Buffer交給BQ, 但這其實是在浪費CPU的resource, 我們可以延後這個等待的動作, 延後到Consumer真的拿到這塊Buffer時再做. 對Consumer也是一樣, 當Consumer利用releaseBuffer將Buffer還給BQ時, 它不用等這塊Buffer真的沒在用, 它可以先還給BQ, 當Producer拿到Buffer時再等待Buffer真的沒有使用後再寫入新的資料. Sync Model利用fence來做到這個功能. 在sync model裡, 每一個Slot都有一個fence物件, 當一個Slot的狀態是QUEUED時, 這個fence可以稱為acquire fence, 當它的狀態是FREE時, 可稱為release fence. Acquire fence指的是當Consumer利用acquireBuffer取得Buffer時, 需要等待fence完成才能使用, 而release fence指的是當Producer利用dequeueBuffer取得Buffer時, 需要等待fence完成才能開始寫入資料.



Attach/Detach Model

在現今的BQ interface裡, 我們可以觀察到有多出幾個function, 分別是attachBuffer, detachBuffer, 這兩個function不論在Producer或Consumer都可以觀察到. attachBuffer會從BQ裡找到一個FREE的Slot, 並與Producer或Consumer提供的GraphicBuffer聯結. 這與一般的dequeueBuffer不同, 在dequeueBuffer時, 如果FREE的Slot本身沒有GraphicBuffer的話, dequeuBuffer會利用allocator去產生, 但是attachBuffer則是Producer/Consumer提供一個已經存在的Buffer, 它的目的是要將一個GraphicBuffer放入BQ裡, 這個GraphicBuffer可以是其它BQ所產生, 也可以是Producer/Consumer利用allocator產生的. AOSP裡有一個StreamSplitter, 將接收到的GraphicBuffer, 交給多個Consumer去使用. StreamSplitter本身是一個Consumer, 但同時也是一個Producer. 當Consumer被通知有新的Buffer放入BQ時, 它會做以下的事

    mInput->acquireBuffer(&acquiredBuffer);
    mInput->detachBuffer(acquiredBufer.mBuf);

    foreach producer in all downstream BQ {
        output->attachBuffer(&slot, acquiredBuffer.mGraphicBuffer);
        output->queueBuffer(slot)
    }
mInput就是StreamSplitter的Consumer端, 利用acquireBuffer取得可使用的Buffer後, 而利用detachBuffer將它從upstream BQ移除(Slot的狀態會從ACQUIRED變成FREE), upstream BQ就沒這個GraphicBuffer的存在. StreamSplitter再針對每個downstream BQ的producer端, 將取得的GraphicBuffer利用attachBuffer放入BQ裡的一個Slot, 這時候該Slot的狀態會從FREE變成ACQUIRED, 再利用queueBuffer放入downstream BQ裡. 利用attachBuffer/detachBuffer就可以簡單的將一個GraphicBuffer提供給多個BQ使用.

BQ Sample In SF


下面這張圖是SF裡BQ的使用情形. 在Android裡, 每一個ViewRootImpl代表的是一個Activity畫面,畫在一個GraphicBuffer裡, 這個GraphicBuffer並不是由ViewRootImpl直接管理的. 每一個ViewRootImpl都有一個WindowStateAnimator物件, 負責本身畫面的動畫, 而這個GraphicBuffer就是由WindowStateAnimator負責管理的. WindowStateAnimator會透過SurfaceComposerClient與SF溝通, 要求SF產生一個layer, 每個layer會產生一個BQ, SF端是Consumer, WindowStateAnimtor端則是Producer. 每一個ViewRootImpl都透過自已的BQ將畫完的GraphicBuffer交給SF.



Android的每個monitor都會有一個EGLSurface, 這個EGLSurface是一個GraphicBuffer的Producer, SF會記錄每個monitor要顯示的Layer, 將每一個monitor的Layer做Composition後(可以用GLES或overlay), 通過BQ交給FramebufferSurface, FramebufferSurface收到新的畫面, 再通知HWComposer顯示在使用者面前.


GraphicBuffer

GraphicBuffer用來儲存Producer與Consumer之間要交換的資料, 它是ANativeWindowBuffer的subclass, 記錄著由gralloc HAL傳回的handle. gralloc HAL負責從Graphic Memory裡分配一塊記憶體, 傳回的handle代表這塊memory. Handle的type是native_handle_t, 基本上這個type是很多class的base class.
    typedef struct
    {
        int version;
        int numFds;
        int numInts;
        int data[0];
    } native_handle_t;

ANativeWindowBuffer記錄Buffer基本的屬性, 像是width, height, stride, format等, 最重要的是它記錄著一個native_handle_t的pointer.

    typedef struct ANativeWindowBuffer
{
        struct android_native_base_t common;    // for reference counting

        int width;
        int height;
        int stride;
        int format;
        int usage;

        void* reserved[2];
    
        buffer_handle_t handle;    // alias of native_handle_t
        ...
    }

而GraphicBuffer則是繼承自ANativeWindowBuffer與RefBase的class, 它除了簡化ANativeWindowBuffer的使用外, 也提供了對gralloc HAL的interface, 像是alloc, free與lockXXX/unlockXXX等等.

GraphicBuffer用了兩個helper class來使用gralloc HAL, 分別是GraphicBufferAllocator與GraphicBufferMapper. Allocator負責使用HAL的alloc/free, Mapper則是使用lockXXX/unlockXXX.

BufferSlot

在BQ裡, 最多可以有64個Slot, 每個Slot有自已的State, 分別是FREE, QUEUED, DEQUEUED, ACQURED. 這些State是跟著Slot, 而不是GraphicBuffer. 當Consumer或Producer透過acquireBuffer或dequeueBuffer取得一個GraphicBuffer時, BQ也會提供一個Slot Index, 這個時候Slot會有相對應的GraphicBuffer, 當releaseBuffer, cancelBuffer或queueBuffer將Slot還給BQ時, GraphicBuffer也是跟著BufferSlot還給BQ. 但是利用attachBuffer/detachBuffer, 我們將跟Slot與GraphicBuffer的聯連移除. detachBuffer將GrahicBuffer從Slot中移除, Slot的狀態會變成FREE, 但下次這個Slot被dequeueBuffer選上時, 會再allocate一個GraphicBuffer. 被detach的GraphicBuffer可以繼續使用.


BufferQueue Creation

AOSP提供了一個Helper class來幫助Programmer寫Producer與Consumer, 在開始研究這些Helper Class之前, 我們可以先了解一下如何使用. BufferQueue::createBufferQueue是一個static function, 它會產生BufferQueueConsumer與BufferQueueProducer的object, 並以IGraphicBufferConsumer與IGraphicBufferProducer的type傳回. BufferQueueConsumer/Producer也是Helper class, 用來簡化Programmer的effort.

    sp producer;
    sp consumer;
    BufferQueue::createBufferQueue(&producer, &consumer, 
        new GraphicBufferAlloc());
    myConsumer c = new myConsumer(customer, ...);
    myProducer p = new myProducer(producer, ...);

要注意的是, BufferQueueConsumer/Producer的用意並不是要我們去寫它們兩個的subclass, 而是用它們. 對Consumer而言, AOSP還提供ConsumerBase與GLConsumer可以使用, 一般來講, Consumer會繼承自這個這兩個Class. GLConsumer是ConsumerBase的subclass, 它可以將acqure的GraphicBuffer當作GLES的texture使用.