Mac OS X 进程间通信与运行时环境解析
1. 进程间通信概述
在操作系统中,进程常常需要与其他进程进行通信,可能是为了传输数据,或者告知其他进程自身的状态变化。然而,现代操作系统中进程间的通信并非易事,如果设计和执行不当,可能会影响系统的稳定性和性能。
在 Mac OS X 中,程序有多种进程间通信的方式,这些通信机制分布在系统的不同层次,各有其特定的用途、限制和适用场景。以下是 Mac OS X 中存在的进程间通信机制:
- 对于大多数进程间通信,应用程序应优先选择 Apple 事件。
- 分布式通知可用于向同一机器上的所有应用程序广播简单通知。
- 在对性能要求较高的情况下,可考虑使用 Core Foundation 的 CFMessagePort 机制替代 Apple 事件。
- 使用 BSD 套接字进行网络通信。
- BSD 管道可用于同一计算机上的原子单向通信。
- BSD 信号由内核调用,用于向进程传达异常信息。
- 结合共享内存和 POSIX 信号量,可与其他进程共享图片、声音或电影等大型资源。
- NSPasteboard 是一个 Cocoa 类,允许对公共可共享数据进行简单的运行时持久存储。
- Cocoa 应用程序可以使用服务功能,通过服务菜单向其他应用程序提供服务。
- Cocoa 应用程序还可以使用分布式对象向同一计算机上其他线程或进程中的对象发送消息。
- Mach 端口对象是 Mac OS X 中所有进程间通信的底层原语。
2. 具体通信机制详解
2.1 Apple 事件通信
Apple 事件是一种高级语义事件,应用程序可以向自身、同一计算机上的其他应用程序或远程计算机上的应用程序发送。它是 Mac OS X 上应用程序间通信的主要方法。
Apple 事件对象具有明确的数据结构,支持可扩展的分层数据类型。应用程序通常使用 Apple 事件向其他应用程序请求服务和信息,或响应此类请求提供服务和信息。开发者可以定义自定义事件,但为了提高与其他应用程序的互操作性,建议采用 Apple 文档中规定的标准 Apple 事件集。
除了“保存文档”和“打开文档”等低级文档管理任务外,许多功能领域(如文本处理和数据库管理)都定义了标准的 Apple 事件套件和相关数据结构。一个定义良好的 Apple 事件套件还可以通过 AppleScript 支持丰富的脚本接口。
不过,创建 Apple 事件对象可能需要大量时间,因此在对性能要求较高的情况下,通常不建议使用。为了提高 AppleEvent 的创建性能,可以尝试使用 Carbon AEBuild 和 AEStream 实用程序,它们通常比 AEPutDesc 和 AEPutParam 快得多。
2.2 广播简单通知
分布式通知是任何进程向每台机器的通知中心发布的消息,通知中心会将消息广播给所有感兴趣的进程。通知中包含发送者的标识符,还可以选择包含一个字典,其中包含额外的信息。分布式通知机制由 Core Foundation 的 CFNotificationCenter 对象和 Cocoa 的 NSDistributedNotificationCenter 类实现。
分布式通知适用于简单的通知类事件,例如传达某些关键硬件(如网络接口或排版机)的状态。但它无法限制允许接收通知的进程集合,任何注册了特定通知的进程都可能收到。由于分布式通知使用字符串作为唯一注册键,因此存在命名空间冲突的可能性。而且,分布式通知是真正的通知,接收者无法回复。
2.3 使用 CFMessagePort 传输原始数据
CoreFoundation 的 CFMessagePort 对象实现了一种快速、高效的机制,用于在同一机器上的进程之间传输原始数据。如果 Apple 事件的速度无法满足需求,可以考虑使用 CFMessagePort。
2.4 BSD 套接字通信
套接字支持任意数量进程之间的双向通信。套接字是一种将地址与文件描述符关联起来的对象,在 Mac OS X 中,所有网络通信都应使用套接字。
套接字主要有两种类型:文件套接字和网络套接字。文件套接字以文件名作为地址,由于各种原因,它不支持不同机器上进程之间的通信。网络套接字使用网络主机名和端口号(例如,www.apple.com 和 80)作为地址。两种类型的套接字都可以使用 POSIX 调用 read 和 write 进行读写操作。
Core Foundation 框架包含了套接字的抽象(CFSocket/CFRunLoop)。使用 CFSocket 而不是原始套接字 API 调用,Core Foundation 可以抽象出不同操作系统之间的潜在差异,并提供更面向对象的接口,使编程更加容易。使用 CFSocket 和 CFRunLoop 可以将从套接字接收的数据与从其他源接收的数据进行多路复用,从而将应用程序中的线程数量保持在最低限度,有利于提高性能。CFMessagePort 也可以与 CFRunLoop 一起工作。
以下是一个简单的套接字通信流程:
graph LR
A[创建套接字] --> B[绑定地址]
B --> C[监听连接(服务器端)/发起连接(客户端)]
C --> D[接受连接(服务器端)/建立连接(客户端)]
D --> E[数据读写]
E --> F[关闭套接字]
2.5 BSD 管道通信
管道是一种具有发送端和接收端的通信通道,写入管道的数据按先进先出(FIFO)顺序读取。要从管道读取或写入数据,管道的读写两端都必须打开。
无名管道必须由一个共同的祖先进程创建,然后将管道描述符编号传递给两个子进程。命令行 shell 通常使用此功能来连接通过管道连接在一起的进程(例如,“cat magic.txt | grep -e Gwendoyln” 通过 C 库控制台输入流将 magic.text 的内容发送到 grep 命令)。
命名管道由文件系统中的一个名为 FIFO 特殊文件的文件表示。命名管道必须使用发送和接收进程都知道的唯一名称创建。
如果写入的数据大小低于内核指定的某个大小,向管道读写少量数据可以原子地进行,这可以避免命名管道的接收端读取部分缓冲区。
2.6 处理 BSD 信号异常
信号是可以在指定进程上调用的软件中断。系统提供的默认信号处理行为通常会在接收到信号时立即终止进程。进程可以通过安装信号处理程序例程来覆盖此行为。
信号最常见的用途是内核使用它们来通知进程异常情况,如无效地址错误和除零错误。另一个常见用途是命令行 kill 工具,它能够向进程发送任何用户指定的信号,最常见的用途是使用 SIGHUP 终止进程。
然而,有效地使用信号很复杂,并且它们在不同操作系统上的行为可能不同(有时不可靠)。信号命名空间由单个整数组成,因此是有限的,可能会与第三方信号编号或操作系统未来版本提供的编号发生冲突。因此,在正常的进程间通信需求中,通常应避免使用信号。
2.7 共享内存共享大型资源
共享内存是一个进程专门为多个进程可读且可能可写而分配的内存区域。该区域被映射到每个有权访问它的进程的地址空间中。对该内存区域的访问通过 POSIX 信号量进行控制,POSIX 信号量实现了一种锁定机制。
与其他形式的进程间通信相比,共享内存有两个明显的优点:
- 任何具有适当权限的进程都可以读写共享内存区域。
- 数据不会被复制,因为任何进程都可以直接读取它。
但共享内存非常脆弱,当共享内存区域中的数据结构损坏时,引用该数据结构的所有进程也会受到影响。因此,共享内存最好仅用作原始数据(如原始像素或音频)的存储库,而控制数据结构则通过更传统的进程间通信方式进行访问。
2.8 向其他应用程序提供服务
“标准服务”功能允许 Cocoa 应用程序向其他应用程序提供其功能,而无需其他应用程序事先知道提供了哪些功能。提供服务的应用程序会宣传它可以对特定类型的数据执行的操作(例如,加密所选文件或打开所选 URL)。任何使用服务功能的应用程序都可以通过其服务菜单自动访问该功能。它无需事先知道操作是什么,只需指出它拥有的数据类型,服务菜单就会提供适用于这些类型的操作。因此,服务功能为 Cocoa 应用程序提供了一种开放式的方式来扩展彼此的功能。数据传输通过剪贴板进行,可以是单向或双向的。
2.9 使用分布式对象调用其他进程
Objective - C 语言运行时支持一种称为“分布式对象”的进程间消息传递解决方案。此机制使 Cocoa 应用程序能够调用不同 Cocoa 应用程序中的对象。调用可以是同步的,即发送进程在等待接收者的回复时被阻塞;也可以是异步的,即不期望回复,发送者不会被阻塞。
2.10 Mach 端口对象消息传递
Mach 端口对象实现了一种标准、安全且高效的进程间消息传递结构。Mac OS X 上的所有其他进程间通信原语在某种程度上都使用了 Mach 端口对象。
由于发送和接收进程是独立调度的,因此不能保证给定进程在任何给定时间都能自由接收发送给它的消息。因此,到达的消息会被放入队列中,并在接收进程方便时进行检索。
进程在没有适当的访问权限(在 Mach 术语中称为“端口权限”)的情况下,不能访问给定的端口。Mach 内核的固有稳定性部分归因于这种机制。
不过,如果有其他替代方案,不建议直接使用 Mach 消息传递,因为内核未来版本中的接口可能会发生变化。需要注意的是,Mach 端口对象与 BSD 套接字中使用的互联网地址端口号以及 Carbon 图形端口原语(GrafPort)无关,不应混淆。
3. 运行时环境与可执行格式
3.1 运行时环境概述
运行时环境(或简称为运行时)是一组约定,它决定了代码和数据如何加载到内存中并进行管理。Mac OS X 支持两种主要的运行时环境:dyld(动态链接编辑器)和 CFM(代码片段管理器)。在一个系统上存在多个运行时环境会带来一个棘手的问题,即如何让为一个运行时环境准备的代码访问为另一个运行时环境准备的代码。
3.2 CFM 和 dyld 对比
CFM(代码片段管理器)和 dyld(动态链接编辑器)都是库管理器,负责将一个或多个代码和数据容器(或模块)映射到内存中并为执行做准备。它们主要通过尝试解析对外部定义符号的引用来为执行做准备,这些符号通常在构建时与容器链接的共享库中定义。
两者的主要区别在于解析这些引用并将它们绑定到适当库中的地址的时间。CFM 采用静态方法,它将每个代码和数据容器(称为片段)作为一个单元(称为闭包)进行准备。在构建时,CFM 通过确定各种引用符号在运行时的位置来最终确定可执行文件。而 dyld 库管理器则尝试在运行时解析所有未定义的符号。更具体地说,符号仅在程序执行期间被引用时才被解析。dyld 管理器仅在需要时链接动态共享库中的代码模块。
| 对比项 | CFM | dyld |
|---|---|---|
| 符号解析时间 | 构建时 | 运行时 |
| 代码模块链接方式 | 静态 | 动态,按需链接 |
3.3 PEF 和 Mach - O 可执行格式
CFM 和 dyld 库管理器都期望它们准备执行的代码和数据容器采用特定的可执行文件格式。可执行格式是机器可读(可执行)代码的打包约定。对于 CFM,这种格式称为 PEF(首选可执行格式);对于 dyld,这种格式称为 Mach - O(Mach 对象文件格式)。
PEF 和 Mach - O 在很多方面相似,它们都为代码、全局数据、非常量数据等定义了节(或段)。它们的主要区别在于对多个容器的支持。PEF 是一种容器(片段)格式,与可执行文件一一对应。而在 dyld 环境中,一个可执行文件可以由多个 Mach - O 容器(对象文件)组成。
3.4 代码生成模型差异
CFM 运行时和 dyld 运行时之间不兼容的真正原因是它们的代码生成模型使用了不同的外部调用约定。这些差异影响了 C 函数指针的表示和全局数据的访问方式。
- CFM 代码生成模型使用指向 TVector 的指针作为函数指针的基础,通过 R2 寄存器间接访问全局数据,将其用作全局数据的基指针,这种访问方法称为 TOC。
- dyld 代码生成模型使用简单的代码指针作为函数指针的基础,相对于代码访问全局数据,使用基地址的偏移量,这种访问方法称为 GOT。
3.5 向量库的作用
Mac OS X 上的所有系统框架都基于 dyld 和 Mach - O,其中一些框架包含 Carbon API。因此,如果有一个基于 CFM 的 Carbon 应用程序或库,代码需要调用这些系统框架中的函数。
Apple 通过一种称为向量库的技术,使基于 CFM 的代码能够调用基于 dyld 的框架中的函数。向量库充当包含 Carbon API 的系统框架的桥梁。这座桥梁的一部分是一个向量或跳转表,它提供“粘合”代码来处理代码生成模型的差异。基于 CFM 的客户端(应用程序或库)可以使用这些向量库,从而访问相关基于 dyld 的框架中的 Carbon API。
Carbon 开发人员只要代码被定义为 Carbon 的一部分,就无需进行任何特殊操作来访问系统框架中的代码。要利用向量库的桥接技术,他们只需链接 CarbonLib SDK 中的存根库。
需要注意的是,向量库不能反向桥接,即从 dyld 应用程序或框架到 CFM 库。虽然可以使用 CFPlugIn 从 dyld 调用 CFM,但这种解决方案并不适用于所有情况。一般来说,如果希望库能在所有 Mac OS X 执行环境中可用,建议将其构建为基于 dyld 的库。
综上所述,Mac OS X 提供了丰富的进程间通信机制和多样化的运行时环境,开发者需要根据具体的应用场景和需求,选择合适的通信方式和运行时环境,以实现高效、稳定的应用程序开发。
Mac OS X 进程间通信与运行时环境解析
4. 不同进程间通信机制的适用场景分析
在实际开发中,选择合适的进程间通信机制至关重要,它直接影响到应用程序的性能、稳定性和可维护性。下面对各种进程间通信机制的适用场景进行详细分析:
| 通信机制 | 适用场景 | 不适用场景 |
|---|---|---|
| Apple 事件 | 适用于需要进行高级语义交互、请求服务和信息的场景,如应用程序间的协作、脚本化操作等。例如,一个文本编辑应用向另一个图像编辑应用请求插入图片服务。 | 对性能要求极高的场景,因为创建 Apple 事件对象耗时较长。 |
| 分布式通知 | 适合简单的通知类事件,如硬件状态通知、系统状态变化通知等。例如,网络接口状态改变时通知所有感兴趣的应用。 | 需要对接收进程进行精确控制、接收进程需要回复的场景,因为无法限制接收进程集合且接收者不能回复。 |
| CFMessagePort | 当需要在同一机器上快速传输原始数据时使用,如实时数据传输、高性能计算中的数据交互等。 | 跨网络通信场景,因为它主要用于同一机器上的进程通信。 |
| BSD 套接字 | 用于网络通信,包括不同机器上进程间的双向通信,如网络服务器与客户端之间的通信。 | 同一机器上简单的单向通信场景,使用套接字会增加不必要的复杂性。 |
| BSD 管道 | 适用于同一计算机上的原子单向通信,如命令行工具之间的数据传递。例如,将一个命令的输出作为另一个命令的输入。 | 跨网络通信和需要双向通信的场景,管道是单向的且不支持网络通信。 |
| BSD 信号 | 主要用于内核通知进程异常情况,如无效地址错误、除零错误等。 | 正常的进程间数据传输和交互场景,因为信号使用复杂且存在命名空间冲突风险。 |
| 共享内存 | 适合需要共享大量数据的场景,如多媒体数据(图片、声音、电影)的共享。 | 对数据一致性要求极高的场景,因为共享内存容易因数据结构损坏影响多个进程。 |
| NSPasteboard | 用于简单的运行时持久存储和剪贴板操作,如应用间的复制、粘贴功能。 | 大量数据存储和复杂数据结构的共享场景,它主要用于简单数据的存储。 |
| 服务功能 | 让 Cocoa 应用程序可以向其他应用提供功能,实现应用间的功能扩展。例如,一个加密应用向其他应用提供文件加密服务。 | 不需要向外提供服务的应用,或者对服务调用性能要求极高的场景。 |
| 分布式对象 | 适用于 Cocoa 应用程序间的消息传递,可实现同步或异步调用。例如,一个应用调用另一个应用中的对象方法。 | 非 Cocoa 应用程序的通信场景,以及对性能要求极高且不需要对象调用的场景。 |
| Mach 端口对象 | 作为底层原语,适用于对安全性和稳定性要求极高的系统级通信。 | 普通应用开发中,如果有其他替代方案,不建议直接使用,因为接口可能变化。 |
5. 运行时环境选择的考量因素
在选择 dyld 或 CFM 运行时环境时,开发者需要综合考虑多个因素:
5.1 兼容性
- 如果需要与现有的 CFM 代码兼容,或者使用一些依赖 CFM 环境的旧库,那么可能需要选择 CFM 运行时。
- 对于新开发的应用,尤其是需要与现代系统框架和新的功能特性集成的应用,dyld 运行时是更好的选择,因为 Mac OS X 上的所有系统框架都基于 dyld 和 Mach - O。
5.2 性能
- dyld 的动态链接特性使得它在运行时可以根据需要加载代码模块,减少了内存占用,提高了启动速度,对于大型应用和动态扩展的应用更具优势。
- CFM 的静态链接方式在某些情况下可能会导致可执行文件较大,并且缺乏动态性,但在一些对性能要求不高且对静态资源管理有特定需求的场景下也有其适用性。
5.3 开发难度
- CFM 的代码生成模型和静态链接方式相对复杂,需要开发者对底层内存管理和符号解析有更深入的理解。
- dyld 的运行时解析和动态链接机制使得开发过程更加灵活,代码的可维护性和可扩展性更好,对于大多数开发者来说更容易上手。
6. 最佳实践建议
6.1 进程间通信最佳实践
- 优先选择 Apple 事件进行应用程序间的高级语义通信,但在性能敏感的部分考虑使用 CFMessagePort 替代。
- 对于简单通知,使用分布式通知机制,但要注意命名空间冲突问题,可以通过合理命名通知标识符来避免。
- 在网络通信中,使用 BSD 套接字,并结合 Core Foundation 的 CFSocket 进行开发,以提高跨平台兼容性和编程便利性。
- 对于同一机器上的单向通信,优先考虑 BSD 管道,确保数据按顺序传输。
- 当需要共享大量数据时,使用共享内存结合 POSIX 信号量,但要做好数据一致性保护。
- 在 Cocoa 应用中,充分利用服务功能和分布式对象机制,实现应用间的功能扩展和消息传递。
6.2 运行时环境选择最佳实践
- 新开发的应用尽量采用 dyld 运行时环境,以充分利用现代系统框架的优势。
- 如果必须使用 CFM 代码,利用向量库技术实现与 dyld 框架的交互,但要注意向量库的局限性。
- 在开发过程中,保持对运行时环境的更新和兼容性测试,以确保应用在不同版本的 Mac OS X 上稳定运行。
7. 未来发展趋势
随着技术的不断发展,Mac OS X 的进程间通信机制和运行时环境也可能会发生变化。
7.1 进程间通信机制的发展
- 可能会出现更高效、更安全的进程间通信协议,以满足日益增长的高性能计算和分布式系统的需求。
- 对跨平台通信的支持可能会进一步增强,使得 Mac OS X 应用能够更方便地与其他操作系统的应用进行交互。
7.2 运行时环境的发展
- dyld 运行时可能会进一步优化动态链接机制,提高应用的启动速度和内存使用效率。
- 对新的编程语言和编程模型的支持可能会更加完善,为开发者提供更多的选择和便利。
开发者需要密切关注这些发展趋势,及时调整开发策略,以适应不断变化的技术环境。
总之,Mac OS X 的进程间通信机制和运行时环境为开发者提供了丰富的工具和选择。通过深入理解各种机制的特点、适用场景和发展趋势,开发者能够更好地利用这些资源,开发出高效、稳定、具有创新性的应用程序。
超级会员免费看

被折叠的 条评论
为什么被折叠?



