第一章-OSX的起源 早期系统

受到早期个人计算机的性能限制,操作系统通常只通过命令行(BASIC)与用户交互。在这时大部分人只把个人计算机当作一个性能强大的游戏机使用,对计算机能做到的其他事缺乏了解。在1981年,第一个图形界面操作系统Alto在施乐问世了。然而施乐并没有发现图形界面的潜力,只把它当作一个内部项目使用。另一方面,当时个人计算机市场的领头羊之一苹果慧眼识珠,在OSX诞生前,苹果已经创造出了Macintosh这个成功的图形界面操作系统。Micintosh和施乐的Alto类似,使用鼠标作为指向设备,让用户可以直观地使用电脑的每个部分。

然而竞争对手也已经发现了良好的图形界面可以让入门用户也能发挥电脑的强大功能,也开发了自己的图形界面操作系统。其中最有名的便是微软基于MS-DOS开发的Windows系列。在1988年,Macintosh已经问世了四年,苹果知道自己需要一个更新的系统来竞争。在一番头脑风暴之后,苹果内部使用三色卡片区别三个不同的项目。蓝色卡片是关于Macintosh的一系列改进,是1991年苹果发布的Macintosh System 7的基础。粉色卡片则是一个更加激进的计划,构想了一个更加现代的操作系统。其中包括了面向对象,保护内存,多线程支持等等一系列现代操作系统的功能。最后的红色卡片则包含了比红色卡片更加激进的计划。

然而,粉色和红色卡片的构想被证明是难以实现的,而即使是最保守的蓝色卡片也一直到1991年才发布,此时微软已经在1990年发布了非常受欢迎的Windows 3.x系统,并在1993年发布了脱离MS-DOS的Windows NT系统以及1995年发布了更加受欢迎的Windows 95系统。苹果却在1991年后在更新的操作系统上几乎毫无建树。在1991到1997年苹果有高达7个操作系统有关的计划在实行中,包括运行在x86平台上的Star Trek项目,目标是构建全新的微内核的Raptor和NuKernel项目,与IBM合作尝试实现粉色卡片的TalOS项目,全面升级现有Macintosh的Copland和Gershwin项目,以及收购并改进一个现有的操作系统BeOS计划的Be项目。这七个项目的目标尽管各有千秋,但他们最后的结局无一例外的是流产了。其中最好的结局是Copland计划的一部分融入Macintosh的现有代码,而Gershwin则被认为只是个名头,并没有人去实现它。此时苹果处于低谷时期,管理层的目光又转回了乔布斯和他自己创建的NEXTSTEP操作系统。尽管当时NEXTSTEP还没有获得巨大的成功,它的设计已经吸引了一些企业用户,并且与苹果其他的计划不同的是它是一个真正的可以运行的操作系统。苹果在1997年收购了NeXT公司,事实证明,NEXTSTEP将为苹果翻开下一篇章。

NEXTSTEP使用了一个混合内核,它的底层是CMU开发的一个名不见经传的微内核Mach,而用户空间则是一个BSD系统。因此NEXTSTEP是一个Unix兼容的操作系统,并和当时的Unix系统一样提供一个命令行供用户操作。然而NEXTSTEP真正引人注目的地方是它的图形系统。事实上,很多NEXTSTEP的功能一直保留到现在的OSX中,包括:

  • 屏幕底部的Dock
  • 多窗口处理,程序可以被隐藏而不是退出
  • 一个可以访问网络存储的文件管理器
  • 一个面向对象的驱动框架,Driver Kit
  • 拖拽操作文件,例如可以直接把图片拖进邮件应用中作为附件发送

NEXTSTEP和其他操作系统另一个不同之处是使用了Objective-C开发整个系统。Objective-C受Smalltalk启发,是一个强调面向对象的C家族语言。由于这个特性NEXTSTEP的很多部分都是面向对象设计的。另一方面,NEXTSTEP大大简化了编写图形化程序的难度。系统自带一个Interface Builder使用户可以拖拽构建图形界面,然后再针对其编程。在1995年,NEXTSTEP发布了最后一个版本3.3,此时它已经是一个完整的图形界面操作系统,有着CD音效已经多任务处理功能,这也是苹果未来的系统雏形。

NEXTSTEP的内核和其他系统的区别在于整个内核是围绕Mach这个微内核构建的。而Mach最初来源于罗切斯特大学一个一个实验性项目RIG。RIG的目标是为不同的设备创建一个统一的接口。例如磁带机,打印机,制图机乃至于网络上的另一台计算机在RIG中都是统一的资源。这个计划诞生了Aleph内核,这个内核的特点是所有的进程都可以发送消息通信。每一个进程有一些port可以用来接收信息。如果进程X想要发一个消息给Y,那只需要指定Y的port号即可直接发送。由于消息的目的地并不是进程而是port,这个机制允许一个进程拦截另一个进程的信息,构建更加强大的系统。然而,由于RIG在1975年诞生,运行的硬件有诸多限制,例如没有虚拟内存,对port使用没有保护,以及由于内存限制每条消息最多只能2KB。因此这个项目很快就被搁置了。

随后,一名RIG的研究人员到卡耐基梅隆大学继续做着类似的研究。吸取RIG的经验,以及有了更新的硬件,一个新的内核Accent诞生了。Accent最大的改进是支持了虚拟内存,因此消息没有了大小限制,并且port有了权能作为保护措施。Accent更进一步地将跨进程通信的能力拓展到了网络上,一台计算机可以通过一个中转进程向另一台计算机的进程发送消息而不用担心它在网络的什么位置。然而Accent还是有一些问题,例如它不能支持多个处理器,并且不能兼容Unix的程序,也就意味着Accent没有成熟的生态。

为了进一步修补这些问题,Mach诞生了。Mach相对于Accent加入了完整的多处理器支持,并且要能兼容Unix。最后,开发者希望这个新内核要比Unix内核简单,因为Unix在当时已经有了15年的历史,内核臃肿程度让Mach开发者之一Richard Rashid将它成为“充满了特性或者功能的垃圾场”。因此,Mach使用4.3BSD作为起点,逐步替换掉复杂的Unix内核,并用更加简单的Mach微内核的功能代替。终于,在1986年Mach诞生了。Mach和Accent相比更加完善,但还是保留了从RIG以来就有的消息和port系统用于任务间通信(Mach将进程process称作任务task)。Mach相比Unix也改进了虚拟内存子系统。原本的4.3BSD虚拟内存需要一个连续的物理内存空间映射,而Mach的虚拟内存允许将多个分散的物理内存空间组合在一起使用,并且Mach保留了Accent的CoW技术,让内存使用更加充分。这个虚拟内存子系统的抽象非常完善,以至于理论上应用程序可以用同样的系统调用直接使用另一台计算机的内存而不会注意到任何区别。现在的OpenBSD的虚拟内存子系统就是以Mach的为基础做成的。

在Mach1和2中,由于整个系统是从4.3BSD修改的,BSD和Mach依然在同一个内存空间中,也就是说Mach还不是真正的微内核。从Mach3开始,Mach内核彻底脱离了BSD,BSD成为了一个用户空间程序,而不直接触及硬件。所有的底层调用全部由Mach内核完成。这个版本的Mach将硬件也抽象成了一个可以接收消息的port,让人物和硬件接口统一。此时的Mach内核更像一个虚拟机,可以让直接另一个操作系统运行在Mach内核上。然而,将操作系统运行在微内核上意味着需要在不同的系统之间来回切换,并且Mach使用的IPC机制尽管强大但有着不小的性能开销,因此Mac OS X使用了Mach2的做法,让BSD和Mach内核运行在同一个空间中以减小开销。随着Mach的成熟以及NEXTSTEP的收购,属于OS X的历史即将开始。

在收购了NeXT公司后,苹果开始对自己的操作系统进行一系列更新。收购后的第一个版本是1996年发布的7.6版操作系统,这也是第一个被称作 “Mac OS”而不是Macintosh的操作系统。在这个版本中苹果着重强调了这个系统的网络功能,包括内置的拨号功能已经可以将网络资源存储到桌面的功能。7.6只是一个过渡版本,很快,Mac OS来到了Mac OS 8版本。

Mac OS 8融合了之前提到的Copland的一部分功能,例如HFS+文件系统以及Carbon API的原型Copland API。在用户体验方面,Mac OS 8重新设计了一个更加好看的图形界面,以及加入了多用户的支持。另外,这个系统中还内置了浏览器(IE和网景)以及Java运行时。接下来的Mac OS 9则成为了第一个可以通过网络更新的系统版本,以及加入了文件加密和Keychain密码保护功能。然而这些版本都是在原本的Macintosh的基础上的改进,Mac OS 9.2.2也成为了Macintosh系统的最后一个版本,这些现在被称为Classic Mac OS系统。

在对Macintosh改进的同时,苹果也在马不停蹄地改进NeXT的OPENSTEP系统(NeXTSTEP的开源版本),并在1997年WWDC正式亮相。此时新系统被称作Rhapsody,它有一个全新的底层,包含

  • 基于Mach和BSD的内核
  • 一个兼容Classic Mac OS的子系统(蓝盒子)
  • 一个OPENSTEP API的扩展(黄盒子)
  • 一个Java虚拟机
  • 一个基于Display PostScript窗口系统
  • 一个融合NeXTSTEP标志性的拖拽功能和Mac OS的华丽界面的用户界面

其中兼容Classic Mac OS的子系统是一个加载了Mac OS 8的映像的虚拟机,而开发环境(也被称作黄盒子)提供了针对三个OPENSTEP核心框架:Foundation,Application Kit和Display PostScript的支持。开发者可以使用Objective-C和Java开发程序。黄盒子除了在Rhapsody上提供以外,也提供Windows版本供开发者使用。

在发行了两个Rhapsody版本之后,苹果终于发布了Mac OS X系统和Darwin。Darwin是一个供开发者使用的开源版Mac OS X内核。首先发布的是Mac OS X server,本质上是Rhapsody的另一个改进版,包含了一些开发用的工具,例如apache服务器和一些网络工具。而桌面版的系统则发布了开发者预览版,并在2000年9月13日发布了一个公众预览版Mac OS X,标志着Mac OS X正式进入市场。在这个版本中Mac OS X使用了拟物化界面设计,并在底部加入了的dock。在2001年3月24日,Mac OS X发布了一个正式版“猎豹”。OS X(Darwin)使用的内核名为XNU,是X is Not Unix的缩写。XNU除了使用Mach以外,还加入了一些其他项目的代码,例如苹果赞助的MkLinux的代码,和NetBSD以及OpenBSD的代码。

Posted in OSX Internals笔记 | Leave a comment

第一章-OSX的起源

NEXTSTEP的内核和其他系统的区别在于整个内核是围绕Mach这个微内核构建的。而Mach最初来源于罗切斯特大学一个一个实验性项目RIG。RIG的目标是为不同的设备创建一个统一的接口。例如磁带机,打印机,制图机乃至于网络上的另一台计算机在RIG中都是统一的资源。这个计划诞生了Aleph内核,这个内核的特点是所有的进程都可以发送消息通信。每一个进程有一些port可以用来接收信息。如果进程X想要发一个消息给Y,那只需要指定Y的port号即可直接发送。由于消息的目的地并不是进程而是port,这个机制允许一个进程拦截另一个进程的信息,构建更加强大的系统。然而,由于RIG在1975年诞生,运行的硬件有诸多限制,例如没有虚拟内存,对port使用没有保护,以及由于内存限制每条消息最多只能2KB。因此这个项目很快就被搁置了。

随后,一名RIG的研究人员到卡耐基梅隆大学继续做着类似的研究。吸取RIG的经验,以及有了更新的硬件,一个新的内核Accent诞生了。Accent最大的改进是支持了虚拟内存,因此消息没有了大小限制,并且port有了权能作为保护措施。Accent更进一步地将跨进程通信的能力拓展到了网络上,一台计算机可以通过一个中转进程向另一台计算机的进程发送消息而不用担心它在网络的什么位置。然而Accent还是有一些问题,例如它不能支持多个处理器,并且不能兼容Unix的程序,也就意味着Accent没有成熟的生态。

为了进一步修补这些问题,Mach诞生了。Mach相对于Accent加入了完整的多处理器支持,并且要能兼容Unix。最后,开发者希望这个新内核要比Unix内核简单,因为Unix在当时已经有了15年的历史,内核臃肿程度让Mach开发者之一Richard Rashid将它成为“充满了特性或者功能的垃圾场”。因此,Mach使用4.3BSD作为起点,逐步替换掉复杂的Unix内核,并用更加简单的Mach微内核的功能代替。终于,在1986年Mach诞生了。Mach和Accent相比更加完善,但还是保留了从RIG以来就有的消息和port系统用于任务间通信(Mach将进程process称作任务task)。Mach相比Unix也改进了虚拟内存子系统。原本的4.3BSD虚拟内存需要一个连续的物理内存空间映射,而Mach的虚拟内存允许将多个分散的物理内存空间组合在一起使用,并且Mach保留了Accent的CoW技术,让内存使用更加充分。这个虚拟内存子系统的抽象非常完善,以至于理论上应用程序可以用同样的系统调用直接使用另一台计算机的内存而不会注意到任何区别。现在的OpenBSD的虚拟内存子系统就是以Mach的为基础做成的。

在Mach1和2中,由于整个系统是从4.3BSD修改的,BSD和Mach依然在同一个内存空间中,也就是说Mach还不是真正的微内核。从Mach3开始,Mach内核彻底脱离了BSD,BSD成为了一个用户空间程序,而不直接触及硬件。所有的底层调用全部由Mach内核完成。这个版本的Mach将硬件也抽象成了一个可以接收消息的port,让人物和硬件接口统一。此时的Mach内核更像一个虚拟机,可以让直接另一个操作系统运行在Mach内核上。然而,将操作系统运行在微内核上意味着需要在不同的系统之间来回切换,并且Mach使用的IPC机制尽管强大但有着不小的性能开销,因此Mac OS X使用了Mach2的做法,让BSD和Mach内核运行在同一个空间中以减小开销。随着Mach的成熟以及NEXTSTEP的收购,属于OS X的历史即将开始。

Posted in OSX Internals笔记 | Leave a comment

第一章-OSX的起源 进化至OSX

在收购了NeXT公司后,苹果开始对自己的操作系统进行一系列更新。收购后的第一个版本是1996年发布的7.6版操作系统,这也是第一个被称作 “Mac OS”而不是Macintosh的操作系统。在这个版本中苹果着重强调了这个系统的网络功能,包括内置的拨号功能已经可以将网络资源存储到桌面的功能。7.6只是一个过渡版本,很快,Mac OS来到了Mac OS 8版本。

Mac OS 8融合了之前提到的Copland的一部分功能,例如HFS+文件系统以及Carbon API的原型Copland API。在用户体验方面,Mac OS 8重新设计了一个更加好看的图形界面,以及加入了多用户的支持。另外,这个系统中还内置了浏览器(IE和网景)以及Java运行时。接下来的Mac OS 9则成为了第一个可以通过网络更新的系统版本,以及加入了文件加密和Keychain密码保护功能。然而这些版本都是在原本的Macintosh的基础上的改进,Mac OS 9.2.2也成为了Macintosh系统的最后一个版本,这些现在被称为Classic Mac OS系统。

在对Macintosh改进的同时,苹果也在马不停蹄地改进NeXT的OPENSTEP系统(NeXTSTEP的开源版本),并在1997年WWDC正式亮相。此时新系统被称作Rhapsody,它有一个全新的底层,包含

  • 基于Mach和BSD的内核
  • 一个兼容Classic Mac OS的子系统(蓝盒子)
  • 一个OPENSTEP API的扩展(黄盒子)
  • 一个Java虚拟机
  • 一个基于Display PostScript窗口系统
  • 一个融合NeXTSTEP标志性的拖拽功能和Mac OS的华丽界面的用户界面

其中兼容Classic Mac OS的子系统是一个加载了Mac OS 8的映像的虚拟机,而开发环境(也被称作黄盒子)提供了针对三个OPENSTEP核心框架:Foundation,Application Kit和Display PostScript的支持。开发者可以使用Objective-C和Java开发程序。黄盒子除了在Rhapsody上提供以外,也提供Windows版本供开发者使用。

在发行了两个Rhapsody版本之后,苹果终于发布了Mac OS X系统和Darwin。Darwin是一个供开发者使用的开源版Mac OS X内核。首先发布的是Mac OS X server,本质上是Rhapsody的另一个改进版,包含了一些开发用的工具,例如apache服务器和一些网络工具。而桌面版的系统则发布了开发者预览版,并在2000年9月13日发布了一个公众预览版Mac OS X,标志着Mac OS X正式进入市场。在这个版本中Mac OS X使用了拟物化界面设计,并在底部加入了的dock。在2001年3月24日,Mac OS X发布了一个正式版“猎豹”。OS X(Darwin)使用的内核名为XNU,是X is Not Unix的缩写。XNU除了使用Mach以外,还加入了一些其他项目的代码,例如苹果赞助的MkLinux的代码,和NetBSD以及OpenBSD的代码。

Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 系统概览

总的来说,Mac OS X的技术有三个来源:来自苹果自己的,来自NeXT的和来自其他第三方开源项目的。然而,苹果做了非常出色的工作整合了所有这些代码,让开发者和用户都不会察觉到之间的区别。Mac OS X同时提供了传统的Unix体验和易用的Macintosh体验。Mac OS X有着标准的Unix接口所以大部分Unix软件,例如GNU和X Window软件都可以在Mac OS X上运行(事实上Mac OS X是受认证的Unix系统)。另一方面,那些一般不会在Unix系统上运行的软件,例如Microsoft Office或者Adobe Creative Suite,同样可以在Mac OS X上运行。为了做到这一点,Mac OS X有许多不同的组件支持系统运行。下面是一张Mac OS X的系统概览图。

由于系统组件的复杂性,这张图并没有那么精确。例如OpenGL不仅是图形系统的一部分,也是图形硬件的HAL层(目前已经被Metal取代)。又比如BSD的应用环境同时还包含了C API,本应画在内核上,但也没有在这张图中表现出来。

然而这张图也包含了足够的信息让我们可以概览Mac OS X的架构。例如

  • 低层次的组件更加接近硬件,并为上层组件提供支持
  • 每一层中可能包含应用,函数库和框架
  • 一个组件可能会跨越多层,例如Quicktime是一个应用,同时也是图形系统的一部分
  • 终端用户通常只会使用最上层的应用,而开发者会使用更底层的框架构建应用
Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 内核

和其他系统一样,Mac OS X的系统由一个firmware启动。PowerPC中使用的是Open Firmware,而x86则换成了EFI。这两个firmware有着和老式的BIOS类似的功能,但比起BIOS只能用于引导系统相比,Open Firmware和EFI可以自定义启动过程以及诊断启动问题。Firmware在PowerPC上加载一个名叫BootX的bootloader引导Mac OS X启动,在x86上EFI会加载boot.efi。Bootloader会在各自的firmware环境中运行,并最终启动内核。

在bootloader运行后,计算机的控制权就会交给Mac OS X内核。有一些人会把Mac OS X内核称作Darwin,然而Darwin和Mac OS X是不同的两个系统。如前文所说Darwin是一个开源版的Rhapsody系统,也就是Mac OS X的前身。Darwin本身是一个开源软件的集合,有着来自苹果自己的或者NeXT的开源组件,也有GNU或X11这样久负盛名的开源软件。而Mac OS X以Darwin为基础,加入了大量苹果私有的软件,例如Cocoa和Quicktime。而这些又是苹果其他图形化软件的基础。因此,Darwin不能运行Mac OS X的应用,包括Safari和Xcode。然而,由于Darwin是一个完整的Unix内核,使用X11运行Unix软件还是可以的。

真正的Mac OS X内核是XNU,也就是之前所述的Mach为核心,使用BSD环境,并有着面向对象的驱动环境的系统。然而XNU并不会包含驱动代码,驱动会被包装在一个Darwin package中运行。因此严格来讲Mac OS X的内核比XNU的代码还要多。但总的来说,驱动也是XNU的一部分,因此下文会用XNU指代整个内核。内核可以被分为以下几个部分

  • Mach – 底层服务层
  • BSD – 系统程序接口层
  • I/O Kit – 驱动运行时
  • libkern – 内核中的函数库
  • libsa – 通常在系统启动时使用到的内核函数库
  • The Platform Expert – 硬件抽象模块
  • Kernel extensions – 大量使用I/O Kit的驱动和少数不使用I/O Kit的扩展

其中,内核的核心就是Mach。Mach提供了最重要的底层服务,包括

  • 一些硬件抽象
  • 处理器管理,包括多处理器多任务调度
  • 抢占式多任务处理以及对任务(Mach的进程)与线程的支持
  • 虚拟内存管理,包括分页,保护,共享内存和内存继承
  • IPC机制
  • 软实时任务处理,用于支持多媒体程序等对计时敏感的程序
  • 内核debug支持
  • 控制台I/O

尽管网络上很多人会一口认定Mach是一个微内核,但从第一章的历史可以看出Mach3才是一个真正的微内核。苹果使用的正是Mach3,但其实苹果也并没有按照微内核的方式使用Mach3。真正的微内核通常要求只有内核本身在内核地址空间中,其他程序(包括BSD内核和驱动)都在用户空间。而在Mac OS X中,BSD内核和I/O Kit其实都和Mach在同一个内存空间。然而,这些组件都在逻辑上分离以保证不会冲突。

Mach之上是一个BSD系统,但如上文所述BSD内核和Mach内核在某种程度上缠绕在一起,因此BSD在某种程度上依然同时存在在用户空间和内核空间。BSD的职责包括

  • 提供BSD的进程模型
  • 信号
  • 用户系统和全县系统
  • POSIX API
  • 异步I/O(AIO)
  • BSD的syscall
  • TCP/IP网络栈,BSD socket和防火墙
  • 网络内核扩展,将BSD的网络架构融入XNU
  • 大量的文件系统支持,包括virtual file system支持
  • System V和POSIX的IPC机制
  • 内核密码学加密框架
  • FreeBSD的系统通知机制,即kqueue/kevent
  • 使用fsevents的文件系统变化通知机制,被spotlight使用
  • ACL和kauth授权框架
  • 一些同步原语

BSD的一些代码主要是为了保证Unix和Mac OS X的兼容性,因此有一些BSD的机制会被Mach取代。例如尽管BSD的进程模型保留了下来,但BSD并不会调度这些进程。取而代之的是每一个BSD进程都会映射到Mach任务上运行。一些BSD syscall例如fork则会调用Mach的syscall创建新的任务。系统自动确保BSD和Mach的进程/任务数据结构是统一的。因此BSD的进程在Mac OS X里就像胶水一样连起了Mach和Unix。

尽管大多数情况下BSD的syscall会自动转换成Mach的syscall,有时Mac OS X也会直接使用Mach的syscall,也被称作Mach trap,去完成映射内存或复制硬盘等操作。

内核的下一个部分是I/O Kit,也就是驱动框架。I/O Kit是一个使用C++的子集写成的驱动框架。I/O Kit使用的C++没有异常,没有多重继承,没有多个构造器,没有模板,没有初始化列表也没有RTTI(虽然I/O Kit自带一个简单的RTTI)。简单的说就是只保留了最关键的面向对象的部分。

整个I/O Kit由两个内核中的C++库libkern和IOKit以及一个用户空间的框架IOKit.framework组成。整个驱动运行时是模块化的,因此每个I/O Kit的驱动不仅用于驱动硬件,也用来表示多个硬件间的关系。例如硬盘分区是通过多个I/O Kit类之间的关系表示的,包括硬盘,硬盘控制器,总线等等其他一系列驱动。I/O Kit提供以下功能

  • 一系列程序接口,包括对应用程序和用户空间驱动开放的I/O kit接口
  • 连接设备的功能,包括通过ATA/ATAPI,火线,图形接口,HID,网络,PCI和USB连接
  • 面向对象的抽象
  • 热插拔和动态设备管理支持
  • 电源管理
  • 驱动的抢占式调用,多线程,多任务处理和内存保护
  • 动态寻找和加载驱动
  • 一个记录已经初始化的对象信息的数据库(I/O Registry)
  • 一个记录所有支持的I/O Kit类的数据库(I/O Catalog)

标准设备例如鼠标和键盘通常不需要额外的I/O Kit驱动支持,或者只需要一个用户空间驱动,因为I/O Kit已经提供了USB等连接的支持。

Libkern和libsa是内核中的两个函数库。libkern主要提供了I/O kit需要的C++类和一些其他方便内核软件开发的支持类。其中最重要的是OSObject类,这是整个Mac OS X内核其他类的基类。OSObject实现了动态类型和加载内核模块的功能。除此以外libkern还提供了

  • 动态分配,构建和解构支持的对象,包括数组,布尔型和字典
  • 原子操作和辅助函数,比如bcmp(),memcmp()和strlen()
  • 字节操作函数
  • 追踪所有类的每一个实例状态
  • 缓解脆弱基类问题(fragile base-class problem)

脆弱基类问题是C++的常见问题,由于C++的面向对象依赖已知的内存布局(基类的变量位置,虚表等),如果只修改基类而不重新编译用到这个基类的子类可能会破坏子类的行为。libkern允许开发者预留一些空间用于在未来加入新的变量和函数而不会破坏子类。

libsa是一个辅助内核启动阶段和内核模块加载的库。sa指的是stand-alone,也就是加载一个独立的程序-系统内核。Mac OS X的内核拓展通常使用/usr/libexec/kextd加载(在Mac OS 11中已经被System extension取代,kextd和旧的内核模块已经被废弃,但整个Mac OS X系列一直到10.15都还支持)。在系统启动早期kextd还不可用,这时libsa就担当了kextd的一部分功能。libsa的功能有

  • 简单的内存分配
  • 二分检索
  • 排序
  • 各种字符串处理函数
  • 符号remangle
  • 通过依赖图自动寻找内核拓展依赖
  • 解压内核拓展并校验

要注意的是libsa只在启动阶段存在,一旦kextd启动libsa就会被删除。就算在启动阶段libsa的函数也没有一个接口可以被其他程序调用。

最后一个重要组件是Platform Expert,这是一个主板相关的驱动,用于让系统知道现在运行的平台。I/O kit会给Platform Expert注册一个nub。一个nub定义了物理或逻辑设备的接入点或通讯频道。这个设备可以是总线和显卡,或者仲裁器和电源管理驱动。注册完毕后IOPlatformExpertDevice实例会成为设备树的根。这个根nub会加载平台有关的驱动,为每一个总线注册一个nub,然后I/O Kit会给每一个总线nub加载对应的驱动,并通过驱动发现连接的设备,就这样注册每一个设备。

Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 用户空间文件系统构成

尽管Mac OS X的内核非常有趣,大部分时间终端用户和开发者会使用用户空间的程序。而文件系统则是用户能够使用系统的基础。Mac OS Xde文件系统布局主要来自Unix和NEXTSTEP的布局,但也有来自传统的Macintosh的影响。

Mac OS X总的来说有着Unix风格的文件系统,所有的文件都在一个volume下。文件夹布局也类似于Unix,例如在根目录下有/bin,/dev,/etc,/sbin,/tmp,/usr等等Unix系统上可以见到的目录。除此之外,Mac OS X将文件系统分成四个domain:User,Local,Network和System。

User类似Unix系统的home目录,例如一个叫amit的用户对应的home目录即为/Users/amit。一个用户home目录下有一些标准的子目录,例如.Trash,Applications,Desktop,Documents等。一些用户目录例如Public默认公开,因此可以用于和系统中其他用户共享文件。

Local下有着对系统中所有用户都可用的文件,例如共享的应用和文档。这个domain通常在boot volume中,而Mac  OS  X的boot volume通常也是root volume。例如系统默认的安装目录/Application就是Local domain的一部分。虽然所有用户都可以读取Local domain的文件,只有系统管理员可以修改Local domain。

Network中包含了对本地网络中所有用户可见的文件,例如网络中共享的文档。这个domain的内容通常在一个独立的文件服务器中,并被挂载在/Network下。同样只有网络管理员可以修改这个domain的文件。

System顾名思义包含了Mac OS X本身,其中有着系统的函数库,程序,脚本和配置文件。这个domain和Local一样在boot volume或root volume中。在系统搜索一个特定的资源,例如一个字体时,将会以从近到远的顺序搜索,也就是User,Local,Network,如果都没有需要的资源则会在全局的System中搜索。

System domain有着一些重要的文件夹,其中最重要的文件夹之一是Library文件夹。Library中包含了内核和内核拓展的缓存,苹果提供的框架,文件系统和launchd的启动脚本。在/System/Library/CoreServices中包含了系统最基础的组件,例如Dock和Finder程序。其他的一些组件包括System Events,ReportCrash,WifiAgent等基础组件。

Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 运行时环境

现代操作系统通常提供了多种运行时环境,Mac OS X也不例外。然而所有的环境基础都是Mach-O,全称为Mach Object File Format。但这里的Mach并不代表是Mach内核的一部分。事实上这个格式是Mac OS X内核独有的,通过execve加载的二进制格式。这个syscall实现在BSD部分。

Mach-O用于实现各种各样的系统组件,例如

  • bundle(可通过编程加载的代码)
  • 动态链接库
  • 框架
  • umbrella framework(包含多个其他框架的框架)
  • 内核扩展
  • 可链接对象文件
  • 静态链接库
  • 可执行文件

Mac OS X也提供了一些程序用来创建,分析和修改Mach-O文件

  • as – GNU汇编器前端
  • clang,clang++ – clang编译器前端
  • dyld – 动态链接器
  • ld – 静态链接器
  • libtool – 一个可以从Mach-O对象文件创建动态链接库和静态链接库的工具,通常被编译器调用用来创函数库
  • nm – 可以现实符号表的工具
  • otool – 一个可以读取mach-O内部构造的多功能工具,同时也可以反编译

Mach-O文件包含一个固定大小的头部,并跟随着多个大小可变的load命令,紧接着的是更多的segment。每个segment又可以分成一个或多个section。头部定义如下

Mach-O文件头包含了文件的特征,布局和链接特征。filetype有以下几种

  • MH_BUNDLE – bundle类型文件,一段可以通过编程在运行时加载进程序的代码
  • MH_CORE – 内核转储文件
  • MH_DYLIB – 动态链接库
  • MH_DYLINKER – 动态链接器,一个特殊的动态链接库
  • MH_EXECUTE – 课程性文件
  • MH_OBJECT – 对象文件,通常以.o结尾。也被用来做内核拓展

对于可执行程序来说,在Mach-O的文件头中有一个LC_LOAD_DYNLINKER指令用于指定动态链接器位置。目前唯一可用的动态链接器为苹果自己的dyld,位于/usr/lib/dyld。这个文件本身是一个MH_DYLINKER类型文件。内核和dyld一起执行一系列步骤启动一个可执行文件,粗略步骤如下:

  • 内核首先检查Mach-O文件头,确认文件类型
  • 内核执行load指令,例如LC_SEGMENT指令会将程序加载进内存
  • 内核执行LC_LOAD_DYLINKER启动动态链接器
  • 内核调用动态链接器,动态链接器是第一个在程序的地址空间被执行的程序(先于程序本身的代码执行)。内核将Mach-O文件头和命令行参数传递给动态链接器
  • 动态链接器继续执行Mach-O文件头的load指令,根据程序要求将动态链接库加载进内存并将Mach-O的导入符号和动态链接库的定义相连接
  • 动态链接器调用LC_UNIXTHREAD或LC_THREAD load指令指定的程序入口。这通常是语言运行时的初始化函数,这个函数将调用程序真正的main函数。

下面是一个简单的例子,我们有一个C语言程序

编译后通过otool可以看到这个程序的文件头

LC_UNIXTHREAD指向了一个寄存器。这个例子中指向的是PowerPC的srr0寄存器。srr0中包含了一个地址0x000023cc,这就是程序开始的地址。这个地址是一个名为start的函数,由start函数最终调用程序的main函数执行。这个start函数来自C语言运行时库crt1.o,编译器在编译时会链接这个库。

如果在x86计算机上编译这个程序,LC_UNIXTHREAD会指向x86的eip寄存器,其中保存了同样的start函数地址。

在更新的Mac OS X版本中,LC_MAIN取代LC_UNIXTHREAD,但LC_UNIXTHREAD依然可用,因为LC_MAIN需要dyld支持,dyld本身不能使用LC_MAIN。因此dyld依然在使用LC_UNIXTHREAD。

由于Mac OS X支持多个平台(在本书中是PowerPC和x86,现在是x86和ARM),Mac OS X使用胖二进制文件让同一个程序可以在不同平台上运行。这种文件被苹果称作Apple Universal Binary

胖二进制文件本质上只是打包了不同平台的二进制文件,并有一个特殊的fat_header头,这个文件头中记录了在这个文件中包含了几个不同平台的二进制文件。在fat_header后是一些fat_arch头,每一个fat_arch头记录了对应的平台信息和偏移量。内核可以根据偏移量找到需要的二进制文件的位置并加载。

在Mac OS X中,动态链接是默认的,所有正常用户空间的程序都是动态链接的。事实上苹果不支持静态链接用户空间的程序(Mac OS X没有带静态链接库)。动态链接其中一个原因是C库和系统之间的ABI是私有的,也就是说syscall的trap指令不应该被用户空间程序直接调用。但是内核拓展却不能动态链接,因为内核拓展是对象文件而不是可执行文件。

otool可以显示一个文件需要的动态链接库是哪些。比如下面这个例子显示了launchd使用的动态链接库。有趣的是launchd是用户空间的第一个程序(pid 1),在正常的Unix系统中通常是静态链接的

Mach-O的运行时还有其他特性,第一个是多种动态库的连接方式。分别是懒绑定 lazy binding,也叫Just-In-Time binding和加载时绑定load-time binding。此外还有一个pre binding,在10.4时被废弃。lazy binding只会在一个动态链接库被第一次调用时才会被加载,并且符号不会立刻和动态链接库绑定,只有在第一次使用时才会被绑定。load-time binding会在加载时一次性绑定所有符号。pre binding稍有不同,是在程序编译时就解析所有用到的符号并为动态链接库预留出空间,动态链接器只需要在运行时根据编译生成的符号加载函数即可。这会加快动态链接的速度,但也需要程序保证预留的空间不会被其他内存覆盖。在10.4之后dylib已经经过优化,使pre binding带来的性能优势变得十分微弱,因此在之后被废弃。

第二个特性是双层命名空间。使用哪一个函数不仅由符号名决定,也由这个函数所在的动态链接库决定。因此动态链接库默认情况下不能在程序运行前使用DYLD_INSERT_LIBRARIES环境变量加载(例如如果我想用自己写的my_malloc代替系统的malloc,由于双层命名空间的存在我的函数命名类似my_malloc::malloc,而系统的则是malloc::malloc,程序还是会使用系统的malloc,但dyld也提供了解决方法__interpose)。可以使用DYLD_FORCE_FLAT_NAMESPACE关闭双层命名空间。然而很多动态链接库会有重名的函数,这样做可能会导致程序不能正常运行。

第三个特性是弱引用符号。通常情况下当一个符号不存在时程序会崩溃,然而如果一个符号是弱引用的,动态链接器如果发现这个符号不存在只会返回一个NULL。因此调用的程序需要在调用前检查函数是否存在。下面是一个弱引用的例子

上文提到函数替换的问题,dyld支持一个特殊的__interpose指令可以替换一个特定的函数,例如我想使用my_open替代open函数,可以在代码里这样写,此时动态库函数可以被替换

Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 C标准库

在Mac OS X中C标准库被称作libSystem。libSystem事实上是一个很大的函数库,里面包含了数个独立的BSD函数库。为了保持兼容Unix中的标准库,例如libc,会被符号链接到libSystem上。另外还有一些库是libSystem内部使用的。libSystem包含以下库

  • libc – C标准库
  • libdbm – 数据库操作库
  • libdl – 动态链接器的API
  • libinfo – 一些“info” API,例如查询当前的DNS,NIS和网络信息的API
  • libkvm – 提供KVM虚拟机的API,一些实用程序例如ps会使用它
  • libm – 标准数学函数库
  • libpoll – 对BSD的select() syscall的包装,模拟System V的poll() syscall
  • libpthread – POSIX线程库
  • librpcsvc – 一个Sun的RPC服务库

libSystem内部使用的库如下

  • libdyldapis – 一个动态链接器的底层API
  • libkeymgr – 用于维护进程的全局状态,这个状态对所有进程的所有线程和所有动态链接库可见
  • liblaunch – launchd的一些接口
  • libmacho – 一个访问Mach-O文件内部信息的API
  • libnotify – 允许应用通过基于命名空间的无状态通知发送事件信息
  • libstreams – 实现了I/O流机制
  • libunc – 创建和分派用户通知的库

libSystem还包含了一个commpage符号的对象文件。commpage是一块被映射到每一个进程地址空间的特殊内存。这块内存是共享且只读的。commpage中包含了常用的系统函数和数据。commpage的符号包含在libSystem一个特殊段下(在__commpage节的__DATA段下)。因此debugger可以使用这些符号。

Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 Bundle

Bundle和框架是两个Mac OS X中经常出现的抽象。大部分用户空间的功能被实现为框架,而框架是一种特殊的bundle。

一个bundle是一系列相关的资源的集合,内部有文件夹的结构。在bundle中可以有可执行文件,动态链接库,插件,头文件,图片,音频,文档和其他bundle。因此bundle非常适合打包,部署,维护和使用一个软件。Mac OS X最常见到的bundle之一是应用bundle。一个应用bundle本质上是一个文件夹,以.app结尾。大部分Mac OS X应用都不只是一个简单的可执行文件。例如一个应用有图标,可能在使用时播放音频,使用动态链接库模块化开发。应用可能还会有插件功能,允许其他开发者拓展应用的功能。Bundle非常适合这种应用,因为它可以把应用结构清晰地规划。例如iTunes.app就包含最主要的iTunes可执行文件,一个可以被载入的插件bundle,一些图标,媒体文件,一个辅助应用,本地化文档还有很多其他东西。所有的东西都被方便地打包金iTunes.app文件夹,而不是散落在系统各处。对于这样的bundle,安装和卸载也非常方便,只需要将.app拖入Application文件夹或直接删除就可以完成安装和卸载。

Finder会把一些类型的bundle看作一个文件而不是一个文件夹。例如双击应用bundle就会启动应用而不是进入文件夹。用户可以右击bundle并从上下文菜单中进入bundle内部。有一些Mach-O类型文件会在bundle中出现。Bundle可以使用CFBundle和 NSBundle API 访问。

很多插件都是以bundle的形式出现在Mac OS X中。总的来说,一个插件是一个在宿主环境中匀性的一段外部代码。宿主环境通常是一个应用,但也可以是操作系统或者另一个插件。这个宿主环境需要有可拓展性,也就是说它需要暴露一些API供插件调用。因此使用宿主环境的API,插件可以在不修改宿主环境的代码的情况下增加功能。

要注意的是表中的拓展名只是约定,只要使用bundle的程序知道这个bundle会包含什么,bundle的拓展名可以是任何拓展名。

Posted in OSX Internals笔记 | Leave a comment

第二章-OSX概览 plist文件

在Mac OS X中一个经常看到的文件是Property List(plist)文件。这些文件是Core Foundation框架使用的结构化数据在硬盘上存储的形式。在plist被读入内存后会被转换成Core Foundation框架的原生数据类型。这些类型有CFArray,CFBoolean,CFData,CFDate,CFDictionary,CFNumber和CFString。这些类型在Mac OS X的不同子系统都是portable的。例如在I/O Kit中这些CF类型都有对应的I/O Kit类型。

由于plist本质上是CF数据结构的序列化,它可以是二进制存储或者是使用XML存储。在Mac OS X中bundle和应用是plist使用最多的地方。Bundle使用一种名叫information plist的plist类型存储重要的属性。这类plist通常被称作Info.plist。例如一个应用包含的Info.list会存储以下属性

  • 应用处理的文档类型
  • 应用的可执行文件名称
  • 应用的图标位置
  • 应用的UUID
  • 应用的版本

除了Info.plist以外应用可以自定义新的plist用于存储用户数据。例如Safari会把书签存入Bookmarks.plist文件。在~/Library/Preferences/目录下可以找到很多类似的plist类型的配置文件。通常这些plist命名方法是倒过来的DNS名称,例如Safari的配置名是com.apple.Safari.plist。

在Mac OS X中有很多工具可以创建或修改plist。例如plutil命令行工具可以转换plist的类型或者检查plist语法错误。XML类型的plist文件可以直接用文本编辑器编辑,但更方便的是使用Property List Editor图形编辑器。Cocoa和Core Foundation框架也都提供API可以让程序修改plist文件。这些框架里有多个标准对象类型也都是以plist存储的。

Posted in OSX Internals笔记 | Leave a comment