跳至主要内容

mount -o remount 到 OverlayFS:Android Driver 開發者必須弄懂的 remount 機制

適合對象:做 BSP、kernel module、vendor HAL、firmware 整合的系統工程師。假設你已經知道 AOSP build 流程、fastboot、以及 Linux mount 的基本語意。


一、問題的起點:為什麼「改一個檔案」在 Android 上這麼貴

在一般 Linux 開發機上,改一個 driver 的流程是:make modulescp foo.ko /lib/modules/...rmmod && insmod → 看 dmesg。整個 loop 十秒鐘。

在 Android 裝置上,同一件事的預設路徑是:改 code → mmmm → 產出 vendor.img(甚至整包 super.img)→ fastboot flashall → 開機 → 重新佈置測試環境 → 重現問題。一輪五到十五分鐘,而且很多時候會把你辛苦重現出來的現場狀態(充放電狀態、sensor calibration、modem 註冊狀態、某個只在跑了三小時後才出現的 race)整個洗掉。

adb remount 存在的唯一理由,就是把第二種流程壓回第一種。它是系統層開發者迭代速度的槓桿,而不是一個「把手機變成可寫」的 hack 小技巧。

但要正確使用它、並且知道它什麼時候會騙你,你必須理解 Android 為什麼一開始要把這些分區鎖成唯讀——那背後疊了三層彼此獨立的約束。


二、第一層理解:Linux 的 remount 到底做了什麼

mount -o remount,rw /vendor 這個操作,在 VFS 層的語意是「不卸載、不重新建立 mount point,只修改既有 superblock 的 mount flags」。

這個區別很重要。正常的 umount + mount 需要該檔案系統上沒有任何開啟中的檔案描述子、沒有任何 process 的 cwd 在裡面。對 /system/vendor 而言這永遠不可能滿足——initvold、每一個 HAL service 都在跑。所以 remount 是唯一可行的路徑:它在原地翻轉 MS_RDONLY flag,正在執行的 process 完全無感。

在 Android 4.x/5.x 的年代,事情就到此為止。/system 是一塊實體分區、格式是可寫的 ext4、也還沒有 verified boot,adb remount 真的就只是幫你對每個唯讀掛載點跑一次 mount -o remount,rw

之後十年裡,三件事讓這個天真的模型徹底失效。


三、第二層理解:三道把分區鎖死的約束

3.1 dm-verity / AVB —— 完整性驗證

Verified Boot 的核心是 dm-verity:/system/vendor 這些分區被建成一棵 Merkle hash tree,root hash 存在 vbmeta 裡並由 bootloader 用金鑰驗簽。kernel 透過 device-mapper 把分區包成一個 dm-verity 裝置,每一次 block 讀取都會即時驗 hash

這意味著:即使你成功把分區 remount 成 rw 並寫了一個位元組進去,下一次讀到那個 block 時 dm-verity 會發現 hash 對不上,然後依 verity mode 設定直接 panic 或把整個裝置踢進 restart-bootloader。

換句話說,verity 沒關掉的情況下,remount 成功 = 稍後必炸。這就是為什麼標準流程一定是 disable-verity 先行、而且必須重開機才生效(bootloader 要在下一次開機時看到那個 flag,才會用不帶 verity 的方式建立 dm 裝置)。

現代裝置上這件事又分兩層:adb disable-verity 處理的是 dm-verity 本身;而 AVB 的 vbmeta 驗證則可能還要另外靠 adb shell avbctl disable-verification,或是在刷機時用 fastboot --disable-verity --disable-verification flash vbmeta ...。哪一種有效取決於你的 BSP 怎麼配置,這也是新手在別人家的板子上最常卡住的地方。

3.2 Dynamic Partitions —— 沒有可用的剩餘空間

從 Android 10 開始,/system/vendor/product/system_ext/odm/vendor_dlkm 不再是實體分區,而是 super 這塊實體分區裡由 LP metadata 描述的 logical partition

伴隨而來的是 right-sizing:build 系統會把每個 image 縮到剛好裝得下內容的大小,好把省下來的空間還給 super 的動態配置池。結果是 /vendor 的檔案系統上幾乎沒有 free space。你 remount 成功了,但 cp 一個 2MB 的 .so 進去會直接 ENOSPC

3.3 EROFS / 壓縮唯讀檔案系統 —— 根本不可寫

再往後,為了省空間與提升讀取效能,許多裝置把唯讀分區改用 EROFS(Enhanced Read-Only File System),並在 block 層做去重(shared blocks)。

EROFS 在設計上就沒有寫入路徑。mount -o remount,rw 對它不是「被禁止」,而是語意上不存在。就算 verity 關了、空間夠了,你也寫不進去。而 shared blocks 更糟:多個檔案共用同一批 block,就算真的能寫,改一個檔案會連帶改到另一個毫不相干的檔案。


四、Google 的解法:把 remount 重新定義成 OverlayFS

面對這三道牆,AOSP 沒有選擇讓分區變回可寫,而是換掉了 adb remount 的實作。從 Android 10 起,fs_mgr 裡多了一套 overlayfs 整合邏輯(system/core/fs_mgr/fs_mgr_overlayfs.cpp,官方說明在 README.overlayfs.md)。

新的語意是:

  • lower layer:原本的唯讀分區(ext4 + verity,或 EROFS),保持不動。
  • upper layer:一塊實際可寫的 backing storage。
  • 兩者用 overlayfs 疊起來掛在原本的掛載點上。

adb push 一個檔案到 /vendor/lib/modules/foo.ko,實際寫入的是 upper layer;讀取時 overlayfs 把 upper 蓋在 lower 上合併呈現,於是系統「看起來」就像 /vendor 被改了。底層的 EROFS image 一個位元組都沒動,verity hash 也就沒有被破壞的問題(前提還是要關 verity,因為 fs_mgr 需要在早期就介入建立 overlay)。

最關鍵的認知轉變adb remount 之後你看到的檔案系統,已經不是出廠 image 的檔案系統了,而是一個「出廠 image + 你的 diff」的合成視圖。這個區別會在後面的踩坑清單裡反覆咬人。

4.1 Backing storage 放在哪裡

fs_mgr 會依裝置形態自動挑選,你不需要指定,但你必須知道它在哪,因為除錯時要去確認:

裝置形態Backing storage 位置
非 A/B 裝置/cache/overlay/
A/B 裝置(可用 LRAP)/mnt/scratch/overlay,來自 super 裡動態建立的 scratch logical partition
A/B 裝置(retrofit dynamic partition)直接徵用另一個 slot,例如跑在 _a 就吃掉整個 system_b

因此 scratch 是保留名稱,你的 BSP 不能拿去命名別的分區。另外注意最後一種情況相當暴力:另一個 slot 的 metadata 與內容會被清掉,之後想切回去 _b 開機是不行的。

值得注意的是,AOSP 明確做了一個架構決定:不使用 /data/overlay/,即使 userdata 空間幾乎無限。這是為了讓 overlay 能在開機極早期就掛上(/data 那時還沒掛、還可能是加密的)。代價就是 backing storage 的空間很緊,這是設計上的取捨而非疏忽。

4.2 為什麼「早期掛載」很重要

fs_mgr 會盡可能早地掛上 overlay——可以早到 first stage init,或是在 init rc 執行 mount_all 的時候。

這對 driver 開發者有直接意義:因為掛得夠早,你 push 進去的 vendor SELinux policy、init.*.rc、以及 /vendor/lib/modules 底下的 .ko 才能在 init 的各個 exec 階段之前就生效。如果 overlay 是等到系統跑起來才掛,這些東西全都來不及。

反過來說,這也劃出了一條硬邊界(見第七節):ramdisk 與 first stage init 本身無法被 overlay 覆蓋


五、實務流程

5.1 標準流程

adb root # 需要 userdebug / eng build
adb disable-verity # 需要 bootloader unlocked
adb reboot
adb wait-for-device
adb root
adb remount

之後可以:

adb push out/target/product/<device>/vendor/lib/modules/foo.ko /vendor/lib/modules/
adb reboot

或整包同步:

adb shell stop
adb sync
adb shell start

5.2 快捷寫法

前兩步可以合併成一行:

adb remount -R

-R 會在需要時自動關 verity 並重開機;如果裝置已經處在 remount 狀態,它不會多此一舉重開機。日常腳本裡用這個比較乾淨。

5.3 確認它真的生效了

這步不能省。AOSP 文件自己都寫了:「backing storage 在遇到錯誤時會被丟棄或忽略,這會造成困惑;使用 adb remount 除錯時,建議重開機後確認更新確實還在。」

adb shell mount | grep -i overlay # 看 overlay 有沒有掛上、掛在哪些點
adb shell df -h /mnt/scratch # 看 backing storage 還剩多少
adb shell ls -l /vendor/lib/modules/ # 直接確認檔案的 timestamp / size
adb shell dmesg | grep -i overlayfs # 掛載失敗的線索通常在這

養成「push 完 → reboot → 再確認一次檔案還在」的習慣。debug 一個其實根本沒被載入的新版 driver,是這個機制最常見也最浪費時間的失敗模式。


六、對 driver 開發到底省了什麼

把它拆開來看會更清楚價值在哪。以下都是 remount 之後可以秒級迭代的東西:

Kernel modules(/vendor/lib/modules/*.kovendor_dlkm 改一行 pr_debug、調一個 timeout、加一段 tracepoint,push 完 rmmod/insmod 或重開機就能驗。搭配 modules.load 的順序調整、或臨時把某個 module 從載入清單拿掉來做二分法定位,都不用重新出 image。

Vendor HAL(/vendor/lib64/hw/*.so/vendor/bin/hw/* HAL 跟 driver 的介面問題(IOCTL 參數、buffer 生命週期、HIDL/AIDL 行為)往往要兩邊一起試。remount 之後可以只 push 一邊,adb shell setprop ctl.restart vendor.foo-hal-default 重啟該 service,framework 完全不用動。

Firmware blobs(/vendor/firmware/ 做 firmware 版本的 A/B 比對——換一顆 blob、重開、看行為差異——這是 remount 最有價值的場景之一,因為 firmware 檔案通常很大,重新出 image 特別慢。

init.*.rc 與 device-specific 設定 調整 service 的啟動順序、on boot 觸發時機、chown/chmod 給 sysfs 節點的權限。這類問題本質上就是「試一次改一次」,沒有 remount 幾乎無法工作。

Vendor SELinux policy(/vendor/etc/selinux/ 新增一個 sysfs 節點或 device node,幾乎必然伴隨一輪 avc denial 的修補。push policy → reboot → 看 dmesg | grep avc,這個 loop 一天可能要跑二十次。

各種 config / calibration 檔 sensor 校正參數、display panel timing、thermal profile。這些檔案改動頻繁但體積小,最適合 overlay。

真正的價值不只是「快」,而是不破壞現場。很多硬體問題只在特定溫度、特定電量、跑了幾小時之後、或某個週邊處於某個狀態時才重現。能在不重刷、不清 data 的前提下換掉一個 module 再看一次,跟每次都要從頭重現,是完全不同量級的除錯能力。


七、你必須知道的邊界與踩坑

這一節是這篇文章真正的重點。以下每一條都對應到實際會浪費你半天的狀況。

7.1 first stage init 與 ramdisk 無法被覆蓋

這是 driver 開發者最容易踩的坑。 如果你的 module 是在 first stage init 從 ramdisk(vendor_boot 裡的 lib/modules)載入的——很多早期必要的 driver 都是,例如 storage、PMIC、clock、pinctrl——那麼 adb remount 對它完全無效。你 push 到 /vendor/lib/modules 的新版本根本不會被讀到,而你會困惑地看著一模一樣的 log。

這種 module 只能靠 fastboot flash vendor_boot(或 boot)處理。在開始 debug 前先確認你的 module 是從哪裡載入的:檢查 vendor_boot ramdisk 裡的 modules.load,以及 /vendor/lib/modules/modules.load

同理,kernel image 本身、ramdisk、bootloader 都不在 overlay 的覆蓋範圍內。

7.2 只能覆蓋子目錄,不能覆蓋整個分區

fs_mgr 刻意只對分區底下的子目錄建立 overlay。用 overlayfs 蓋掉整個分區會導致裝置無法開機。這也代表某些位於分區根目錄的檔案是動不了的。

7.3 空間會用完,而且用完的方式很難看

backing storage 是 file-by-file 複製(overlayfs 的 copy-up 語意)。你改了一個 40MB .so 裡的一行,upper layer 就會多出完整的 40MB。scratch 分區並不大,push 幾輪之後就可能塞爆。

更麻煩的連鎖反應:scratch 佔用了 super 的空間,之後如果 OTA 或某個操作要把某個 logical partition 變大,可能會因為 scratch 的存在而失敗。解法是清掉 backing storage:

adb enable-verity # 會釋放 overlay 並還原到修改前的狀態
# 或
fastboot flashall

然後重新佈署你的覆蓋內容。

7.4 空間門檻是硬編碼的

fs_mgr 是「探測檔案系統型態與剩餘空間」後自動決定用傳統 remount 還是 overlayfs。門檻寫死在程式碼裡:大致上如果分區還剩 1% 以上空間,它就不會啟用 overlayfs,而是走傳統直接 remount——但那點空間對實際開發往往根本不夠。

實務影響是:同一套指令在不同板子、不同 build 上可能走到不同路徑,行為不一致。這就是為什麼 5.3 節的驗證步驟不能省——先看清楚你這台裝置到底走的是哪條路。

7.5 remount 過的裝置不能吃 OTA

fs_mgr_overlayfs_is_setup() 回傳 true 時,update_engine 會拒絕運作。而 adb enable-verity 解除 overlay 之後,update engine 會執行完整 OTA(而非增量)。

如果你的測試流程涉及 OTA 驗證,remount 過的裝置必須先清乾淨。這兩件事在同一台機器上不能並存。

7.6 bootloader fastboot 刷機不會被偵測到

用 bootloader 的 fastboot(相對於 userspace 的 fastbootd)刷某個分區時,fs_mgr 偵測不到這件事,於是舊的 overlay 內容會繼續留著蓋在新刷的分區上

這會製造出極其詭異的症狀:你明明刷了新 image,行為卻還是舊的。刷完之後養成 adb enable-verity 清一次的習慣。

7.7 backing storage 是「脆弱」的,且不保證通知你

AOSP 文件的原話是:backing storage 被視為 fragile,只要有其他東西需要那塊空間,它就會被清掉,而且保留不通知你的權利

所以:永遠不要把唯一一份修改放在 overlay 上。 你的 patch 應該在 git 裡,overlay 只是一個暫時的部署快取。

7.8 kernel 需求

CONFIG_OVERLAY_FS=y 是必要條件。此外 4.4 以上的 kernel 需要 android-common 的 patch(override_creds mount option,用來處理 Android 嚴格的 SELinux least-privilege 模型與 overlayfs 預設行為的衝突),4.19 以上還額外需要 xattr recursion 的處理。用主線 kernel 自組的板子,這裡很容易出問題,症狀通常是 overlay 掛不上、或掛上了但 SELinux context 全錯。

7.9 remount 不解決的事情

最後釐清一件常見誤解:adb remount 只負責讓檔案「寫得進去」。以下問題完全不在它的職責範圍內,卻常被誤以為是 remount 沒做好:

  • module 簽章驗證失敗(CONFIG_MODULE_SIG_FORCE
  • GKI 的 vermagic / KMI 不相容(insmod-EINVAL 或 "version magic mismatch")
  • SELinux 拒絕存取你新增的節點(要改的是 policy,不是 mount flags)
  • 檔案 push 進去了但 service 沒重啟,載入的還是舊的 .so

八、什麼時候該放棄 remount、老實刷機

建立一條清楚的界線,能省下大量無謂的除錯:

  • 要驗證出廠行為時。 overlay 是合成視圖,不等於實際會出貨的 image。任何要簽核、要交付、要回報給客戶的驗證結果,都必須跑在完整刷機的裝置上。
  • 改動涉及 ramdisk、first stage init、kernel、bootloader 時。
  • 改動涉及分區佈局、LP metadata、AVB descriptor 時。
  • 要測 OTA、factory reset、或 A/B slot 切換時。
  • 出現任何「行為與檔案內容對不上」的詭異症狀時。adb enable-verity 清乾淨、完整刷一次,排除 overlay 這個變因,再回頭 debug。這比在一個狀態不明的合成檔案系統上瞎猜快得多。

另外提醒安全面:整套機制只在 userdebug / eng build、且 bootloader 已解鎖的裝置上可用。這是設計意圖,不是限制。出貨的 user build 必須關掉 ro.debuggable、鎖上 bootloader、開著 verity——如果你的量產機能 adb remount,那是一個嚴重的安全漏洞,不是方便功能。


九、一句話的心智模型

adb remount 不是「把分區打開」,而是「在唯讀的出廠檔案系統上,臨時疊一層可拋棄的 diff」。

抓住這個模型,前面所有的限制都變成推論而非需要背誦的規則:

  • 因為是臨時的 → 會被清掉、不能當唯一備份、wipe data 後消失。
  • 因為是疊在上面的 → 蓋不到 ramdisk 和 first stage init,因為那些階段還沒疊上去。
  • 因為是diff → 空間按檔案計算、會爆、跟 OTA 的增量假設互相衝突。
  • 因為底層沒有真的改 → verity hash 沒被破壞,但也代表你驗的不是出貨的那個 image。

對做 driver 的人來說,這個機制的價值不在於它讓你能改系統檔案,而在於它把「改一行 code 到看到結果」的成本從十分鐘壓到十秒,同時保住了那些難得重現的現場。理解它的邊界,才能在它騙你的時候立刻認出來。


參考資料