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做手腳。



1 則留言:

  1. "它會將EMULATED_STORAGET_TARGET指的到位置mount到EMULATED_STORAGE_TARGET裡.. "
    我想這句是要說: 它會將 EMULATED_STORAGE_TARGET 所指的位置 bind mount 到 EMULATED_STORAGE_SOURCE 所指的位置

    回覆刪除