贯穿始终:launchd
当你打开Mac 或 i-设备的电源时,引导加载器(OS X:EFI, iOS: iBoot)开始查找内核并且启动内核。不过内核只是一个服务提供者,而不是具体的应用程序,用户态的应用程序才是系统负责真正工作的实体,应用程序构建在内核提供的原语之上,向用户提供了丰富的用户态环境,包括文件、多媒体和用户交互。整个用户环境都必须从某个地方启动,在 OS X 和 iOS 中,用户环境始于 launchd。
launchd
OS X 和 iOS 中的 launchd 对应于其他UN*X 系统中的init。 作为系统中的第一个用户态进程,负责直接或间接地启动系统中的其他进程。此外, launchd 还有OS X 和 iOS 特有的特性。尽管launchd 是属于苹果的私有财产,但是仍然属于 Darwin 的范畴,因此launchd 也是完全开源的。
launchd 是有内核直接启动的。负责加载 BSD 子系统的主内核线程创建一个线程来执行 bsdinit_task。这个线程获得 PID 1,并且临时命名为“init”,这是一项来自BSD的遗产。bsdinit_task 然后调用 load_init_program( ), 这个函数调用 execve( ) 系统调用(在内核空间中执行)执行守护程序,init_program_name 变量的值被硬编码为/sbin/launchd 这个名称。
系统范围和用户范围的launchd
- 系统范围的lanuchd(PID 1)是不可终止的。事实上,这个lanuchd是系统上唯一不朽的进程,这个进程是不能被杀掉的。当系统关闭是,launch 也是最后一个退出的进程
- 用户范围的launchd 是用户登录的时候执行的,通过SSH 远程登录也会创建launchd(每登录一个就创建一个)。在iOS 上只有一个launchd实际,即系统范围的实例
守护程序和代理程序
launchd 的核心职责是根据预定的安排或实际的需要加载其他应用程序或作业,launchd 区分两种后台类型的作业:
- 守护程序(deamon):和传统的UNIX 概念一样,是后台服务,通常和用户没有交互。守护程序由系统自动启动,不考虑是否有用户登录进系统
- 代理程序(agent):是一类特殊的守护程序,只有在用户登录的时候才启动,和守护程序不同之处在于:代理程序可以和用户交互,有的代理程序还有GUI
- iOS 不支持用户登录的概念,因此只有 LaunchDaemon(不过存在一个空的/Library/LaunchAgents)
- 守护程序和代理程序都是通过自己的属性列表文件(.plist)声明的,Launchd使用/private/var/db 目录保存运行时配置文件,创建 com.apple.launchd[.peruser.%fd]文件覆盖或禁用守护程序的配置
多面手 launchd
launchd 是用户态出现的第一个进程。当系统还在启动初期的时候,launchd 是系统上唯一的进程(尽管这个状态很短暂)。这意味着系统启动和功能的方方面面都和launchd 直接或间接相关。在 OS X 和 iOS 中,launchd 要身兼数职,而其他UN*X 则会将这些职责委派给多个守护程序。
** init**
launchd 的第一个也是最主要的职责是init 守护程序。 init 的职责是派生出各种各样的后台守护程序,设置好系统,然后转入后台,确保这些守护程序都活着。如果有程序死亡了,launchd 要负责重新派生出新的守护程序。用户范围内的初始化
通过使用LaunchAgents,launchd 允许用户加载指定的应用程序。代理程序本身可以请求默认所有会话中加载,或只在GUI会话中加载:将LimitLoadToSessionType 键设置为LoginWindow或Aqua,或者Background。atf/crond
UN*X 系统上包含两个守护程序:atd 和 crond 来运行定时作业。即在指定时间运行指定的命令。第一个守护程序 atd 负责第一次运行的作业, 是 at( ) 命令的后端引擎。第二个守护程序 crond 提供了重复执行作业的支持。
苹果正在逐步废弃 atd 和 crond 。 atd 再也不是一个独立的守护程序,而是launchd 启动的程序。 这个服务通常都是禁用的。inetd/xinetd
在 UN*X中,inetd(及其继任 xinetd)的用途是启动网络服务。这个守护程序的职责是绑定一些端口(TCP 端口 或 TCP 端口),当有连接请求到达的时候,根据需要启动相应的服务程序,并且将服务程序的输入输出描述符(stdin、stderr 和 stdout)连接带对应的套接字。
launchd 将inetd 的功能结合到自身,允许守护程序和代理程序请求某个指定的套接字。守护程序只需要在其 plist 文件中设置Sockets 键即可。和inetd 不同之处在于:守护程序请求的套接字也可以UNIX domain 套接字。iOS 守护程序lockdownd 通过这种方式监听TCP 端口 62078 和 UNIX 套接字/var/run/lockdownd.sock。mach_init
由于OS X 来源于 NeXTSTEP,在 OS X 10.4 引入 lanunchd 之前,系统启动过程是由 mach_init 完成的。这个守护程序负责派生 BSD 风格的init,后者是一个独立的进程,现在二者合二为一,成为了launchd。 launchd 继承了 mach_init 文档少,但是主要负责自举服务管理器的职责。自举服务器可以被系统中的所有进程访问,其他进程通过指定端口 bootstrap_port 和自举服务器通讯。在launchd 出现之前,mach_init 就负责 bootstrap_server 的职责。现在 launchd 接替了这个职责。在启动的时候声明了相应的端口(bootstrap_port)。如果服务端口可用,而且服务程序已经完成登记,那么这个服务端口会返回给客户程序,客户程序可以通过这个端口发送和接收信息(利用mach_msg( ))。这个自举机制现在是通过 launchd 的 vproc 实现的。事务支持
launchd 比其他的init 更聪明。 init 只知道启动和停止守护程序,而 launchd 还支持事务。事务是 launchd 的 vproc 库引入的新特性。
守护程序通过 vproc_transaction_begin 调用生成一个事务句柄,当事务操作结束时对这个句柄调用 vproc_transaction_end, 这两个调用之间包含的操作称之为未决的事务。启用事务的守护程序可以在 plist 中设置 EnalbeTransactions 键,如果设置了这个键,launchd 就会在系统关闭、用户退出或超过指定时间后检查未决 的事务。如果没有未决的事务(进程干净),那么守护程序就会被直接杀掉(kill -9), 而不是优雅地终止(kill -15),从而加快关闭或退出的过程,或者在一定程度的不活动状态之后释放掉系统资源。资源限制和扼制(throttling)
launchd 可以对其作业施行资源限制。一个作业(守护程序或代理程序)可以指定HardResourceLimits 或 SoftResourceLimits 字典,这样会导致 launchd 调用 setrlimit(2)。Nice 键可以用于设置作业的nice 值,等同于调用 nice( )。此外,作业还可以指定LowProtityIO 键,导致launchd 调用iopolicysys,降低作业的I/O优先级。最后,luanchd 还整合了iOS的Jetsam机制(也称为memorystatus),Jetsam机制可以强制施行虚拟内存使用率的限制,这项特性在没有交换空间的iOS 中特别重要。Autorun模拟和文件系统观察
Windows 有一个著名(往往很恼人)的功能是autorun,在插入可以的媒体(例如:CD、U盘或硬盘)的时候自动启动一个程序。launch 提供了StartOnMount 键,当一个文件系统挂载的时候自动触发启动一个守护程序。这不仅能模拟Windows 的 autorun 功能,而且更加安全。launchd 的守护程序从持久化文件系统中运行,而不是从可移除的文件系统中运行。通过WatchPaths 或 QueueDiretories 键,launchd 还可以设置为观察一个特定路径的变化,这一项功能呢过非常有用,因为 launchd 可以实时响应文件系统的变化。这一项功能是通过监听内核事件(kqueue)实现的。守护程序还可以进一步支持FSEvents,方法是知道一个 LaunchEvents 字典,其中带有一个保存了匹配情况的 com.apple.fsevents.matching 字典。I/O Kit 整合
Lion 中的一项新特性就是在 launchd 中整合了 I/O kit。 I/O Kit 是设备启动程序的运行环境,守护程序或代理程序可以请求当某个设备价值的时候运行,方法是知道一个LaunchdEvents 字典,其中包含一个 com.apple.iokit.maching 字典。
LunchDaemon 列表
在 OS X 和 iOS 中有大量的LaunchDaemon。所有的plist文件(包括Mach服务条目)都以com.apple作为前缀,二进制可执行文件通常放在/usr/libexec 目录下。
在iOS 中有两个特殊的守护程序:SpringBoard 和 lockdownd。
lockdownd
lockdownd 就行用户态的狱警,是所有越狱者的头号敌人。lockdownd 由 launch 启动,负责设备激活、备份、崩溃报告、设备同步以及其他服务。lockdownd 注册 com.apple.lockdown.host_watcher Mach 服务,监听 TCP 端口 62078 以及 UNIX domain 套接字 /var/run/lockdown.sock。还有一个小程序/usr/libexec/lockbot 辅助lockdownd。lockdownd 实际上是一个迷你launchd. lockdownd 在 /System/Library/Lockdown/Services.plist 文件中维护了一个自己要启动的服务列表。这个列表中有一个重要的服务:afc,这个服务负责在 iTunes 主机和 i- 设备之间传递文件。lockdown 和 luanchd一样 以root 用户权限预先,在运行其他进程之前,如果指定了UserName 键,则会降低进程的权限。SpringBoard
SpringBoard 是GUI Shell。
GUI Shell 程序
当用户在控制台登录(自动登录或通过输入凭据登录)的时候,系统会启动一个图形shell婚假,OS X 使用Finder,而iOS 使用的是SpringBoard,不过这两个程序其实不像表面上看上去差别那么大。从launchd 的角度看。Finder 和 SpringBoard 只不过是要处理的上百个守护程序和代理程序中的一两个,但是从用户的角度来看,这些程序就是它们和操作系统接触的第一个界面(通常也是最后一个)。Finder(OS X )
OS X 中的Finder 等同于 Windows 在的 Explorer:向用户提供图形shell 的功能。成功登录之后,launchd 以代理程序的方式启动Finder。Finder 和系统的结合非常紧密,因此原生的文件系统HFS+也是基于Finder 构建的。文件和文件夹数据,甚至宗数据本身都包含了特殊的Finder 信息字段。Finder 还利用扩展属性保存信息,例如色彩标签和替身(alias)。SpringBoard(iOS)
iOS 中的SpringBoard 相当于 OS X 中的Finder。在iOS 中不需要登录系统,因此SpringBoard 自动启动。SpringBoard 向用户提供熟悉的图标界面。
XPC(Lion 和 iOS)
XPC是Lion 和 iOS 5 新引入的轻量级进程间通讯原语,XPC 和 GCD 的结合非常紧密,XPC 允许开发者将应用程序分解为独立的组件。这样可以同时增强应用程序的稳定性和安全性,因为不稳定的功能可以包含在一个XPC服务中,而XPC服务可以在外部进行管理,这是launchd非常适合承担的另一个责任。
就想对待LaunchDaemons 一样,launchd 承担了按需启动、监视(崩溃的时候重新启动)以及终止(服务完成或闲置的时候,通过粗暴的方式kill -9)XPC服务的任务。launchd 通过xpcd(8)、xpchelper(8) 和 xpcproxy(8) 辅助 XPC 的服务。launchd 和标准的 Mach 服务一起管理 XPC 服务, XPC 服务在独立的XPC域中:per-user、private、singleton。
XPC 服务程序和客户程序都链接了libxpc.dylib(有的是直接链接,有的是通过 Cocoa 链接),libxpc.dylib 提供了各种各样的C语言层次的XPC 原语(例如Mountain Lion的 NSXPCConnection)。XPC 还依赖于两个私有框架:XPCService 和 XPCObjects。前者负责处理XPC服务运行时相关的事务,后者为XPC 对象提供编码和解码服务。iOS 还有一个包含私有框架 XPCKit。
XPC 对象类型
XPC对各种数据进行包装盒序列化,这种方式类似于CoreFoundation框架。任何类型的XPC对象都可以处理为不透明的类型 xpc_object_t, 并且通过 xpc_object(3)文档中描述的函数进行操作。这些函数包括xpc_retain/release、xpc_get_type、xpc_hash(提供对象的散列值,可以用于数组索引)、xpc_equal(用于比较对象)和xpc_copy。XPC 消息
对象可以通过消息来发送和接收。默认情况下消息是异步发送的,并且通过分发队列(GCD)处理。通过使用屏障(barrier),开发人员可以指定一个代码块在某个连接上的所有消息都发生完成之后执行。发出器的消息可以有应答,应答也是异步的,通过_reply_sync 函数可以阻塞直到收到应答消息。XPC消息是通过Mach 消息机制实现的,并且使用了Mach Interface Genetator(MIG)实施。后者提供了xpc_domain子系统。xpc_domain 子系统包含用于登记、加载或添加服务以及获得服务名称的消息。 下面是xpc_connection_send_message 流程:
XPC 服务
XPC 服务可以通过Objective-C 或C或C++创建。不管通过哪种语言创建,都需要调用 libxpc.dylib 库的 xpc_main 函数开始服务。C/C++ 的 服务 mian 函数只不过是一个简单的包装函数,它调用xpc_main,并传入时间处理函数(xpc_connection_handler_t)。Objective-C服务也调用xpc_main,不过是通过NSXPCConnection_t的resume方法间接调用的。
事件处理函数接受单独一个参数:xpc_connection_t(Objective-C 将这个对象封装为Foundation.framework 框架中的 NSXPCConnection)。XPC 连接是一个不透明的对象,需要通过xpc_connection_*函数进行操作。
XPC服务程序的一般架构包括:调用dispatch_queue_create 创建一个队列用于接收来接收自客户程序的消息,然后通过xpc_connectiona_set_target_queue 将这个队列分配给连接。服务程序还有设置连接的时间处理程序:调用 xpc_connection_set_event_handle 并提供一个表示处理程序的代码执行(代码本身也可以包装其他函数)。每当服务程序收到一条消息的时候都会调用这个处理程序。服务程序可以创建一个应答(通过调用 xpc_dictionary_create_reply)并将应答消息发送出去。XPC 属性列表
XPC 的服务定义在自己的bundle 中,这个 bundle 包含在其父应用程序或框架的XPCServices 子目录中。和所有的bundle 一样,XPC 服务的bundle 也有一个Info.plist 文件,这个文件的作业是声明各种服务属性和需求:CFBundlePackageType 属性定义为“XPC!”
CFBundleIdentifier 属性定义了XPCService的名称。这个名称设置为何bundle名称一样的值。
** XPCService** 属性定义了一个字典,这个字典包含一个ServiceType 属性(可取值为Applicaton、User和System)以及RunLoopType(可取值为dispatch_main和 NSRunLoop),RunLoopType定义了xpc_main( ) 的运行循环风格。这个字典可能还包含一个JoinExit\stingSession布尔属性,将XPC服务的审计重定向到应用程序已有的审计会话。
XPCService 字典还可以用于指定其他的属性,采用下划线作为前缀。其中包_SandboxProfile(允许指定XPC服务采用的一个沙盒profile),_AllowedClients(指定允许连接到服务的应用程序标识符)