写在前面:这是本系列的第二十八篇。
虚拟化和容器技术使我们可以管理成千上万台计算机,为小到一个班集体、大到全世界的人提供各种云端服务。而连接每一个人的,不仅是云端的超级分布式应用,还有万千的终端物理设备。
虽然通用的 Linux、Windows 操作系统的 API 完全可以胜任管理这些设备,但面向特定领域的操作系统,和数据中心里的巨兽相比,又有着极其独特的设计哲学。
本讲内容:嵌入式(实时)和移动操作系统的设计与实现原理。每个人都可以是生活的黑客!
嵌入式操作系统 (Embedded OS)
我们之前学的都是通用操作系统,这里介绍一下特定领域的操作系统。
嵌入式的操作系统通常更关注并发特性与绝对的可靠性。
为了达到这个目的,它们甚至愿意放弃一些诸如高吞吐量 I/O 读写效率之类的通用性能。
什么是嵌入式系统?
“Embedded”: 计算机系统是“嵌入”到特定物理设备中的。
- 人造卫星、工业控制 (工控)、家用电器、医疗器械、穿戴设备……
- 核心要求:体积小、功耗低、可靠性极高。
- 操作系统通常更简单(因为领域限定);当然,经过高度裁剪的 Linux 也是可以跑在嵌入式设备上的。
例子:PLC (Programmable Logic Controller) 和工控
自动逻辑控制设备,支撑了现代工业的自动化流水线。
典型设计:Event-based (事件驱动) 与 Real-time (实时)
- 通常有严格的实时 (real-time) 保障:
- 必须在严格的时间限制内对外部事件作出可预测的响应。晚了一毫秒,可能就是车毁人亡。
- 例子:无论今天的新能源汽车车机屏幕多么花哨(跑着 Android 甚至玩 3A 游戏),最终负责底盘实时控制的,仍然是底层的 MCU 和硬实时操作系统。
(还记得导致至少 6 人死亡的 Therac-25 医疗事故并发 Bug 吗?这就是缺乏硬实时安全保障的代价)
例子:FreeRTOS
A FreeRTOS application will start up and execute just like a non-RTOS application until
vTaskStartScheduler()is called.
BaseType_txTaskCreate(TaskFunction_t pvTaskCode,// 函数指针constchar*constpcName,// 名称constconfigSTACK_DEPTH_TYPE uxStackDepth,// 堆栈深度void*pvParameters,// 参数UBaseType_t uxPriority,// 任务优先级!(核心)TaskHandle_t*pxCreatedTask);// 返回句柄- 这个 API 和普通的
thread.h里的spawn差不多。 - Task 通常是一个死循环。
- 操作系统按绝对的优先级唤醒阻塞的 Tasks(高优先级无条件抢占低优先级)。
- 虽然它没有绝对的 Hard real-time (硬实时),但严格的优先级机制能实现极强的控制力。
优先级带来的致命麻烦:Priority Inversion
defjyy():set_priority(1)# 优先级最低mutex_lock(restroom_lock)# 拿到了厕所的互斥锁def马院长():set_priority(10)# 优先级中等long_work()# 执行极其耗时的无锁计算任务def校长():set_priority(100)# 优先级极高mutex_lock(restroom_lock)# 想要进厕所,被锁住了,只能等待如果只有一个处理器,会发生什么灾难?
jyy(低优先级) 拿到了锁。校长(高优先级) 想要锁,被阻塞,只能等jyy用完。- 此时
马院长(中优先级) 突然来了,因为他的优先级比jyy高,他直接抢占了 CPU,开始执行漫长的long_work()! - 结果:
jyy被赶下处理器无法释放锁 $ \rightarrow $校长作为最高优先级,被迫一直等待中优先级的马院长执行完毕! - 这就是著名的“优先级反转 (Priority Inversion)”。
这个“事故”曾经在火星上真实发生过
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“实时” 任务操作系统。
任务优先级分配:
ASI/MET task: 大气成分监测 (低优先级)bc_dist task: 分发任务 (中优先级)bc_sched task: 总线调度 (高优先级)
着陆后开始出现系统疯狂重启。原因正是优先级反转触发了硬件的看门狗 (Watchdog) 定时器,系统认为严重故障,被迫硬重启。
- (低优先级)
select$ \rightarrow $pipeIoctl$ \rightarrow $selNodeAdd$ \rightarrow $mutex_lock - (高优先级)
pipeWrite$ \rightarrow $mutex_lock - 最终 JPL 工程师通过远程发送补丁,开启了互斥锁的“优先级继承 (Priority Inheritance)”特性(低优先级拿到锁后,临时提升到等待该锁的最高优先级),才挽救了这台价值上亿美元的火星车。
移动操作系统 (Android 演进史)
人手一个的“手机”
- 曾经的手机可不是今天的样子!
我上大学的时候在用什么手机?
- Nokia 5310 XpressMusic (2007): 320 x 240 屏幕; 200 万像素摄像头; 30 MB 存储(这在当时已经算是非常精细了)。
- 运行着 Symbian S40 系统(相比于“功能机”已经是巨大的进步了)。
Symbian 和 J2ME 的时代
- 没变的是:大学上课和现在一样无聊。
- The K Virtual Machine (KVM) & J2ME。当时的高端玩家都在折腾如何用极小的 KVM 在几十 KB 内存里跑 Java 游戏。
那个男人改变了世界
- 2007 年,乔布斯发布了初代 iPhone。
那个男人遇到了对手
- Google 2005 年秘密收购了 Android Inc.,初创团队包含一些极其硬核的 BeOS 操作系统的成员(例如牛逼的 Binder IPC 机制就源自 BeOS)。
Android 的兴起:高瞻远瞩的豪赌
Android 架构: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 桌面端已经发布了恐怖的 i7-965 (4C8T, 6 发射)。移动端算力大爆炸只是时间问题。
- 对手们的下场:
- Symbian:暴毙于 C++。C++ 代码谁写谁知道,指针飞舞,满地 Bug。
- Windows Phone:暴毙于封闭和缓慢的反应。
- (其实也许只要熬到了移动网页时代,大家就都能活下来了)。
- Android 选择了 Java。2005 年的互联网时代,Java 已经涌现出极其强大的生命力和生态。借着海量 Java 开发者的东风,虽然早期卡顿,但它硬生生熬死了 Symbian 和 Windows Phone。最终形成了如今 Android 和 iPhone 两强争霸的格局。
One More Comment (大模型时代的隐喻)
- 初期的“低性能”总会解决的。算法 (软件) 会进步,硬件也会进步(摩尔定律还没死呢!)。
- 在今天的大语言模型 (LLM) 时代,这同样成立:
- 今天 600B 庞大模型才能做到的智能,通过算法演进,明天 30B 的小模型也能做到。
- 一定存在一个临界点,能实现端侧“实时推理无处不在”。
Android Apps 的真面目
- 首先,它是一个 Java 程序 (Kotlin)。
- 其次,它有独特的生命周期运行模型。
- Android Platform APIs
android.view.View: “the basic building block for user interface components” (UI 组件的基石)android.webkit.WebView: 嵌入应用的网页引擎android.hardware.camera2: 相机控制android.database.sqlite: 原生内置的数据库- 四大组件:Activity (活动), Service (后台服务), ContentProvider (数据提供), BroadcastReceiver (广播接收)。
Platform API 之下:一个“微内核”架构
核心黑科技 1:Binder IPC
Remote Procedure Call (RPC) 远程过程调用:
remote.transact()
在性能优化和易用之间的极致权衡:
- 相比于 UNIX 传统的管道 / 套接字 (Socket),它们太“底层”了,手工管理序列化极其痛苦。
- Binder 基于共享内存实现 (Linux Kernel
binderdriver)。 - 实现了绝无仅有的“Copy once” (只需一次内存拷贝),性能碾压传统 IPC。
- 自带服务端线程池和高效的注册机制。
核心黑科技 2:Activity Stack (活动栈)
- 当你在微信里点击链接拉起知乎,看完后按返回键,知乎会销毁并优雅地返回到微信界面。
- 这种行为在用户“切换应用”后仍能完美保持,这与传统桌面操作系统的窗口管理器(Alt + Tab)逻辑有着本质的体验区别,极大地适应了移动端的小屏操作流。
每个人都可以是生活的黑客 (Hacking Android 😈)
Android: 不只是应用生态,更是极客生态
- UNIX 哲学:POSIX system calls $ \rightarrow $ libc $ \rightarrow $ coreutils $ \rightarrow $ 应用程序世界(幕后是
gcc,gdb,vscode)。 - Android 也为我们留下了极其丰富的后门(开发者工具):
- Android Debug Bridge (adb):神器中的神器!
adb push/pull/install管理文件和应用。adb shell获取底层终端 (比如screencap /sdcard/screen.png瞬间截图)。adb forward,logcat抓取日志。
我们可以做什么花活?
1. scrcpy
- 利用 ADB 在设备上捕获高帧率视频流 (通常有硬件加速),通过 USB 极低延迟投屏到电脑 Host 端进行控制。
2. UI Automation (Monkey 测试)
- 无限猴子定理:给 10,000 只猴子和打字机,有一只能盲打出 C 程序,其他只能打出 Perl 乱码。
- 让“电子猴子”去疯狂随机点击、滑动屏幕,用来测试我们的应用会不会 Crash,这就是高强度的自动化鲁棒性测试。
甚至可以“任意改变” App 的行为 (Hook 注入)
- LSPosed: 强大的 Xposed 框架分支,可以在运行时动态劫持和修改任意 Java 方法的返回值(比如拦截广告、伪造定位、微信自动抢红包)。
- 现在甚至有
VirtualXposed等技术,利用双开容器的原理,连 Root 权限都不需要就能实现 Hook。
极客实战:保活与反杀 (Diehard Apps)
如何彻底杀死一个 Android 进程?
- 读读内核源码
ActivityManagerService.java: - Android 为了安全,给每一个 App 都分配了一个独立的沙箱
uid。 - 遍历进程表,找到属于这个
uid的所有进程组。 - 执行
Process.KillProcessGroup。 - 为了防止那些生命力顽强的流氓 App,系统会间隔 5ms,连续发送 40 次 SIGKILL 信号,确保彻底斩草除根!
科普:
kill -9** 代表了什么?**
普通的kill默认发送SIGTERM(信号编号 15),进程可以捕获它,并在退出前优雅地保存数据。
而kill -9发送的是SIGKILL信号。这是一个底层的强杀命令,进程根本无法捕获或忽略,连喊救命的机会都没有,直接被内核干掉。
黑客的反制:利用数据竞争进行进程保活!
- 既然系统要杀我,那我就利用并发漏洞!
- 让子进程故意成为“孤儿进程”(脱离原本的进程组),从而在组强杀中幸存,逃避
SIGKILL信号。 - 利用文件锁互相监控。一旦主进程被杀死,孤儿进程立刻在极短的时间窗口内重新拉起另一个主进程!
- 于是,手机里出现了连系统强制停止都杀不死的僵尸 App。“Diehard apps” (EuroSys’19 顶级学术会议论文探讨了这种奇观)。
在 Android 的浪潮里折腾
- 作为系统程序员,我们也想过做一些极其有趣的事。
- 甚至把它做成了学术论文发表 (Jigsaw paper)。
- 在 LLM 大模型时代,基于 UI 的自动化和语义识别,这些 Hack 行为会变得更容易做成、更好玩。
- 很多时候我们都被学术界 “Publish or perish (要么发表,要么出局)” 的枷锁束缚了,但只要跳出框架:
- 每个人都可以是生活中的极客与黑客!
总结
Take-away messages:
在每一个细分的计算领域,操作系统的底层架构师都会做出一些面向该领域的特殊设计妥协:例如嵌入式领域的绝对实时性控制 (real-timeliness),或是 Android 面向庞大应用生态而建立的 JVM + Binder 运行模型。
但剥开层层迷雾,回到应用视角去俯瞰操作系统:它本质上永远是一组封装好的底层 API。这组 API 屏蔽了枯燥的硬件细节,使得无数开发者能够愉快、高效地构建出改变世界的应用。
是的,大道至简,操作系统的魔力就是这么简单。