本文参考《Mac OS X and iOS Internals: To the Apple’s Core》 by Jonathan Levin
文章内容主要是阅读这本书的读书笔记,建议读者掌握《操作系统》,了解现代操作系统的技术特点,再阅读本文可以事半功倍。
虽然iOS系统内核使用极简的微内核架构,但内容依然十分庞大,所以会分
系统架构、进程调度、内存管理和文件系统四个部分进行阐述。
1 iOS 进化史
Mac OS Classic 拥有伟大的GUI,但系统设计却非常糟糕,尤其是协作式的多任务系统和低效的内存管理,以今天的标准看非常原始。
NeXTSTEP操作系统作为乔布斯回归苹果的嫁妆,连同架构中的Mach和OC等一起融合进了Mac OS Classic。与其说是Mac OS Classic融合了NeXTSTEP,不如说是后者反客为主替换了前者,这个转变并不是一瞬间发生的。
OS Classic拥有不错的GUI但系统设计糟糕,NeXTSTEP 设计很棒,但GUI非常平淡。这两个小众操作系统融合的结果是一个继承二者优点的成功得多的全新操作系统—Mac OS X
全新的Mac OS X 在设计与实现上都同 NeXTSTEP非常接近,诸如 Cocoa、Mach 、Interface Builder 等核心组件都源自NeXTSTEP。
iOS最初称为iPhone OS 是OS X 应对移动平台的分支,本质上iOS就是Mac OS X。拥有一样的操作系统层次结构以及相同的操作系统核心Dawin。
2 iOS 和 OS X的不同点:
- iOS 内核和二进制文件编译的目标架构是ARM架构, OS X基于Intel i386 和 x86_64。相比Intel ARM的优势在于电源管理。
- iOS 的内核闭源(苹果2017年开源了iOS的XNU内核代码),OS X内核是开源的,而且苹果承诺会一直开源
- iOS 内核编译稍有不同,有相当一部分嵌入式特性和API
- iOS 的GUI是SpringBoard,OS X的是 Aqua。前者针对触屏,后者是鼠标驱动的窗口系统。
- iOS 的内存管理要紧凑的多。 iOS系统不支持虚拟内存,OS X通过内存映射获得额外的内存,几乎有无穷的交换空间可以使用。(这条错误,iOS同OSX共用相同的虚拟内存机制,感谢评论区的同学支出问题)
- iOS app不允许访问底层的Darwin,也没有root访问权限,并且只能访问自己目录内的数据。
最后一点是最重要的,苹果公司竭尽全力确保iOS作为一个移动平台操作系统的封闭性。这个特性主要是通过文件目录隔离实现的,正常的app安装在Applications/ 目录,只有有限的权限。在越狱系统中,可以将app安装在System/ 目录,以获取更多系统权限。
3 iOS的架构
- UI层: 主要有SpringBoard、Spotlight等UI交互界面
- 应用框架层:主要有 Cocoa Touch
- 核心框架层:主要有 OpenGL、Quartz等图形、多媒体组件
- Darwin:操作系统核心,包括XNU内核和UNIX shell
3.1 Darwin 架构:
图片引用自《深入解析Mac OS X & iOS 操作系统》
Darwin的内核是XNU,XNU is Not Unix。XNU是两种技术的混合体,Mach和BSD。BSD层确保了Darwin系统的UNIX特性,真正的内核是Mach,但是对外部隐藏。BSD以上属于用户态,所有的内容都可以被应用程序访问,而应用程序不能访问内核态。当需要从用户态切换到内核态的时候,需要通过mach trap实现切换。
3.2 XNU 包含:
- Mach微内核
- BSD层
- libkern
- I/O Kit
3.3 Mach
Mach 是 XNU的原子核,是一个微内核轻量级操作系统,仅处理最核心的任务
- 进程和线程抽象
- 任务调度
- 进程间通讯和消息传递
- 虚拟内存管理
3.4 BSD层
BSD层简历在Mach之上,确保了Darwin符合 POSIX 。提供了更高层次的功能,包括:
- UNIX 进程模型
- POSIX线程模型(Pthread)及相关的同步原语
- UNIX 用户和组
- 网络协议栈(BSD Socket API)
- 文件系统访问
- 设备访问(通过/dev目录访问)
3.5 libKern
I/OKit是C++ 编写的;为了支持C++运行时并提供所需要的基类,是一个內建的自包含的C++库。
I/O Kit
这是一个在内核中的完整的自包含的执行环境,让开发者可以使用C++快速创建设备驱动程序。
3.6 POSIX系统调用
POSIX 兼容性是由XNU中BSD层提供的。
所有的POSIX 系统调用不论底层实现如何都有相同的原型,也就是说具有相同的参数和返回值。
系统调用编号:除了固定的原型之外,POSIX还完整定义了系统调用的编号。
3.7 Mach 系统调用
BSD层是对Mach内核的包装,但是Mach系统调用仍然可以在用户态访问。需要借助mach trap实现用户态到内核态的转换。
在32位系统上,Mach系统调用的编号都为负数,POSIX调用编号为非负。
在64位系统上,Mach系统调用为正数,但是以0x2000000开头,而POSIX调用编号以0x1000000开头。
4 iOS 使用的技术
4.1 bundle
平时开发中,我们经常会听到bundle这个词,在阿里大多数情况下bundle表示一个模块或者一个pod 库。实际上,bundle的概念来源于OS X,最先由NeXTSTEP使用。苹果的给出的定义是:** 一个标准化的有层次的结构,保存了可执行的代码以及该代码所需要的资源。**
bundle是framework、plugin、widgets、内核扩展的根基。
4.2 FSEvents
提供有关文件系统通知的API,通过这个API,用户应用程序可以简单快速地响应文件添加、修改和删除时间,OC中CoreServices框架(Carbon)提供的FSEventStreamCreate及其它相关API是对FSEvents的封装。
4.3 通知
分布式IPC的一种形式,进程可以通过这种机制广播或监听事件。
4.4 GCD
这个不说大家也都知道
4.5 iOS 的安全机制
- 代码签名:使用SSL验证身份,通过发布者的私钥对公钥进行签名,来验证应用程序的来源以及在传输过程中是否被篡改
- 隔离机制(沙盒化):不受信任的应用程序必须在一个独立的隔间中运行,隔间实际上就是一个隔离的环境,在这个环境所有的操作都会受到限制,采用“黑名单”风格方法来阻止已知的危险操作,只有在列表具有足够的限制性时才有效果
- Entitlement:更为严格的沙盒,采用“白名单”的方式,只允许那些已知是安全的操作,其他所有操作都不允许,替换当前沙盒机制中采用的“黑名单”方式
MAC层:强制访问控制 Mandatory Access Control 是iOS沙盒机制的基础,控制应用程序只能访问指定的数据。属于 BSD 相关特性
MAC中的关键概念是label,label指的是一个预定义的分类,如果请求访问某个对象或者操作时没有提供匹配的label,那么MAC会拒绝访问请求。
5 用户态和内核态
5.1 Intel 架构:ring
基于Intel的系统提供了所需要的基于硬件的分离。从286处理器开始,Intel 引入了“保护模式”的概念。保护模式强制使用了4个“ring”。这些“ring0”指的就是权限级别,分别从0到3编号。这些“ring”以同心圆的方式组织,最内层的ring为 ring 0,最外层的为ring 3。 ring 0的权限最高,通常被称为超级用户模式(Supervisor mode)。只有最受信任的代码才能运行在处理器的ring 0上。随着ring级别递增,安全限制越多,权限也越低。
** ring 0对应的是内核态,ring 3对应的是用户态 **。ring 1 和 ring 2 预留给操作系统服务使用,但是在实际中却没有使用。
5.2 ARM架构:CPSR
ARM 处理器使用了一个特殊的寄存器:当前程序状态寄存器(current program status register,CPSR)来定义处理器所在的模式,ARM 处理器有以下主要的操作模式,如下表:
模式 | 表示模式的位 | 用途 |
---|---|---|
USR | 10000 | 用户模式 --- 不允许操作 |
SVC | 10011 | 管理器模式(默认的内核模式) |
SYS | 11111 | 系统模式 --- 同用户模式,但是允许写入CPSR |
FIQ | 10001 | 快速中断请求 |
IRQ | 10010 | 普通中断请求 |
ABT | 10111 | 终止模式 --- 错误的内存访问 |
UND | 11011 | 未定义模式 --- 非法/不支持的指令 |
USR 是唯一没有特权的模式,在内核通常运行组SVC模式。在任何特权模式中,都可以直接访问CPSR寄存器,隐藏只要修改CPSR中的模式位即可切换模式。在用户态,必须使用一种用户态/内核态转换机制。
5.3 内核态/用户态转换机制
用户态和内核态的转换机制有两种类型:
- 自愿转换:
当应用程序要求内核服务的时候,应用程序可以进行一个调用进入内核态,通过一个预定义的硬件指令可以开始进入内核态的切换。这些内核服务称为系统调用 - 非自愿转换:
当发生执行异常、中断或处理器陷阱的时候,代码的执行会被挂起,并且保留发生错误时候的完整状态。控制权被转交给预定义的内核态错误处理程序或中断服务程序(interrupt service rountine,ISR)
当用户态的程序需要内核服务的时候,会发出一个系统调用,系统调用将控制权转换交给内核。实现系统调用的方法有两种:
- 模拟中断:SVC和SWI指令
- 指令:SYSENTRY 和 SYSCALL
5.4 系统调用的处理
5.4.1 BSD 系统调用
BSD 的接口是 XNU 暴露出来的主要接口,这些在内部称为“UNIX 系统调用”或“BSD 调用”
BSD 系统调用的流程如下:
(1)验证传入的状态快照和处理器架构是否相匹配
(2)通过current_task 获得当前BSD 进程的数据结构。检查这个BSD 进程确实存在
(3)如果系统调用号为0,那么说明这是一个非直接的系统调用。相应地修正参数
(4)系统调用传递的参数应该是64位的。对于64未的处理程序来说,如果系统调用的阐述不能全部通过寄存器进行传递(即参数数目大于6的情况),则需要一些额外的工作,多余的参数需要复制到栈上。在32位的处理程序中,需要对参数进行mung操作。mung指的是从用户态复制参数的同时保持32位/64位兼容性的过程
(5)执行sysent表中的系统调用
(6)在很罕见的情况下,系统调用可能会表示需要重新执行,重新执行是由pal_syscall_restart( )处理的
(7)系统调用返回的错误码放在返回寄存器中(Intel:EAX/RAX 寄存器;ARM:R0 寄存器)
(8)系统调用处理通过thread_exception_return( )返回(在iOS 上通过load_and_go_user 返回),这个函数的处理和return_from_trap( )相同,在返回的过程中会处理 AST
5.4.2 Mach Trap
在 iOS 上系统调用编号为负,那么内核流程进入Mach陷阱处理的流程,而不是BSD 系统调用的处理。Mach 陷阱的处理程序成为mach_call_munger[64]
Mach 陷阱是由 mach_mall_munger[64] 处理的。UNIX 和 Mach 调用的参数都要进程 mung 处理,32 位的 unix_syscall 依然包含 munging 操作的代码
mach_trap_table 是一个mach_trap_t 结构体数组。之后跟着是mach_syscall_name_table,其中保存了对应的名字。
总结
iOS的核心是Darwin,Darwin的核心是XNU,XNU有两个核心,外层的BSD和内层的Mach。由于有两个内核所有有两种系统调用方式。造成这种现象的原因是OS X作为一款PC操作系统希望运行针对UNIX开发的应用,而iOS作为OS X的分支继承了这个特性。