 操作系統(tǒng) (JYY) 學(xué)習(xí)筆記:火星上的 Bug 與 Android 移動操作系統(tǒng)的黑客法則)
寫在前面這是本系列的第二十八篇。虛擬化和容器技術(shù)使我們可以管理成千上萬臺計算機為小到一個班集體、大到全世界的人提供各種云端服務(wù)。而連接每一個人的不僅是云端的超級分布式應(yīng)用還有萬千的終端物理設(shè)備。雖然通用的 Linux、Windows 操作系統(tǒng)的 API 完全可以勝任管理這些設(shè)備但面向特定領(lǐng)域的操作系統(tǒng)和數(shù)據(jù)中心里的巨獸相比又有著極其獨特的設(shè)計哲學(xué)。本講內(nèi)容嵌入式實時和移動操作系統(tǒng)的設(shè)計與實現(xiàn)原理。每個人都可以是生活的黑客嵌入式操作系統(tǒng) (Embedded OS)我們之前學(xué)的都是通用操作系統(tǒng)這里介紹一下特定領(lǐng)域的操作系統(tǒng)。嵌入式的操作系統(tǒng)通常更關(guān)注并發(fā)特性與絕對的可靠性。為了達到這個目的它們甚至愿意放棄一些諸如高吞吐量 I/O 讀寫效率之類的通用性能。什么是嵌入式系統(tǒng)“Embedded”: 計算機系統(tǒng)是“嵌入”到特定物理設(shè)備中的。人造衛(wèi)星、工業(yè)控制 (工控)、家用電器、醫(yī)療器械、穿戴設(shè)備……核心要求體積小、功耗低、可靠性極高。操作系統(tǒng)通常更簡單(因為領(lǐng)域限定)當(dāng)然經(jīng)過高度裁剪的 Linux 也是可以跑在嵌入式設(shè)備上的。例子PLC (Programmable Logic Controller) 和工控自動邏輯控制設(shè)備支撐了現(xiàn)代工業(yè)的自動化流水線。典型設(shè)計Event-based (事件驅(qū)動) 與 Real-time (實時)通常有嚴格的實時 (real-time) 保障必須在嚴格的時間限制內(nèi)對外部事件作出可預(yù)測的響應(yīng)。晚了一毫秒可能就是車毀人亡。例子無論今天的新能源汽車車機屏幕多么花哨跑著 Android 甚至玩 3A 游戲最終負責(zé)底盤實時控制的仍然是底層的 MCU 和硬實時操作系統(tǒng)。(還記得導(dǎo)致至少 6 人死亡的 Therac-25 醫(yī)療事故并發(fā) Bug 嗎這就是缺乏硬實時安全保障的代價)例子FreeRTOSA FreeRTOS application will start up and execute just like a non-RTOS application untilvTaskStartScheduler()is called.BaseType_txTaskCreate(TaskFunction_t pvTaskCode,// 函數(shù)指針constchar*constpcName,// 名稱constconfigSTACK_DEPTH_TYPE uxStackDepth,// 堆棧深度void*pvParameters,// 參數(shù)UBaseType_t uxPriority,// 任務(wù)優(yōu)先級(核心)TaskHandle_t*pxCreatedTask);// 返回句柄這個 API 和普通的thread.h里的spawn差不多。Task 通常是一個死循環(huán)。操作系統(tǒng)按絕對的優(yōu)先級喚醒阻塞的 Tasks高優(yōu)先級無條件搶占低優(yōu)先級。雖然它沒有絕對的 Hard real-time (硬實時)但嚴格的優(yōu)先級機制能實現(xiàn)極強的控制力。優(yōu)先級帶來的致命麻煩Priority Inversiondefjyy():set_priority(1)# 優(yōu)先級最低mutex_lock(restroom_lock)# 拿到了廁所的互斥鎖def馬院長():set_priority(10)# 優(yōu)先級中等long_work()# 執(zhí)行極其耗時的無鎖計算任務(wù)def校長():set_priority(100)# 優(yōu)先級極高mutex_lock(restroom_lock)# 想要進廁所被鎖住了只能等待如果只有一個處理器會發(fā)生什么災(zāi)難jyy(低優(yōu)先級) 拿到了鎖。校長(高優(yōu)先級) 想要鎖被阻塞只能等jyy用完。此時馬院長(中優(yōu)先級) 突然來了因為他的優(yōu)先級比jyy高他直接搶占了 CPU開始執(zhí)行漫長的long_work()結(jié)果jyy被趕下處理器無法釋放鎖 $ \rightarrow $校長作為最高優(yōu)先級被迫一直等待中優(yōu)先級的馬院長執(zhí)行完畢這就是著名的“優(yōu)先級反轉(zhuǎn) (Priority Inversion)”。這個“事故”曾經(jīng)在火星上真實發(fā)生過Sojourner “探路者” (PathFinder)1997 年 7 月 4 日登陸火星。Lander (登陸艙):IBM Rad6000 SC (20 MIPS), 128 MiB RAM, 6 MiB EEPROM。Rover (火星車):Intel 80C85 (0.1 MIPS), 512K RAM, 176K Flash。運行著VxWorks“實時” 任務(wù)操作系統(tǒng)。任務(wù)優(yōu)先級分配ASI/MET task: 大氣成分監(jiān)測 (低優(yōu)先級)bc_dist task: 分發(fā)任務(wù) (中優(yōu)先級)bc_sched task: 總線調(diào)度 (高優(yōu)先級)著陸后開始出現(xiàn)系統(tǒng)瘋狂重啟。原因正是優(yōu)先級反轉(zhuǎn)觸發(fā)了硬件的看門狗 (Watchdog) 定時器系統(tǒng)認為嚴重故障被迫硬重啟。(低優(yōu)先級)select$ \rightarrow $pipeIoctl$ \rightarrow $selNodeAdd$ \rightarrow $mutex_lock(高優(yōu)先級)pipeWrite$ \rightarrow $mutex_lock最終 JPL 工程師通過遠程發(fā)送補丁開啟了互斥鎖的“優(yōu)先級繼承 (Priority Inheritance)”特性低優(yōu)先級拿到鎖后臨時提升到等待該鎖的最高優(yōu)先級才挽救了這臺價值上億美元的火星車。移動操作系統(tǒng) (Android 演進史)人手一個的“手機”曾經(jīng)的手機可不是今天的樣子我上大學(xué)的時候在用什么手機Nokia 5310 XpressMusic (2007): 320 x 240 屏幕; 200 萬像素攝像頭; 30 MB 存儲這在當(dāng)時已經(jīng)算是非常精細了。運行著 Symbian S40 系統(tǒng)相比于“功能機”已經(jīng)是巨大的進步了。Symbian 和 J2ME 的時代沒變的是大學(xué)上課和現(xiàn)在一樣無聊。The K Virtual Machine (KVM) J2ME。當(dāng)時的高端玩家都在折騰如何用極小的 KVM 在幾十 KB 內(nèi)存里跑 Java 游戲。那個男人改變了世界2007 年喬布斯發(fā)布了初代 iPhone。那個男人遇到了對手Google 2005 年秘密收購了 Android Inc.初創(chuàng)團隊包含一些極其硬核的 BeOS 操作系統(tǒng)的成員例如牛逼的 Binder IPC 機制就源自 BeOS。Android 的興起高瞻遠矚的豪賭Android 架構(gòu)Linux Full JVM Framework API 開放平臺早期的 Dalvik VM 虛擬機 $ \rightarrow $ 后來演化成了 Android Runtime (ART)。那么代價呢卡到爆啊第一代安卓機 HTC G1 使用的是 Qualcomm MSM7201 處理器。32-bit ARMv6 528MHz (單核 CPU, TSMC 90nm工藝)。順序八級流水線 (IPC 1)拖著沉重的 Java 虛擬機連滑動地圖都會卡成 PPT。但這其實是一個高瞻遠矚的決定他們賭摩爾定律會生效2008 年同年Intel 桌面端已經(jīng)發(fā)布了恐怖的 i7-965 (4C8T, 6 發(fā)射)。移動端算力大爆炸只是時間問題。對手們的下場Symbian暴斃于 C。C 代碼誰寫誰知道指針飛舞滿地 Bug。Windows Phone暴斃于封閉和緩慢的反應(yīng)。(其實也許只要熬到了移動網(wǎng)頁時代大家就都能活下來了)。Android 選擇了 Java。2005 年的互聯(lián)網(wǎng)時代Java 已經(jīng)涌現(xiàn)出極其強大的生命力和生態(tài)。借著海量 Java 開發(fā)者的東風(fēng)雖然早期卡頓但它硬生生熬死了 Symbian 和 Windows Phone。最終形成了如今 Android 和 iPhone 兩強爭霸的格局。One More Comment (大模型時代的隱喻)初期的“低性能”總會解決的。算法 (軟件) 會進步硬件也會進步摩爾定律還沒死呢。在今天的大語言模型 (LLM) 時代這同樣成立今天 600B 龐大模型才能做到的智能通過算法演進明天 30B 的小模型也能做到。一定存在一個臨界點能實現(xiàn)端側(cè)“實時推理無處不在”。Android Apps 的真面目首先它是一個 Java 程序 (Kotlin)。其次它有獨特的生命周期運行模型。Android Platform APIsandroid.view.View: “the basic building block for user interface components” (UI 組件的基石)android.webkit.WebView: 嵌入應(yīng)用的網(wǎng)頁引擎android.hardware.camera2: 相機控制android.database.sqlite: 原生內(nèi)置的數(shù)據(jù)庫四大組件Activity (活動), Service (后臺服務(wù)), ContentProvider (數(shù)據(jù)提供), BroadcastReceiver (廣播接收)。Platform API 之下一個“微內(nèi)核”架構(gòu)核心黑科技 1Binder IPCRemote Procedure Call (RPC) 遠程過程調(diào)用remote.transact()在性能優(yōu)化和易用之間的極致權(quán)衡相比于 UNIX 傳統(tǒng)的管道 / 套接字 (Socket)它們太“底層”了手工管理序列化極其痛苦。Binder 基于共享內(nèi)存實現(xiàn) (Linux Kernelbinderdriver)。實現(xiàn)了絕無僅有的“Copy once” (只需一次內(nèi)存拷貝)性能碾壓傳統(tǒng) IPC。自帶服務(wù)端線程池和高效的注冊機制。核心黑科技 2Activity Stack (活動棧)當(dāng)你在微信里點擊鏈接拉起知乎看完后按返回鍵知乎會銷毀并優(yōu)雅地返回到微信界面。這種行為在用戶“切換應(yīng)用”后仍能完美保持這與傳統(tǒng)桌面操作系統(tǒng)的窗口管理器Alt Tab邏輯有著本質(zhì)的體驗區(qū)別極大地適應(yīng)了移動端的小屏操作流。每個人都可以是生活的黑客 (Hacking Android )Android: 不只是應(yīng)用生態(tài)更是極客生態(tài)UNIX 哲學(xué)POSIX system calls $ \rightarrow $ libc $ \rightarrow $ coreutils $ \rightarrow $ 應(yīng)用程序世界幕后是gcc,gdb,vscode。Android 也為我們留下了極其豐富的后門開發(fā)者工具Android Debug Bridge (adb):神器中的神器adb push/pull/install管理文件和應(yīng)用。adb shell獲取底層終端 (比如screencap /sdcard/screen.png瞬間截圖)。adb forward,logcat抓取日志。我們可以做什么花活1. scrcpy利用 ADB 在設(shè)備上捕獲高幀率視頻流 (通常有硬件加速)通過 USB 極低延遲投屏到電腦 Host 端進行控制。2. UI Automation (Monkey 測試)無限猴子定理給 10,000 只猴子和打字機有一只能盲打出 C 程序其他只能打出 Perl 亂碼。讓“電子猴子”去瘋狂隨機點擊、滑動屏幕用來測試我們的應(yīng)用會不會 Crash這就是高強度的自動化魯棒性測試。甚至可以“任意改變” App 的行為 (Hook 注入)LSPosed: 強大的 Xposed 框架分支可以在運行時動態(tài)劫持和修改任意 Java 方法的返回值比如攔截廣告、偽造定位、微信自動搶紅包?,F(xiàn)在甚至有VirtualXposed等技術(shù)利用雙開容器的原理連 Root 權(quán)限都不需要就能實現(xiàn) Hook。極客實戰(zhàn)?;钆c反殺 (Diehard Apps)如何徹底殺死一個 Android 進程讀讀內(nèi)核源碼ActivityManagerService.javaAndroid 為了安全給每一個 App 都分配了一個獨立的沙箱uid。遍歷進程表找到屬于這個uid的所有進程組。執(zhí)行Process.KillProcessGroup。為了防止那些生命力頑強的流氓 App系統(tǒng)會間隔 5ms連續(xù)發(fā)送 40 次 SIGKILL 信號確保徹底斬草除根科普kill -9** 代表了什么**普通的kill默認發(fā)送SIGTERM信號編號 15進程可以捕獲它并在退出前優(yōu)雅地保存數(shù)據(jù)。而kill -9發(fā)送的是SIGKILL信號。這是一個底層的強殺命令進程根本無法捕獲或忽略連喊救命的機會都沒有直接被內(nèi)核干掉。黑客的反制利用數(shù)據(jù)競爭進行進程?;罴热幌到y(tǒng)要殺我那我就利用并發(fā)漏洞讓子進程故意成為“孤兒進程”脫離原本的進程組從而在組強殺中幸存逃避SIGKILL信號。利用文件鎖互相監(jiān)控。一旦主進程被殺死孤兒進程立刻在極短的時間窗口內(nèi)重新拉起另一個主進程于是手機里出現(xiàn)了連系統(tǒng)強制停止都殺不死的僵尸 App?!癉iehard apps” (EuroSys’19 頂級學(xué)術(shù)會議論文探討了這種奇觀)。在 Android 的浪潮里折騰作為系統(tǒng)程序員我們也想過做一些極其有趣的事。甚至把它做成了學(xué)術(shù)論文發(fā)表 (Jigsaw paper)。在 LLM 大模型時代基于 UI 的自動化和語義識別這些 Hack 行為會變得更容易做成、更好玩。很多時候我們都被學(xué)術(shù)界 “Publish or perish (要么發(fā)表要么出局)” 的枷鎖束縛了但只要跳出框架每個人都可以是生活中的極客與黑客總結(jié)Take-away messages:在每一個細分的計算領(lǐng)域操作系統(tǒng)的底層架構(gòu)師都會做出一些面向該領(lǐng)域的特殊設(shè)計妥協(xié)例如嵌入式領(lǐng)域的絕對實時性控制 (real-timeliness)或是 Android 面向龐大應(yīng)用生態(tài)而建立的 JVM Binder 運行模型。但剝開層層迷霧回到應(yīng)用視角去俯瞰操作系統(tǒng)它本質(zhì)上永遠是一組封裝好的底層 API。這組 API 屏蔽了枯燥的硬件細節(jié)使得無數(shù)開發(fā)者能夠愉快、高效地構(gòu)建出改變世界的應(yīng)用。是的大道至簡操作系統(tǒng)的魔力就是這么簡單。