android下存儲設備的使用

本文轉自android官網,原文地址:https://source.android.google.cn/devices/storage

1、概覽

1.1、存儲

Android 外部存儲設備 HAL 圖標

Android 一直在不斷髮展,可支持各種存儲設備類型和功能。所有 Android 版本均支持配有傳統存儲(包括便攜式存儲和內置存儲)的設備。便攜式存儲是指物理介質(如 SD 卡或 USB 設備),用於進行臨時數據傳輸/文件存儲。物理介質可以隨設備一起保留更長時間,但並非固定在設備上,可以移除。自 Android 1.0 開始,SD 卡已可用作便攜式存儲;Android 6.0 增加對 USB 的支持。“內置”存儲可通過將部分內部存儲暴露於模擬層來實現存儲,並且從 Android 3.0 開始便已支持此功能。

從 Android 6.0 開始,Android 支持可合併的存儲設備,這種存儲設備是指可以像內部存儲設備那樣進行加密和格式化的物理介質(例如 SD 卡或 USB 設備)。移動存儲設備可存儲各類應用數據。

1.2、權限

採用各種 Android 權限保護對外部存儲設備的訪問。從 Android 1.0 開始,採用 WRITE_EXTERNAL_STORAGE 權限保護寫入訪問。從 Android 4.1 開始,採用 READ_EXTERNAL_STORAGE 權限保護讀取訪問。

從 Android 4.4 開始,外部存儲設備上的文件所有者、組和模式根據目錄結構合成。這樣,應用可在外部存儲設備上管理其特定文件包的目錄,而無需獲得廣泛的 WRITE_EXTERNAL_STORAGE 權限。例如,文件包名稱爲 com.example.foo 的應用現在可以自由訪問外部存儲設備上的 Android/data/com.example.foo/,沒有權限限制。通過將原始存儲設備封裝在 FUSE 守護進程中,可實現此類合成權限。

1.3、運行時權限

Android 6.0 引入了一種新的運行時權限模式,在該模式中,應用可在運行時根據需要請求功能。由於新模式包含 READ/WRITE_EXTERNAL_STORAGE 權限,因此平臺需要動態授予存儲訪問權限,而不會終止或重新啓動已運行的應用。通過維護所有安裝存儲設備的三個不同視圖可實現該模式:

  • /mnt/runtime/default 是向無特殊存儲權限的應用以及 adbd 和其他系統組件所在的根命名空間顯示。
  • /mnt/runtime/read 是向具有 READ_EXTERNAL_STORAGE 的應用顯示
  • /mnt/runtime/write 是向具有 WRITE_EXTERNAL_STORAGE 的應用顯示

在 Zygote 進行 fork 操作時,我們會爲各運行應用創建裝載命名空間,並將相應的初始視圖掛載到位。稍後,當授予運行時權限時,vold 將跳轉到已運行應用的裝載命名空間,並將升級後的視圖掛載到位。請注意,權限降級定會導致應用被終止。

用於實現此特性的 setns() 功能至少需要運行 Linux 3.8,但補丁程序已反向移植至 Linux 3.4。PermissionsHostTestCTS 測試可用於驗證內核行爲是否正確。

在 Android 6.0 中,第三方應用無權訪問 sdcard_r 和 sdcard_rw GID。相反,訪問通過僅爲該應用裝載適當的運行時視圖來控制。系統會使用 everybody GID 來阻止用戶間交互。

 

2、傳統存儲設備

Android 支持採用傳統存儲的設備,它被定義爲具有不可變 POSIX 權限類和模式且不區分大小寫的文件系統。傳統存儲設備的概念包括模擬和便攜式存儲設備。便攜式存儲設備指的是系統未合併的任何外部存儲設備,因此,未經格式化、加密或綁定到特定設備。由於傳統的外部存儲設備對存儲的數據提供最低限度的保護,因此係統代碼不應將敏感數據存儲到外部存儲設備中。具體來說,只能將配置和日誌文件存儲到可爲其提供妥善保護的內部存儲設備中。

2.1、多用戶外部存儲設備

從 Android 4.2 開始,設備可以支持多用戶,且外部存儲設備必須滿足以下限制條件:

  • 每個用戶都必須有各自的獨立主要外部存儲設備,且不得訪問其他用戶的主要外部存儲設備。
  • /sdcard 路徑必須根據運行進程的用戶身份解析到特定於該用戶的正確主要外部存儲設備。
  • Android/obb 目錄中較大的 OBB 文件的存儲可作爲優化在多個用戶之間共享。
  • 次要外部存儲設備不得讓應用寫入內容,除非在特定於軟件包的目錄中獲得合成的權限。

此功能的默認平臺實現利用 Linux 內核命名空間,爲每個 Zygote 所派生的進程創建獨立的裝載表,然後使用綁定裝載向私有命名空間提供特定於用戶的正確主要外部存儲設備。

啓動時,系統會在 EMULATED_STORAGE_SOURCE(隱藏於應用中)裝載一個模擬的外部存儲設備 FUSE 守護進程。在 Zygote 派生之後,它會將特定於用戶的相應子目錄從 FUSE 守護進程下綁定裝載到 EMULATED_STORAGE_TARGET,以便外部存儲設備路徑正確解析應用。由於應用缺少其他用戶存儲的可訪問裝載點,它們只能供啓動的用戶訪問存儲設備。

該實現還使用共享的子樹內核功能將裝載事件從默認的根命名空間傳播到應用命名空間中,從而確保 ASEC 容器和 OBB 裝載等功能繼續正常運行。它通過將 rootfs 裝載爲共享模式,然後在每個 Zygote 命名空間都創建好後重新將其裝載爲從屬模式來實現。

2.2、多個外部存儲設備

從 Android 4.4 開始,多個外部存儲設備通過 Context.getExternalFilesDirs()Context.getExternalCacheDirs() 和 Context.getObbDirs() 提供給開發者。

通過這些 API 提供的外部存儲設備必須是設備的半永久部件(如電池盒中的 SD 卡插槽)。開發者希望存儲在這些位置的數據可供長期使用。因此,瞬態存儲設備(如 USB 大容量存儲驅動器)不應通過這些 API 提供。

WRITE_EXTERNAL_STORAGE 權限必須僅向在設備上的主要外部存儲設備授予寫入權限。不允許應用寫入次要外部存儲設備,除非在特定於軟件包的目錄中獲得合成的權限。以這種方式限制寫入可確保系統在應用被卸載時將文件清理乾淨。

2.3、USB 媒體支持

Android 6.0 支持只需短時間內連接到設備的便攜式存儲設備,如 U 盤。當用戶插入新的便攜式設備時,該平臺會顯示一條通知,以讓用戶複製或管理相應設備上的內容。

在 Android 6.0 中,任何未合併的設備均被視爲便攜式設備。由於便攜式存儲設備只能短時間連接到設備,因此 Android 平臺會避免執行媒體掃描之類的繁重操作。第三方應用必須通過存儲訪問框架與便攜式存儲設備中的文件進行交互;出於隱私和安全考慮,明確禁止直接訪問。

 

3、可合併的存儲設備

Android 一直都支持外部存儲配件(如 SD 卡),但由於這些配件存在預期的無常性,以及傳統外部存儲設備只受最低限度的數據保護,因此這些配件一直以來僅限於進行簡單的文件存儲。Android 6.0 推出了合併外部存儲媒介(使其可以像內部存儲設備一樣使用)的功能。

注意:在運行 Android 7.0-8.1 的設備上,文件級加密 (FBE) 無法用於可合併的存儲設備。在使用 FBE 的設備上,必須將新添加的存儲媒介(例如 SD 卡)用作傳統存儲設備

運行 Android 9 及更高版本的設備可以使用可合併的存儲設備和 FBE。

當合並外部存儲媒介時,系統將對其進行格式化和加密處理,以便一次只在一臺 Android 設備上使用。由於該媒介與合併它的 Android 設備緊密關聯,因此可以安全地爲所有用戶存儲應用和私密數據。

當用戶將新的存儲媒介(如 SD卡)插入到可合併的位置時,Android 會詢問他們想要如何使用該媒介。他們可以選擇合併該媒介,這樣的話,系統會對該媒介進行格式化和加密處理,或者也可以繼續按原樣將其用於簡單的文件存儲。如果他們選擇合併媒介,平臺會詢問是否將主要共享存儲內容(通常裝載在 /sdcard 上)遷移到新合併的媒介上,從而騰出寶貴的內部存儲空間。不同於因使用 MBR 而限制爲 2TB 的傳統存儲設備,可合併的存儲設備使用 GPT,因而文件存儲空間上限約爲 9ZB。

只有當開發者通過 android:installLocation 屬性指示提供支持時,才能將應用放置在合併的存儲媒介上。新安裝的受支持的應用將自動放置在具有最多可用空間的存儲設備上,用戶可以在“設置”應用中在存儲設備之間移動支持的應用。移動到已合併媒介的應用在媒介彈出時被記住,並在重新插入媒介時返回彈出前的狀態。

3.2、安全性

平臺爲每個合併的設備隨機生成加密密鑰,該密鑰存儲在 Android 設備的內部存儲設備上。這樣可以有效地使得合併的媒介與內部存儲設備一樣安全。密鑰與合併的設備(基於合併的分區 GUID)相關聯。合併的設備使用通過 aes-cbc-essiv:sha256 算法和 128 位密鑰大小配置的 dm-crypt 進行加密。

合併設備的磁盤佈局緊密對應內部數據分區,包括 SELinux 標籤等。當在 Android 設備上支持多用戶時,合併的存儲設備也通過與內部存儲設備相同的隔離級別支持多用戶。

由於合併的存儲設備的內容與合併該設備的 Android 設備密切相關,加密密鑰不應可以從父設備中進行提取,因此該存儲設備無法裝載到其他位置。

內容模式的默認加密算法是 aes-256-xts,而文件名的默認加密算法是 aes-256-heh。您可以通過分別更改屬性 ro.crypto.volume.contents_mode 和 ro.crypto.volume.filenames_mode 的值(更改方式爲在 device.mk 中設置 PRODUCT_PROPERTY_OVERRIDES)來更改這些設置。

如果您的內核不支持 HEH 文件名加密,您可以通過將以下內容添加到 device.mk 來改用 CTS 模式:

PRODUCT_PROPERTY_OVERRIDES += \
ro.crypto.volume.filenames_mode=aes-256-cts

3.4、性能和穩定性

應該只考慮合併位於穩定位置(如電池盒內或防護蓋後面的插槽)的外部存儲媒介,以避免意外的數據丟失或損壞。尤其是,絕不應該考慮合併連接到手機或平板電腦的 USB 設備。一種常見的例外情況是連接到電視類設備的外部 U 盤,因爲整個電視機通常安裝在一個穩定的位置。

當用戶合併新的存儲設備時,平臺將運行基準測試,並將其性能與內部存儲設備進行比較。如果所合併設備的速度明顯慢於內部存儲設備,則平臺將警告用戶體驗可能會受到影響。此基準根據常用 Android 應用的實際 I/O 行爲得出。目前,AOSP 實現只會在超出單個閾值時警告用戶,但是設備製造商可以進一步做出調整,例如如果存儲卡運行非常緩慢,則完全拒絕合併。

合併的設備必須使用支持 POSIX 權限和擴展屬性(如 ext4 或 f2fs)的文件系統進行格式化。爲了獲得最佳性能,建議基於閃存的存儲設備使用 f2fs 文件系統。

在執行週期性空閒維護時,平臺將向合併的媒介發出 FI_TRIM(就像對待內部存儲設備那樣)。目前的 SD 卡規範不支持 DISCARD 命令;不過,內核會回退到使用 ERASE 命令,SD 卡固件可以選擇使用該命令來實現優化目的。

3.5、修正雙重加密

在 Android 8.x 及更低版本中,可合併的存儲設備不支持 FBE。帶有可合併的存儲設備的所有現有設備都使用全盤加密(FDE)。在 Android 9 中,可合併的存儲設備支持 FBE。但在默認情況下,文件內容已進行雙重加密,因爲可合併的存儲設備具有 FDE 和 FBE 層。默認情況下,這兩個層都會加密文件內容,這會降低設備性能。要解決雙重加密問題並提高設備性能,請執行以下操作:

  1. 這些補丁程序添加到內核中。
  2. 要使用 vold 傳達此項更改,請將以下內容添加到 device.mk 中:
PRODUCT_PROPERTY_OVERRIDES += ro.crypto.allow_encrypt_override=true

如果您設置了此項內容,但內核修補程序不存在,則可合併的存儲設備將無法工作,並且 vold 日誌將包含一條錯誤消息(提示它無法創建 dm 設備)。

注意:請勿使用 OTA 更新更改此標記,因爲這會更改可合併的存儲設備的磁盤格式。

3.6、測試

要測試可合併的存儲設備是否正常工作,請運行此 CTS 測試:

cts-tradefed run commandAndExit cts-dev \
    -m CtsAppSecurityHostTestCases \
    -t android.appsecurity.cts.AdoptableHostTest

要在設備沒有內置插槽或正使用 USB 連接器實現有效的 adb 連接時驗證 U 盤和 SD 卡的行爲,請使用:

adb shell sm set-virtual-disk true

 

4、設備配置

外部存儲空間由 vold init 服務和 StorageManagerService 系統服務共同管理。外部實體存儲卷的裝載由 vold 處理:通過執行分階段操作準備好媒體,然後再將其提供給應用。

注意:在 Android 8.0 中,MountService 類已更名爲 StorageManagerService

4.1、文件映射

對於 Android 4.2.2 及更早版本,特定於設備的 vold.fstab 配置文件定義從 sysfs 設備到文件系統裝載點的映射,每行都遵循以下格式:

dev_mount <label> <mount_point> <partition> <sysfs_path> [flags]
  • label:卷的標籤。
  • mount_point:要裝載卷的文件系統路徑。
  • partition:分區編號(從 1 開始);如果是第一個可用分區,則爲“auto”。
  • sysfs_path:可以提供此裝載點的設備的一個或多個 sysfs 路徑。這些路徑用空格分開,且必須都以 / 開頭。
  • flags:可選的逗號分隔標記列表,不能包含 /。可能的值包括 nonremovable 和 encryptable

對於 Android 4.3 及更高版本,init、vold 和 recovery 所使用的各種 fstab 文件在 /fstab.<device> 文件中進行統一。對於由 vold 管理的外部存儲卷,條目應採用以下格式:

<src> <mnt_point> <type> <mnt_flags> <fs_mgr_flags>
  • src:sysfs(通常在 /sys 下裝載)下可以提供裝載點的設備的路徑。路徑必須以 / 開頭。
  • mount_point:要裝載卷的文件系統路徑。
  • type:捲上的文件系統類型。如果是外部卡,則通常爲 vfat
  • mnt_flagsVold 會忽略此字段,應將其設置爲 defaults
  • fs_mgr_flagsVold 會忽略此字段中不包含 voldmanaged= 標記的統一的 fstab 中的任何行。該標記必須後跟描述卡的標籤,以及分區號或字詞 auto。例如:voldmanaged=sdcard:auto。其他可能的標記有 nonremovableencryptable=sdcardnoemulatedsd 和 encryptable=userdata

4.2、配置詳情

框架層級以及更高層級的外部存儲交互通過 StorageManagerService 來處理。由於 Android 6.0 中進行了配置更改(例如移除了 storage_list.xml 資源疊加層),因此配置詳情分成了兩類。

4.2.1、Android 5.x 及更低版本

設備專屬的 storage_list.xml 配置文件(通常通過 frameworks/base 疊加層提供)定義存儲設備的屬性和限制。<StorageList> 元素包含一個或多個 <storage> 元素,其中一個元素應被標記爲主元素。<storage> 屬性包括:

  • mountPoint:此裝載的文件系統路徑。
  • storageDescription:描述此裝載的字符串資源。
  • primary:如果此裝載是主要外部存儲,則爲 true。
  • removable:如果此裝載包含可移動媒體(如物理 SD 卡),則爲 true。
  • emulated:如果此裝載由可能使用 FUSE 守護進程的內部存儲模擬和支持,則爲 true。
  • mtp-reserve:MTP 應爲免費存儲預留的存儲 MB 數。僅在裝載被標記爲模擬時使用。
  • allowMassStorage:如果此裝載可通過 USB 大容量存儲設備共享,則爲 true。
  • maxFileSize:最大文件大小(以 MB 爲單位)。

設備可以通過模擬由內部存儲支持的文件系統(不區分大小寫,無需權限)來提供外部存儲。system/core/sdcard 中的 FUSE 守護進程提供一個可能的實現,可添加爲特定於設備的 init.rc 服務:

# virtual sdcard daemon running as media_rw (1023)
service sdcard /system/bin/sdcard <source_path> <dest_path> 1023 1023
    class late_start

其中,source_path 爲提供支持的內部存儲,dest_path 爲目標裝載點。

配置特定於設備的 init.rc 腳本時,必須將 EXTERNAL_STORAGE 環境變量定義爲主要外部存儲的路徑。/sdcard 路徑也必須通過符號鏈接解析到同一位置。如果設備在平臺更新之間調整外部存儲的位置,則應創建符號鏈接,以便舊的路徑繼續發揮作用。

4.2.2、Android 6.0及以上

目前,存儲子系統的配置集中在特定於設備的 fstab 文件中,並且移除了一些歷史靜態配置文件/變量,以支持更多動態行爲:

  • storage_list.xml 資源疊加層已被移除,框架已不再使用該疊加層。現在,存儲設備在被 vold 檢測到時動態配置。
  • EMULATED_STORAGE_SOURCE/TARGET 環境變量已被移除,Zygote 已不再使用這些變量來配置特定於用戶的裝載點。相反,用戶分離現在由特定於用戶的 GID 強制執行,主要共享存儲由 vold 在運行時裝載到位。
    • 開發者可以根據其使用情形繼續動態或靜態構建路徑。在路徑中包含 UUID 可識別每個卡,以便爲開發者提供更清晰的位置。(例如,/storage/ABCD-1234/report.txt 明顯是與 /storage/DCBA-4321/report.txt不同的文件。)
  • 硬編碼的 FUSE 服務已從特定於設備的 init.rc 文件中移除,在需要時將從 vold 動態派生。

除了這些配置更改之外,Android 6.0 還包含可合併的存儲設備的概念。對於 Android 6.0 設備,任何未被合併的物理媒體都被視爲便攜式設備。

可合併的存儲設備

要在 fstab 中表示可合併的存儲設備,請在 fs_mgr_flags 字段中使用 encryptable=userdata 屬性。典型定義如下:

/devices/platform/mtk-msdc.1/mmc_host*           auto      auto     defaults
voldmanaged=sdcard1:auto,encryptable=userdata

合併存儲設備時,該平臺會擦除內容並寫入定義兩個分區的 GUID 分區表:

  • 一個較小的空 android_meta 分區,預留以備將來使用的。分區類型 GUID 爲 19A710A2-B3CA-11E4-B026-10604B889DCF。
  • 一個較大的 android_ext 分區,使用 dm-crypt 加密並使用 ext4 或 f2fs(取決於內核功能)格式化。分區類型 GUID 爲 193D1EA4-B3CA-11E4-B075-10604B889DCF。

便攜式存儲設備

在 fstab 中,具有 voldmanaged 屬性的存儲設備默認被視爲便攜式設備,除非定義了其他屬性(如 encryptable=userdata)。例如,典型的 USB OTG 設備的定義如下:

/devices/*/xhci-hcd.0.auto/usb*             auto            auto    defaults
                                                    voldmanaged=usb:auto

該平臺在裝載之前使用 blkid 檢測文件系統類型,用戶可以選擇在文件系統不受支持時將媒體格式化。

 

5、更快地獲得存儲統計信息

在早期版本的 Android 中,系統會遍歷特定應用擁有的所有文件以測量磁盤使用情況。此手動測量過程可能需要幾分鐘的計算時間,然後才能在“設置”中向用戶顯示結果。

此外,清除緩存數據文件的內部算法僅查看所有應用的修改時間。這使得惡意應用可以通過將修改時間設置在遙遠的未來以使其不當地擁有高於其他應用的優先級,從而降低整體用戶體驗。

爲了提升這些體驗,Android 8.0 會詢問是否利用 ext4 文件系統的“配額”支持來幾乎即時地返回磁盤使用情況統計信息。此配額功能還可以防止任何單個應用使用超過 90% 的磁盤空間或 50% 的索引節點,從而提高系統的穩定性。

5.1、實現

配額功能是 installd 默認實現的一部分。 在特定文件系統上啓用配額功能後,installd 會自動使用該功能。如果在所測量的塊設備上未啓用或不支持配額功能,則系統將自動且透明地恢復手動計算方式。

要在特定塊設備上啓用配額支持,請執行以下操作:

  1. 啓用 CONFIG_QUOTACONFIG_QFMT_V2 和 CONFIG_QUOTACTL 內核選項。
  2. 將 quota 選項添加到 fstab 文件中的 userdata 分區:
/dev/block/platform/soc/624000.ufshc/by-name/userdata   /data
ext4    noatime,nosuid,nodev,barrier=1,noauto_da_alloc
latemount,wait,check,formattable,fileencryption=ice,quota

您可以在現有設備上安全地啓用或停用 fstab 選項。在更改 fstab 選項後的第一次啓動過程中,fsmgr 會強制執行 fsck 傳遞以更新所有配額數據結構,這可能會導致首次啓動時間稍長。後續啓動不會受到影響。

配額支持僅在 ext4 和 Linux 3.18 或更高版本上進行了測試。如果在其他文件系統或者較舊的內核版本上啓用,設備製造商將負責測試和檢查統計信息的正確性。

不需要特殊硬件支持。

5.2、驗證

StorageHostTest 下包含 CTS 測試,它們可使用用於測量磁盤使用情況的公共 API。無論是否啓用了配額支持,這些 API 都應返回正確的值。

5.3、調試

測試應用通過爲空間大小使用唯一的質數來仔細分配磁盤空間區域。調試這些測試時,請使用此質數來確定任何差異的原因。例如,如果增量爲 11MB 的測試失敗了,請檢查 Utils.useSpace() 方法以查看 11MB blob 是否存儲在 getExternalCacheDir() 中。

還有一些可能對調試有用的內部測試,但它們可能需要停用安全檢查才能通過:

runtest -x frameworks/base/services/tests/servicestests/ \
  src/com/android/server/pm/InstallerTest.java
adb shell /data/nativetest64/installd_utils_test/installd_utils_test
adb shell /data/nativetest64/installd_cache_test/installd_cache_test
adb shell /data/nativetest64/installd_service_test/installd_service_test

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章