Linux 内核模块机制:为什么 Android 是唯一“两头不靠“的系统
日期:2026-10-17
起因:评估把网络共享迁移到 Android 设备时,发现“内核模块”是整个方案的硬约束。顺着这个问题往下查,发现它不是一个设备的问题,而是四种操作系统在内核扩展机制上的路线差异,最终集中体现在 Android 这个最尴尬的位置上。
本文整理内核模块的加载机制、四种系统的驱动模型对比,以及 Android 为什么卡在中间。文中 Android 设备以“未开源内核源码的机型”代称。
目录
-
问题从哪来
-
内核能力的两种形态与加载约束
-
四种系统的驱动模型
-
对比:谁能扩展内核,谁有用户态出口
-
难题:Android 是唯一“两头不靠”的系统
-
实测边界
-
设计原则
-
总结
1. 问题从哪来
评估把网络共享方案迁移到 Android 设备时,遇到一个绕不过去的约束:需要 NFS 挂载,但没有 nfs.ko 模块,也没有内核源码可以自己编译。
顺着这个问题往下查:
-
Linux 桌面为什么能加模块?因为有源码和 headers。
-
Windows 也锁内核,为什么没人觉得受限?因为有 UMDF 用户态驱动框架。
-
macOS 为什么能废弃 kext?因为有 DriverKit 作为替代。
-
鸿蒙为什么没有“模块”问题?因为驱动本来就不在内核里。
四个系统都在回答同一个问题:驱动代码跑在哪,谁有权往里加东西? 答案各不相同。而 Android 的答案是:继承了宏内核的架构,锁死了模块能力,又没有提供用户态出口。
2. 内核能力的两种形态与加载约束
2.1 两种形态
| 形态 | 编译选项 | 运行方式 | 能否事后补充 |
|---|---|---|---|
| 编译进内核 | CONFIG_X=y | 开机即加载 | 出厂定死 |
| 内核模块 | CONFIG_X=m | 生成 .ko,按需加载 | 需要匹配的模块文件 |
2.2 模块加载的四道约束
| 约束 | 说明 |
|---|---|
| vermagic | 模块编译时的内核版本字符串必须与 uname -r 匹配 |
| 符号依赖 | 模块引用的内核符号必须在 /proc/kallsyms 中导出 |
| ABI 兼容 | 内核数据结构、函数签名在版本间变化,跨版本模块运行时崩溃 |
| 模块签名 | CONFIG_MODULE_SIG_FORCE=y 时,未签名模块被拒绝 |
四道约束全过,模块才能加载。没有内核源码,就无法为新版本重新编译模块。
3. 四种系统的驱动模型
3.1 Linux:宏内核,模块运行在内核态
Linux 是宏内核。模块加载后运行在内核空间,拥有完整内核权限。
自由度来自源码和工具链的可得性:
-
发行版提供
linux-headers包; -
DKMS 可以在内核升级后自动重编第三方模块;
-
主流发行版默认不强制模块签名。
代价是模块与内核版本强绑定,且驱动 bug 的爆炸半径是整个系统。
3.2 Windows:混合内核,内核态与用户态驱动并存
Windows 的驱动模型分两层:
| 框架 | 运行位置 | 崩溃影响 |
|---|---|---|
| WDM / KMDF | 内核态 | 可能蓝屏(BSOD) |
| UMDF | 用户态(沙盒进程) | 仅进程崩溃,系统不受影响 |
UMDF 的意义:对于不需要直接操作硬件的设备类(传感器、摄像头、USB 协议设备),可以完全避开内核态风险。微软官方文档把“提高系统稳定性”列为 UMDF 的首要优势。
所以 Windows 的“内核锁死”只锁内核态驱动。用户态驱动是官方提供的出口。
3.3 macOS:从 kext 到 DriverKit,主动把驱动推出内核
macOS 内核(XNU)是 Mach 与 BSD 的混合体。历史上驱动以 kext 形式运行在内核态。
但从 macOS Catalina(10.15)开始,Apple 系统性废弃 kext,推动驱动迁移到用户态:
| 机制 | 运行位置 | 状态 |
|---|---|---|
| kext | 内核态 | 逐步淘汰 |
| DriverKit(dext) | 用户态 | 官方替代 |
| System Extension | 用户态 | 安全/网络等功能 |
Apple 的规则很直接:从 macOS 11 开始,如果存在等效的 System Extension 或 DriverKit 方案,内核就不会加载 kext。
macOS 在用架构手段把驱动推出内核,用稳定性换一点性能开销。
3.4 鸿蒙:微内核,驱动完全在用户态
鸿蒙内核是微内核,只保留进程调度、内存管理、IPC 等最核心功能。文件系统、驱动、网络服务全部运行在用户态。
驱动在鸿蒙上的运行方式:
-
加载到用户态执行;
-
被限制在 EL0 权限(用户态特权级),无法提升到内核 EL1 层;
-
每个用户态服务或驱动拥有独立地址空间,只通过 IPC 互通。
即使一个驱动被攻破,攻击者拿到的也只是 EL0 权限,无法触及内核。代价是 IPC 通信开销,通过零拷贝、异步 IPC 和 HDF 框架缓解。
鸿蒙的“内核模块”问题不存在——驱动从一开始就不在内核里。
4. 对比:谁能扩展内核,谁有用户态出口
| 系统 | 内核架构 | 驱动运行位置 | 扩展内核的路径 | 用户态出口 |
|---|---|---|---|---|
| Linux 桌面 | 宏内核 | 内核态 | 有源码、有 headers、DKMS | 无(驱动只能在内核态) |
| 嵌入式 Linux | 宏内核 | 内核态 | 厂商提供 kmod-* 包 | 无 |
| Windows | 混合内核 | KMDF 内核态 / UMDF 用户态 | 内核态驱动受签名约束 | UMDF |
| macOS | 混合内核 | kext 内核态 / DriverKit 用户态 | kext 逐步淘汰 | DriverKit / System Extension |
| 鸿蒙 | 微内核 | 全部用户态 | 驱动本来就不在内核里 | 架构本身 |
| Android(未开源内核源码的机型) | 宏内核 | 内核态 | 无源码、无 headers、签名强制 | 无 |
关键差异不在“内核锁不锁”,而在锁了之后有没有别的路走。
-
Linux 桌面:不锁,能加模块。
-
Windows:锁内核态驱动,但 UMDF 提供用户态出口。
-
macOS:主动收缩内核边界,DriverKit 是官方替代路径。
-
鸿蒙:驱动从一开始就在用户态。
-
Android:锁死模块能力,同时没有用户态驱动框架。
5. 难题:Android 是唯一“两头不靠”的系统
Android 的处境是四个系统里唯一“两头不靠”的:
架构上,它继承了 Linux 的宏内核。 驱动和文件系统必须编译进内核或加载模块,没有 Windows UMDF、macOS DriverKit、鸿蒙微内核那样的用户态驱动框架。任何需要内核级能力的任务,只能走模块这条路。
生态上,模块这条路被锁死了。 对于未开源内核源码的机型,三重限制叠加:
text
第一重:内核出厂一次性编译,厂商不再更新 第二重:内核源码不公开,无法自行编译模块 第三重:模块签名强制,未签名模块被拒绝
结果是:出厂没编译的能力,永久不可用。
替代上,它没有官方的用户态出口。 Windows 用户可以完全不碰内核态驱动,macOS 用户有 DriverKit,鸿蒙的驱动本来就在用户态。Android 没有等价物——想加功能,要么走模块(走不通),要么靠用户态程序(FUSE、TUN)自己凑,而用户态替代能覆盖的功能是有限的。
所以真正的难题不是“Android 没有某个模块”,而是:
未开源内核源码的 Android 机型,是唯一一个既不能加内核模块、也没有用户态驱动框架的系统。
这不是一个可以绕过或变通的技术障碍,而是架构选择、生态策略、厂商行为三者叠加后的结构性结果。
6. 实测边界
6.1 未 root 的 Android 设备(Android 12,5.10 内核,proot 容器)
| 内核能力 | 实测 | 结论 |
|---|---|---|
NFS 客户端(/proc/net/rpc) | 不存在 | 无 |
NFS 服务端(/proc/fs/nfsd) | 目录存在但空 | 有残留无服务 |
| mount / proot | 无 CAP_SYS_ADMIN(CapBnd=0) | 任何内核挂载都不可行 |
| FUSE | Android 必有(/sdcard 模拟就是它) | 用户态挂载可行 |
6.2 已 root 的 Android 设备(arm64,chroot)
| 项 | 情况 |
|---|---|
| 架构 | arm64 |
| root + chroot | 有 CAP_SYS_ADMIN / CAP_NET_ADMIN |
| 内核源码 | 不公开 → NFS/cifs 模块永久不可用 |
| TUN | Android 必有(VPN 功能)→ root 后可开真 tun0 |
6.3 Debian 机顶盒(armv7l)
| 项 | 实测 |
|---|---|
| 内核模块 | Debian 真内核,apt install 即装 |
| cpufreq | 不存在 |
| cpuidle | 不存在 → CPU 永远满频 |
机顶盒是“有内核自由 + NFS 能力”的设备,适合承担内核级任务。
6.4 NFS 与 SMB 的路径差异
| 项目 | NFS | SMB/CIFS |
|---|---|---|
| 内核版 | nfs.ko(必须内核模块) | cifs.ko(必须内核模块) |
| 用户态替代 | 无挂载级替代(libnfs 只能读写不能挂载) | rclone / sshfs(FUSE)能挂载 |
| Android 上的路径 | 死路,只能换协议 | FUSE 用户态挂载 |
FUSE 用户态挂载比内核版慢约 30-50%,但配置同步、文档类 KB 级场景感知不到差距。
7. 设计原则
text
1. 只用"出厂必带"的内核能力:socket / TUN / FUSE 2. 不依赖"需要加载模块"的能力:NFS 挂载 / cifs / 新文件系统 3. 网络共享: - 有内核自由度的设备继续当 NFS 服务器 - 受限设备用 sshfs / rclone / rsync(FUSE 用户态) 4. 组网:root 设备用 TUN,无 root 设备走普通 socket 服务 5. 把需要内核自由度的任务分配给有自由度的设备
判断标准很简单:这个功能需不需要内核模块?需要,就永久做不到。不需要,就看用户态能不能凑。
8. 总结
四种系统在内核扩展机制上的路线:
| 系统 | 路线 | 结果 |
|---|---|---|
| Linux | 宏内核,模块可加载 | 自由 |
| Windows | 混合内核,UMDF 用户态出口 | 锁内核但不锁功能 |
| macOS | 主动把驱动推出内核 | 锁内核但不锁功能 |
| 鸿蒙 | 微内核,驱动本来就在用户态 | 架构本身解决 |
| Android(未开源内核源码的机型) | 宏内核 + 生态锁死 + 无用户态出口 | 两头不靠 |
Android 的难题不是单一技术限制,而是四种系统里唯一一个“继承了宏内核架构、锁死了模块能力、又没有提供用户态驱动框架”的组合。这个边界画死之后,设计上唯一能做的,是把它当作一个固定约束,然后把需要内核自由度的任务分配给有自由度的设备。
本文基于实际设备实测与各系统官方文档整理。不同机型、内核版本和系统版本下细节可能不同,请以实际环境为准。
更多推荐




所有评论(0)