0%

逐步分析

Glide.with(……)

Glide.with() 有下面几种实现方式。

1
2
3
4
5
1. Glide.with(Context context)
2. Glide.with(Activity activity)
3. Glide.with(FragmentActivity activity)
4. Glide.with(android.app.Fragment fragment)
5. Glide.with(View view)

所以的方法实现也是很类似,都是调用同一个方法

1
2
3
public static RequestManager with(Fragment fragment) {
return getRetriever(fragment.getActivity()).get(fragment);
}

再看一下 getRetriever() 方法

1
2
3
4
5
6
private static RequestManagerRetriever getRetriever(@Nullable Context context) {
……
省略一些判空检查
——
return Glide.get(context).getRequestManagerRetriever();
}

其中 Glide.get(context) 主要用来初始化 Glide 的全局单利对象,以及一些配置。

getRequestManagerRetriever() 则是返回 Glide 对象的 requestManagerRetriever 对象。

然后看一下 requestManagerRetriever.get() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public RequestManager get(Context context) {
if (context == null) {
throw new IllegalArgumentException("You cannot start a load on a null Context");
} else if (Util.isOnMainThread() && !(context instanceof Application)) {
if (context instanceof FragmentActivity) {
return get((FragmentActivity) context);
} else if (context instanceof Activity) {
return get((Activity) context);
} else if (context instanceof ContextWrapper) {
return get(((ContextWrapper) context).getBaseContext());
}
}

return getApplicationManager(context);
}

get() 方法会根据传入的 context 对象和当前线程,创建不同的 RequestManager 实例

1
2
3
4
5
1. 非 UI 线程,返回 applicationManager 对象,能感知 Application 生命周期。
2. UI 线程,如果 context 是 Activity 、FragmentActivity
则会创建一个能感知对应 Activity 的 RequestManager。
3. UI 线程,如果 Context 是 Fragment、android.support.v4.app.Fragment
则会创建一个能感知对应 Fragment 生命周期 的 RequestManager。

这里反复提到了一个 感知生命 xx 周期,也是 Glide 的一个特性。

1
2
3
Glide 在加载资源的时候,如果是在 Activity、Fragment 这一类有生命周期的组件上进行。
当 Activity、Fragment 等组件进入不可见,或者已经销毁的时候,Glide 会停止加载资源。
Application 的生命周期贯穿整个应用,所以 applicationManager 只有在应用程序关闭的时候终止加载。

所以尽量不要在非 UI 线程使用 Glide 加载图片,尽量使用 Activity、Fragment 等带有生命周期的组件配合 Glide 使用。

Glide 如何获得 Activity、Fragment 生命周期回调

在 各种 requestManagerRetriever.get() 方法中如果传入的是带有生命周期的组件,并且在 UI 线程,会执行以下几个方法端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// FragmentActivity
assertNotDestroyed(activity);
FragmentManager fm = activity.getSupportFragmentManager();
return supportFragmentGet(activity, fm, null /*parentHint*/);

//android.support.v4.app.Fragment
FragmentManager fm = fragment.getChildFragmentManager();
return supportFragmentGet(fragment.getActivity(), fm, fragment);

//Activity
assertNotDestroyed(activity);
android.app.FragmentManager fm = activity.getFragmentManager();
return fragmentGet(activity, fm, null /*parentHint*/);

//android.app.Fragment
android.app.FragmentManager fm = fragment.getChildFragmentManager();
return fragmentGet(fragment.getActivity(), fm, fragment);
  1. 如果是 Activity ,先获取 FragmentManager ,如果是 Fragment 则先获取 ChildFragmentManager。
  2. 如果是 support 包下面的 Activity 、Fragment 调用 supportFragmentGet,否则调用 fragmentGet。

fragmentGet() 和 supportFragmentGet() 方法大致类似,选取一个分析一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private RequestManager fragmentGet(Context context, android.app.FragmentManager fm,
android.app.Fragment parentHint) {
RequestManagerFragment current = getRequestManagerFragment(fm, parentHint);
RequestManager requestManager = current.getRequestManager();
if (requestManager == null) {
// TODO(b/27524013): Factor out this Glide.get() call.
Glide glide = Glide.get(context);
requestManager =
factory.build(
glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
current.setRequestManager(requestManager);
}
return requestManager;
}

上面这段代码做了两个功能

1
2
1. 创建一个 RequestManagerFragment。
2. 创建一个 RequestManager。

先看一下 getRequestManagerFragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
RequestManagerFragment getRequestManagerFragment(
final android.app.FragmentManager fm, android.app.Fragment parentHint) {
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
pendingRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}

这是是 Glide 设计中比较一个巧妙的地方

1
2
创建一个透明的 RequestManagerFragment 加入到FragmentManager 之中
通过添加的这个 Fragment 感知 Activity 、Fragment 的生命周期。

在 RequestManagerFragment 中可以看到以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onStart() {
super.onStart();
lifecycle.onStart();
}

@Override
public void onStop() {
super.onStop();
lifecycle.onStop();
}

@Override
public void onDestroy() {
super.onDestroy();
lifecycle.onDestroy();
unregisterFragmentWithRoot();
}

可以通过 RequestManagerFragment 把 Activity 的生命周期通过 lifecycle 传递给在 lifecycle 注册的 LifecycleListener。

RequestManager.load(url)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public RequestBuilder<Drawable> load(@Nullable Object model) {
return asDrawable().load(model);
}

public RequestBuilder<Drawable> asDrawable() {
return as(Drawable.class);
}

public <ResourceType> RequestBuilder<ResourceType> as(Class<ResourceType> resourceClass) {
return new RequestBuilder<>(glide, this, resourceClass, context);
}

public RequestBuilder<TranscodeType> load(@Nullable Object model) {
return loadGeneric(model);
}

private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}

以上就是 RequestBuilder.load(url) 的相关代码,发现并没有什么特殊之处。 只是创建了一个 RequestBuilder 。

RequestBuilder.into(view)

into() 方法调用起来十分方便,只要传递一个 ImageView ,Glide 就会自动下载图片,并且显示到 ImageView 上。这看似十分简单的一步,也是 Glide 最负责的调用。

1
2
3
4
5
6
7
8
9
10
public Target<TranscodeType> into(ImageView view) {

RequestOptions requestOptions = this.requestOptions;
……

return into(
glideContext.buildImageViewTarget(view, transcodeClass),
/*targetListener=*/ null,
requestOptions);
}

跟踪一下 glideContext.buildImageViewTarget(view, transcodeClass) 会发现这里返回的是一个DrawableImageViewTarget
into(ImageView view) 把 requestOptions 和 DrawableImageViewTarget 传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private <Y extends Target<TranscodeType>> Y into(
@NonNull Y target,
@Nullable RequestListener<TranscodeType> targetListener,
RequestOptions options) {
……
Request request = buildRequest(target, targetListener, options);

Request previous = target.getRequest();
……
requestManager.clear(target);
target.setRequest(request);
requestManager.track(target, request);

return target;
}

下一步跟踪到 requestManager.track(target, request)

1
2
3
4
5
6
7
8
9
10
11
12
13
 void track(Target<?> target, Request request) {
targetTracker.track(target);
requestTracker.runRequest(request);
}

public void runRequest(Request request) {
requests.add(request);
if (!isPaused) {
request.begin();
} else {
pendingRequests.add(request);
}
}

isPaused 变量

1
2
3
4
5
1. 如果此时 GlideRequests 的 Lifecycle 为 ApplicationLifecycle,只要应用存活
isPaused 为 false ,直接执行 request.begin()
2. 如果 GlideRequests 的 Lifecycle 是观测 Fragment 或者 Activity
isPaused 为true ,不会立即执行 request.begin()
当 Fragment 或者 Activity 显示到前台时通过遍历 requests 数组执行 request.begin()

所以执行网络请求下载图片的操作在 request.begin() 之中。
回到 into(ImageView view) 方法的

1
Request request = buildRequest(target, targetListener, options)

经过层层包裹我们可以找到一下路线,发现最后返回的是 SingleRequest

1
2
3
buildRequest >> buildRequestRecursive >> buildThumbnailRequestRecursive

>> obtainRequest >> SingleRequest

创建 SingleRequest 的过程比较复杂,牵扯到缩略图、错误处理之类的逻辑,大致都是上面那条路径。

然后就看一下 SingleRequest.begin()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void begin() {
……
省略一些其他分支
……
status = Status.WAITING_FOR_SIZE;
if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
onSizeReady(overrideWidth, overrideHeight);
} else {
target.getSize(this);
}
……
……
}

begin() 方法很长,我们剔除了一些异常处理,直接看最核心的方法。

如果我给 Glide 设置了 override() 则直接调用 onSizeReady(overrideWidth, overrideHeight)

否则会调用 target.getSize(this) 让 ImageView 计算自己的尺寸。

1
glideContext.buildImageViewTarget(view, transcodeClass) 创建一个 DrawableImageViewTarget

Glide流程

Glide网络下载流程

总结

  • 缓存
    • 活动资源 (Active Resources)
    • 内存缓存 (Memory Cache)
    • 资源类型(Resource Disk Cache)
    • 原始数据 (Data Disk Cache)
    • 网络缓存

问:

阅读全文 »

https

SSL 的握手协议非常有效的让客户和服务器之间完成相互之间的身份认证,其主要过程如下:

1.客户端向服务器请求HTTPS连接:
客户端向服务器传送客户端 SSL 协议的版本号,加密算法的种类,
产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。

2.服务器确认并返回证书。
服务器向客户端传送 SSL 协议的版本号,加密算法的种类,
随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

3.客户端验证服务器发来的证书。
客户端利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:
证书是否过期,
发行服务器证书的 CA 是否可靠(CA 认证机构颁发证书),
发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,
服务器证书上的域名是否和服务器的实际域名相匹配。
如果合法性验证没有通过,通讯将断开;
如果合法性验证通过,将继续进行第4步。
// 关于验证 CA 证书 及可能的中间人攻击:
[
https://www.jianshu.com/p/76540830537f
]

4.信息验证通过,客户端生成随机密钥A,用公钥加密后发给服务器。
从第3步验证过的证书里面可以拿到服务器的公钥,客户端生成的随机密钥就使用这个公钥来加密.
加密之后,只有拥有该服务器(持有私钥)才能解密出来,保证安全。

5.服务器用私钥解密出随机密钥A,以后通信就用这个随机密钥A来对通信进行加密。
至此, 客户端和服务端的握手完成, 即可以开始进行加密传输了。

#须知:
这个握手过程并没有将验证客户端身份的逻辑加进去。
因为在大多数的情况下,HTTPS只是验证服务器的身份而已。
如果要验证客户端的身份,需要客户端拥有证书,在握手时发送证书,而这个证书是需要成本的。

前言

Flutter已经面世这么长时间了,不抽空学习一下,实在是对不起自己。也是由于前天面试被问到Flutter的一些东西,一脸懵逼,真不是一个合格的iOS开发猿!!!

分享一下学习经验和心得,纪录一下学习过程的疑问。

官网学习,手敲练习demo源码

学习之前我有几个问题问自己

  • 为什么大家都说Flutter比RN和Weex流畅?
  • 和RN、Weex实现原理对比, Flutter的实现原理是什么?
  • 怎么和Native通讯?
阅读全文 »

LLDB官方文档地址

LLDB调试器

LLVM源代码树,并在lldb 子目录中找到源代码:

git clone https://github.com/llvm/llvm-project.git
请注意,LLDB通常使用CMake和Ninja从top-of-trunk构建。另外它构建:

  • 在macOS上生成Xcode项目
  • 在Linux和FreeBSD上使用clang和libstdc ++ / libc ++
  • 在NetBSD上使用GCC / clang和libstdc ++ / libc ++
  • 在Windows上生成VS 2017或更高版本的项目

先说大话:

  • 构建库以包含在IDE,命令行工具和其他分析工具中
  • 高性能和高效的内存使用
  • 可扩展:Python可编写脚本并使用插件架构
  • 在有意义的地方重用现有的编译器技术
  • 出色的多线程调试支持
  • 对C,Objective-C和C ++的大力支持
  • 可重定向以支持多个平台
  • 为调试器研究和其他创新提供基础

LDB支持各种基本调试功能,如读取DWARF,支持步骤,下一步,完成,回溯等。一些更感兴趣的位是:

  • 用于可移植性和可扩展性的插件架构:
    • 可执行文件格式的目标文件解析器。目前支持包括Mach-O(32位和64位)和ELF(32位)。
    • 对象容器解析器,用于提取文件中包含的对象文件。支持目前包括通用Mach-O文件和BSD档案。
    • 调试符号文件解析器以从对象文件中逐步提取调试信息。支持目前包括DWARF和Mach-O符号表。
    • 符号供应商插件从可执行对象的各种不同源收集数据。
    • 每个体系结构的反汇编插件。支持目前包括用于i386,x86-64,ARM / Thumb和PPC64le的LLVM反汇编程序
    • 调试器插件实现调试所需的主机和目标特定功能。
  • SWIG生成的脚本桥接允许Python访问和控制调试器库的公共API。
  • 远程协议服务器debugserver在i386和x86-64上实现macOS调试。
  • 命令行调试器 - lldb可执行文件本身。
  • 库的框架API。

编译器集成的好处

LLDB目前将调试信息转换为clang类型,以便它可以利用clang编译器基础结构。这允许LLDB在表达式中支持最新的C,C ++,Objective-C和Objective-C ++语言特性和运行时,而无需重新实现任何此功能。它还利用编译器在对表达式进行函数调用时,在反汇编指令和提取指令细节等时处理所有ABI细节。

主要好处包括:

  • 最新的C,C ++,Objective-C语言支持
  • 可以声明局部变量和类型的多行表达式
  • 支持时使用JIT表达式
  • 当不能使用JIT时,评估表达式中间表示(IR)

使用

命令结构

<noun> <verb> [-options [option-value]] [argument [argument...]]

选项可以放在命令行的任何位置,但是如果参数以“ - ”开头,那么你必须告诉lldb你已经完成了当前命令的选项,方法是添加一个选项终止:“ - ”所以例如如果你想启动一个进程并给“进程启动”命令“-stop-at-entry”选项,但是你想要启动你要启动的进程并使用参数“-program_arg value”,你会输入:

  • (lldb) process launch –stop-at-entry – -program_arg value

  • 要在LLDB中设置相同的文件和换行符,您可以输入以下任一项:
    (lldb) breakpoint set –file foo.c –line 12
    (lldb) breakpoint set -f foo.c -l 12

  • 您可以多次使用-name选项在一组函数上创建断点。
    (lldb) breakpoint set –name foo
    (lldb) breakpoint set -n foo
    (lldb) breakpoint set –name foo –name bar

  • 要在名为foo的所有C ++方法上设置断点,您可以输入以下任一项:
    (lldb) breakpoint set –method foo
    (lldb) breakpoint set -M foo

  • 设置一个名为alignLeftEdges的断点Objective-C选择器:
    (lldb) breakpoint set –selector alignLeftEdges:
    (lldb) breakpoint set -S alignLeftEdges:

  • 您可以使用“-shlib <path>”(简称“-s <path>”)将任何断点限制为特定的可执行映像:
    (lldb) breakpoint set –shlib foo.dylib –name foo
    (lldb) breakpoint set -s foo.dylib -n foo

  • lldb命令解释器对命令名进行最短的唯一字符串匹配,因此以下两个命令都将执行相同的命令:
    (lldb) breakpoint set -n “-[SKTGraphicView alignLeftEdges:]”
    (lldb) br s -n “-[SKTGraphicView alignLeftEdges:]”

设置观察点

除断点外,您还可以使用help watchpoint查看监视点操作的所有命令。例如,我们可能会执行以下操作来查看名为“global”的变量进行写入操作,但只有在条件“(global == 5)”为真时才会停止:

(lldb) watch set var global
(lldb) watch modify -c ‘(global==5)’
(lldb) watch list
(lldb) about to write to ‘global’…

启动或附加到您的程序

  • 要在lldb中启动程序,我们使用“process launch”命令或其内置别名之一:
    (lldb) process launch
    (lldb) run
    (lldb) r

  • 您还可以按进程ID或进程名称附加到进程。当按名称附加到进程时,lldb还支持“-waitfor”选项,该选项等待显示该名称的下一个进程,并附加到该进程
    (lldb) process attach –pid 123
    (lldb) process attach –name Sketch
    (lldb) process attach –name Sketch –waitfor

  • 启动或附加到进程后,您的进程可能会在某处停止:

    1
    2
    3
    4
    5
    (lldb) process attach -p 12345
    Process 46915 Attaching
    Process 46915 Stopped
    1 of 3 threads stopped with reasons:
    * thread #1: tid = 0x2c03, 0x00007fff85cac76a, where = libSystem.B.dylib`__getdirentries64 + 10, stop reason = signal = SIGSTOP, queue = com.apple.main-thread

控制你的程序

  • 启动后,我们可以继续,直到我们达到断点。进程控制的原始命令都存在于“thread”命令下:
    (lldb) thread continue
    Resuming thread 0x2c03 in process 46915
    Resuming process 46915
    (lldb)

  • 步进命令
    (lldb) thread step-in // The same as gdb’s “step” or “s”
    (lldb) thread step-over // The same as gdb’s “next” or “n”
    (lldb) thread step-out // The same as gdb’s “finish” or “f”

  • 逐步指令版本:
    (lldb) thread step-inst // The same as gdb’s “stepi” / “si”
    (lldb) thread step-over-inst // The same as gdb’s “nexti” / “ni”

  • 最后,lldb运行直到行或帧退出步进模式:
    (lldb) thread until 100

检查堆栈帧状态

  • 检查框架参数和局部变量的最方便方法是使用“frame variable”命令:
    (lldb) frame variable

不想写了,用到的时候,写一些用到的东西吧,学习的话,大家去看官方文档吧!!!

iOS App程序崩溃的抓取与分析

备用: xun开源代码地址

前言

  1. cpu无法执行,除以0,无权限内存地址(pagezero-4g)无效
    • pagezero,拦截空指针的访问。 隔绝32位操作系统
  2. 被系统强杀
    • oom
    • ANR
    • 资源异常
    • 死锁
    • 非法应用签名
    • 后台超时
    • 内存紧张
    • 设备过热
  3. oc异常
    • 数组越界
    • C++异常
    • 断言
  4. 中断
    • 外部中断(IO)
    • 异常中断
    • 系统调用

具体处理

  1. Mach异常捕获方式
    • mach_port_allocate 创建一场端口
    • mach_port_insert_right 申请 set_exception_ports 的权限
    • xxx_set_exception_ports 设置异常端口
    • 循环等待异常消息
  2. Unix 信号方式 signal(SIGSEGV, signalHandler)
    • 除了OC层面的异常捕捉之外,很多内存错误、访问错误的地址产生的crash则需要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数。该函数中我们可以输出栈信息,版本信息等其他一切我们所想要的。
  3. Mach异常 + Unix信号方式
    • 某个NSException导致程序Crash的,只有拿到这个NSException,获取它的reason,name,callStackSymbols信息才能确定出问题的程序位置。
    • 方法很简单,可通过注册NSUncaughtExceptionHandler捕获异常信息
      1
      2
      3
      4
      5
      static void my_uncaught_exception_handler (NSException *exception) {
      //这里可以取到 NSException 信息
      }
      NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);

多个 Crash 日志收集服务共存

  • 拒绝传递 UncaughtExceptionHandler
  • 开发测试阶段,可以利用 fishhook 框架去hookNSSetUncaughtExceptionHandler方法,这样就可以清晰的看到handler的传递流程断在哪里,快速定位污染环境者。
  • Mach异常端口换出+信号处理Handler覆盖
  • 影响系统崩溃日志准确性
    • 若程序因NSException而Crash,系统日志中的Last Exception Backtrace信息是完整准确的,不会受应用层的胡来而影响,可作为排查问题的参考线索。

概述

Mach: 微内核,负责操作系统中基本职责:进程和线程抽象、虚拟内存管理、任务调度、进程间通信和消息传递机制。

大家熟知的NSSetUncaughtExceptionHandler() + signal() / sigaction()的方式收集Crash,但是stack overflow并不能被此方法扑捉到;

通常我们所说的异常,一般是有处理器陷阱引发的,通用的Mach异常处理程序exception_triage(),负责将异常转换成Mach 消息。exception_triage()通过调用exception_deliver()尝试把异常投递到thread、task最后是host。首先尝试将异常抛给thread端口,然后尝试抛给task端口,最后再抛给host端口(默认端口),如果没有一个端口返回KERN_SUCCESS,那么任务就会被终止。

代码太多,不再贴了,自行下载源码,找到 osfmk/kern/exception.c 文件。 里面有exception_triage()和exception_deliver()函数。关于Exception的相关定义,可以在osfmk/mach/exception_types.h里面查看。下面只贴伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

// 位于 osfmk/kern/exception.c
kern_return_t
xception_triage(
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt
)
{
thread_t thread;
task_t task;
host_priv_t host_priv;
lck_mtx_t *mutex;
kern_return_t kr = KERN_FAILURE;
assert(exception != EXC_RPC_ALERT);
if (panic_on_exception_triage) {
panic("called exception_triage when it was forbidden by the boot environment");
}
thread = current_thread();

// ================ 分别尝试把异常投递到thread、task最后是host。================
/*
* Try to raise the exception at the activation level.
*/
mutex = &thread->mutex;
if (KERN_SUCCESS == check_exc_receiver_dependency(exception, thread->exc_actions, mutex))
{
kr = exception_deliver(thread, exception, code, codeCnt, thread->exc_actions, mutex);
if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
goto out;
}
/*
* Maybe the task level will handle it.
*/
task = current_task();
mutex = &task->lock;
if (KERN_SUCCESS == check_exc_receiver_dependency(exception, task->exc_actions, mutex))
{
kr = exception_deliver(thread, exception, code, codeCnt, task->exc_actions, mutex);
if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
goto out;
}

/*
* How about at the host level?
*/
host_priv = host_priv_self();
mutex = &host_priv->lock;

if (KERN_SUCCESS == check_exc_receiver_dependency(exception, host_priv->exc_actions, mutex))
{
kr = exception_deliver(thread, exception, code, codeCnt, host_priv->exc_actions, mutex);
if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
goto out;
}

out:
if ((exception != EXC_CRASH) && (exception != EXC_RESOURCE) &&
(exception != EXC_GUARD) && (exception != EXC_CORPSE_NOTIFY))
thread_exception_return();
return kr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

// 位于 osfmk/kern/exception.c
kern_return_t
exception_deliver(
thread_t thread,
exception_type_t exception,
mach_exception_data_t code,
mach_msg_type_number_t codeCnt,
struct exception_action *excp,
lck_mtx_t *mutex)
{
... // 省略部分代码

switch (behavior) {
case EXCEPTION_STATE: {
mach_msg_type_number_t state_cnt;
thread_state_data_t state;

c_thr_exc_raise_state++;
state_cnt = _MachineStateCount[flavor];
kr = thread_getstatus(thread, flavor,
(thread_state_t)state,
&state_cnt);
if (kr == KERN_SUCCESS) {
if (code64) {
kr = mach_exception_raise_state(exc_port,
exception,
code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
} else {
kr = exception_raise_state(exc_port, exception,
small_code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
}
if (kr == MACH_MSG_SUCCESS && exception != EXC_CORPSE_NOTIFY)
kr = thread_setstatus(thread, flavor,
(thread_state_t)state,
state_cnt);
}
return kr;
}

case EXCEPTION_DEFAULT:
c_thr_exc_raise++;
if (code64) {
kr = mach_exception_raise(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
code,
codeCnt);
} else {
kr = exception_raise(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
small_code,
codeCnt);
}
return kr;

case EXCEPTION_STATE_IDENTITY: {
mach_msg_type_number_t state_cnt;
thread_state_data_t state;

c_thr_exc_raise_state_id++;
state_cnt = _MachineStateCount[flavor];
kr = thread_getstatus(thread, flavor,
(thread_state_t)state,
&state_cnt);
if (kr == KERN_SUCCESS) {
if (code64) {
kr = mach_exception_raise_state_identity(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
} else {
kr = exception_raise_state_identity(exc_port,
retrieve_thread_self_fast(thread),
retrieve_task_self_fast(thread->task),
exception,
small_code,
codeCnt,
&flavor,
state, state_cnt,
state, &state_cnt);
}
if (kr == MACH_MSG_SUCCESS && exception != EXC_CORPSE_NOTIFY)
kr = thread_setstatus(thread, flavor,
(thread_state_t)state,
state_cnt);
}
return kr;
}

default:
panic ("bad exception behavior!");
return KERN_FAILURE;
}/* switch */
}

当第一个BSD进程调用bsdinit_task()函数(源码位于bsd/kern/bsd_init.c)启动时,这函数还调用了ux_handler_init()函数(位于bsd/uxkern/ux_exception.c)设置了一个Mach内核线程跑ux_handler()的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

// 位于bsd/kern/bsd_init.c
/* Called with kernel funnel held */
void
bsdinit_task(void)
{
// ...省略

ux_handler_init(); // 初始化handler

// 设置port
thread = current_thread();
(void) host_set_exception_ports(host_priv_self(),
EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT),//pilotfish (shark) needs this port
(mach_port_t) ux_exception_port,
EXCEPTION_DEFAULT| MACH_EXCEPTION_CODES,
0);

ut = (uthread_t)get_bsdthread_info(thread);

bsd_init_task = get_threadtask(thread);
init_task_failure_data[0] = 0;

#if CONFIG_MACF
mac_cred_label_associate_user(p->p_ucred);
mac_task_label_update_cred (p->p_ucred, (struct task *) p->task);
#endif
load_init_program(p);
lock_trace = 1;
}

每一个thread、task及host自身都有一个异常端口数组,通过调用xxx_set_exception_ports()(xxx为thread、task或host)可以设置这些异常端口。 xxx_set_exception_ports()第四个参数为exception_behavior_t behavior,这将会使用到与行为相匹配的实现(exc.defs 或 mach_exc.defs)。
各种行为都在host层被catch_[mach]_exception_xxx处理,64位的对应的是有mach函数(可在/bsd/uxkern/ux_exception.c查看)。
这些函数通过调用ux_exception()将异常转换为对应的UNIX信号,并通过threadsignal()将信号投递到出错线程。

所以,如果异常是栈溢出,那么signal是SIGSEGV而不是SIGBUS;如果进程退出了或者线程/进程未准备好处理signal,所注册的signal()是无法接收信号的。

把Mach exception 和 UNIX signal 的转换制表后,如下

exception type signal
EXC_BAD_ACCESS 1、SIGSEGV (KERN_INVALID_ADDRESS)
2、SIGBUS(其它)
EXC_BAD_INSTRUCTION SIGILL
EXC_ARITHMETIC SIGFPE
EXC_EMULATION SIGEMT
EXC_SOFTWARE 1、SIGSYS(EXC_UNIX_BAD_SYSCALL)
2、SIGPIPE(EXC_UNIX_BAD_PIPE)
3、SIGABRT(EXC_UNIX_ABORT)
4、SIGKILL(EXC_SOFT_SIGNAL)
EXC_BREAKPOINT SIGTRAP ()

实战

在Mach中,异常是通过内核中的主要设施-消息传递机制-进行处理的。一个异常与一条消息并无差别,由出错的线程或任务(通过 msg_send())发送,并通过一个处理程(通过 msg_recv())接收。
由于Mach的异常以消息机制处理而不是通过函数调用,exception messages可以被转发到先前注册的Mach exception处理程序。这意味着你可以插入一个exception处理程序,而不干扰现有的无论是调试器或Apple’s crash reporter。可以使用mach_msg() // flag MACH_SEND_MSG发送原始消息到以前注册的处理程序的Mach端口,将消息转发到一个现有的处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
void catchMACHExceptions() {

kern_return_t rc = 0;
exception_mask_t excMask = EXC_MASK_BAD_ACCESS |
EXC_MASK_BAD_INSTRUCTION |
EXC_MASK_ARITHMETIC |
EXC_MASK_SOFTWARE |
EXC_MASK_BREAKPOINT;

rc = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &myExceptionPort);
if (rc != KERN_SUCCESS) {
fprintf(stderr, "------->Fail to allocate exception port\\\\\\\\n");
return;
}

rc = mach_port_insert_right(mach_task_self(), myExceptionPort, myExceptionPort, MACH_MSG_TYPE_MAKE_SEND);
if (rc != KERN_SUCCESS) {
fprintf(stderr, "-------->Fail to insert right");
return;
}

rc = thread_set_exception_ports(mach_thread_self(), excMask, myExceptionPort, EXCEPTION_DEFAULT, MACHINE_THREAD_STATE);
if (rc != KERN_SUCCESS) {
fprintf(stderr, "-------->Fail to set exception\\\\\\\\n");
return;
}

// at the end of catchMachExceptions, spawn the exception handling thread
pthread_t thread;
pthread_create(&thread, NULL, exc_handler, NULL);
} // end catchMACHExceptions

static void *exc_handler(void *ignored) {
// Exception handler – runs a message loop. Refactored into a standalone function
// so as to allow easy insertion into a thread (can be in same program or different)
mach_msg_return_t rc;
fprintf(stderr, "Exc handler listening\\\\\\\\n");
// The exception message, straight from mach/exc.defs (following MIG processing) // copied here for ease of reference.
typedef struct {
mach_msg_header_t Head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
integer_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[144];
} Request;

Request exc;

struct rep_msg {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
} rep_msg;


for(;;) {
// Message Loop: Block indefinitely until we get a message, which has to be
// 这里会阻塞,直到接收到exception message,或者线程被中断。
// an exception message (nothing else arrives on an exception port)
rc = mach_msg( &exc.Head,
MACH_RCV_MSG|MACH_RCV_LARGE,
0,
sizeof(Request),
myExceptionPort, // Remember this was global – that's why.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);

if(rc != MACH_MSG_SUCCESS) {
/*... */
break ;
};

// Normally we would call exc_server or other. In this example, however, we wish
// to demonstrate the message contents:

printf("Got message %d. Exception : %d Flavor: %d. Code %lld/%lld. State count is %d\\\\\\\\n" ,
exc.Head.msgh_id, exc.exception, exc.flavor,
exc.code[0], exc.code[1], // can also print as 64-bit quantity
exc.old_stateCnt);

rep_msg.Head = exc.Head;
rep_msg.NDR = exc.NDR;
rep_msg.RetCode = KERN_FAILURE;

kern_return_t result;
if (rc == MACH_MSG_SUCCESS) {
result = mach_msg(&rep_msg.Head,
MACH_SEND_MSG,
sizeof (rep_msg),
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
}
}

return NULL;

} // end exc_handler

接下来,我们测试一下。

1
2
3
4
- (void)test
{
[self test];
}

结果如下:

1
2
3
Exc handler listening
Got message 2401. Exception : 1 Flavor: 0. Code 2/1486065656. State count is 8
(lldb)

我们可以查看mach/exception_types.h 对exception type的定义

Exception: 1, 即为EXC_BAD_ACCESS
code: 2, 即KERN_PROTECTION_FAILURE

而ux_exception() 函数告诉我们Code与signal是怎么转换的。
也就是 Exception = EXC_BAD_ACCESS, code = 2 对应的是SIGBUS信号,又因为为是stack overflow,信号应该是SIGSEGV。
那么结这个exception就是:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_PROTECTION_FAILURE

再试试其他的:

1
2
int *pi = (int*)0x00001111;
*pi = 17;

Exc handler listening
Got message 2401. Exception : 1 Flavor: 0. Code 1/4369. State count is 8(lldb)

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS

最后

除了上面硬件产生的信号,另外还有软件产生的信号。软件产生的信号来自kill()、pthread_kill()两个函数的调用,大概过程是这样的:kill()/pthread_kill() –> … –> psignal_internal() –> act_set_astbsd()。最终也会调用act_set_astbsd()发送信号到目标线程。这意味着Mach exception流程是不会走的。
另外,在abort()源码注释着:<rdar://problem/7397932> abort() should call pthread_kill to deliver a signal to the aborting thread , 它是这样调用的(void)pthread_kill(pthread_self(), SIGABRT);。Apple’s Crash Reporter 把SIGABRT的Mach exception type记为EXC_CRASH,不同与上面转变表。

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000

所以尽管Mach exception handle 比 UNIX signal handle 更有优势,但我们还是须要注册signal handle用于处理EXC_SOFTWARE/EXC_CRASH。

阅读全文 »

Mach

OS X内核的基本服务和基本类型都基于Mach 3.0。苹果对Mach进行了修改和扩展,以更好地满足OS X的功能和性能目标。

Mach 3.0最初被认为是一个简单、可扩展的通信微内核。它能够作为一个独立的内核运行,而其他传统的操作系统服务(如I/O、文件系统和网络堆栈)作为用户模式服务器运行。

然而,在OS X中,Mach与其他内核组件链接到一个内核地址空间中。这主要是为了性能;在链接组件之间进行直接调用要比在单独任务之间发送消息执行远程过程调用(RPC)快得多。这种模块化的结构导致了一个比单一内核所允许的更健壮和可扩展的系统,而没有纯微内核的性能损失。

因此,在OS X中,Mach主要不是客户机和服务器之间的通信中心。相反,它的价值由抽象、可扩展性和灵活性组成。特别地,Mach提供了

  • 基于对象的api,使用通信通道(例如端口)作为对象引用
  • 高度并行执行,包括预先调度的线程和对SMP的支持
  • 一个灵活的调度框架,支持实时使用
  • 一组完整的IPC原语,包括消息传递、RPC、同步和通知
  • 支持大型虚拟地址空间、共享内存区域和由持久性存储支持的内存对象
  • 经过验证的可扩展性和可移植性,例如跨指令集体系结构和分布式环境
  • 安全与资源管理作为设计的基本原则;所有资源都是虚拟化的

Mach内核抽象化

Mach提供了一组被设计成既简单又强大的抽象。这些是主要的内核抽象:

  • 任务(Tasks)。资源所有权单位;每个任务由一个虚拟地址空间、一个端口名称空间和一个或多个线程组成。(类似于流程。)
  • 线程(Threads)。任务中CPU执行的单位。
  • 地址空间(Address space)。与内存管理器一起,Mach实现了稀疏虚拟地址空间和共享内存的概念。
  • 内存对象(Memory objects)。内存管理的内部单元。内存对象包括命名项和区域;它们表示可能映射到地址空间的持久数据。
  • 端口(Ports)。安全、简单的通信通道,只能通过发送和接收功能(称为端口权限)访问。
  • IPC。消息队列、远程过程调用、通知、信号量和锁集。
  • 时间(Time)。时钟、定时器和等待。

在陷阱级别,大多数Mach抽象的接口由表示这些对象的内核端口之间消息组成。陷阱(trap)级别的接口(例如mach_msg_overwrite_trap)和消息格式在正常使用中由Mach接口生成器(MIG)抽象出来。MIG用于编译基于消息的api的过程接口,基于这些api的描述。(MIG参看下面总结部分)

任务和线程

OS X进程和POSIX线程(pthreads)分别在Mach任务和线程之上实现。线程是任务中的控制流点。存在一个任务来为其包含的线程提供资源。这种分割是为了提供并行性和资源共享。

一个线程

  • 是任务中的控制流点。
  • 可以访问包含任务的所有元素。
  • 与其他线程(可能)并行执行,甚至是同一任务中的线程。
  • 具有最小的状态信息,以降低开销。

一个任务

  • 是系统资源的集合。这些资源(地址空间除外)由端口引用。如果对端口被分配了权限,那么这些资源可以与其他任务共享。
  • 提供一个大的、可能稀疏的地址空间,由虚拟地址引用。这个空间的一部分可以通过继承或外部内存管理共享。
  • 包含一些线程。

请注意,任务没有自己的线程执行指令的生命周期。当说“task Y做X”时,真正的意思是“task Y中包含的线程做X”。

  1. 任务是一个相当昂贵的实体。它的存在是资源的集合。任务中的所有线程都共享所有内容。如果没有显式的操作(尽管操作通常很简单),两个任务就不能共享任何内容,而且一些资源(例如端口接收权限)根本不能在两个任务之间共享。

  2. 线程是一个相当轻量级的实体。它的创建成本相当低,操作开销也很低。这是真的,因为一个线程只有很少的状态信息(主要是它的寄存器状态)。它所拥有的任务承担着资源管理的重担。在多处理器计算机上,任务中的多个线程可以并行执行。即使并行性不是目标,多线程也有一个优势,即每个线程都可以使用同步编程风格,而不是试图使用单个线程进行异步编程来提供多个服务。

  3. 线程是基本的计算实体。一个线程只属于一个任务,这个任务定义了它的虚拟地址空间。影响地址空间的结构或引用任何资源以外的地址空间,线程必须执行一个特殊的陷阱指令 引起内核代表线程执行操作或发送消息代理代表线程。通常,这些陷阱操作与包含线程的任务相关的资源。内核可以发出请求来操作这些实体:创建它们、删除它们并影响它们的状态。

  4. Mach为线程调度策略提供了一个灵活的框架。OS X的早期版本同时支持分时和固定优先级策略。提高和降低分时线程的优先级,以平衡它与其他分时线程之间的资源消耗。

  5. 固定优先级的线程执行一定的时间量,然后放在具有相同优先级的线程队列的末尾。将固定优先级线程的量子级别设置为无穷大,可以让线程一直运行,直到阻塞,或者直到被优先级更高的线程抢占为止。高优先级实时线程通常是固定优先级的。

  6. OS X还为实时性能提供了时间约束调度。这个调度允许您指定线程必须在一定时间内获得一定的时间量。

Mach调度在Mach调度和线程接口中有进一步的描述。

端口、端口权限、端口集和端口名称空间

除了任务的虚拟地址空间之外,所有其他Mach资源都是通过称为端口的间接级别访问的。端口是请求服务的客户机和提供服务的服务器之间单向通信通道的端点。如果要向此类服务请求提供应答,则必须使用第二个端口。这类似于UNIX中的(单向)管道。

在大多数情况下,由端口访问的资源(即由端口命名的资源)被称为对象。大多数由端口命名的对象都有一个接收方和(可能的)多个发送方。也就是说,对于典型对象(如消息队列),只有一个接收端口,至少有一个发送端口。

对象提供的服务由接收发送到对象的请求的管理器决定。因此,内核是与内核提供的对象关联的端口的接收方,而与任务提供的对象关联的端口的接收方是提供这些对象的任务。

对于命名任务提供的对象的端口,可以将该端口的请求接收方更改为不同的任务,例如通过在消息中将该端口传递给该任务。一个任务可能有多个引用其支持的资源的端口。就此而言,任何给定的实体都可以有多个表示它的端口,每个端口表示不同的允许操作集。例如,许多对象都有一个名称端口和一个控制端口(有时称为特权端口)。对控制端口的访问允许操作对象;对name端口的访问简单地为对象命名,这样您就可以获得关于它的信息,或者对它执行其他非特权操作。

任务具有以特定方式访问端口的权限(发送、接收、发送一次);这些被称为port rights。端口只能通过右值访问。端口通常用于授予客户机对Mach内对象的访问权。有权发送到对象的IPC端口表示有权以规定的方式操作对象。因此,端口所有权是Mach内部的基本安全机制拥有访问对象的权利就是拥有访问或操作该对象的能力

端口权限可以通过IPC在任务之间复制和移动。这样做实际上是将功能传递给某个对象或服务器。

一种类型的对象引用一个端口是一个端口组。顾名思义,端口设置一组端口的权利时,可以当作一个单独的单元接收消息或事件的任何成员集。港口集允许一个线程等待的消息和事件源,例如在工作循环。

传统上,在Mach中,由端口表示的通信通道总是消息队列。然而,OS X支持其他类型的通信通道,这些新的IPC对象类型也由端口和端口权限表示。有关消息和其他IPC类型的详细信息,请参阅下面进程间通信(Interprocess Communication, IPC)一节。

端口和端口权限没有允许直接操作任意端口或权限的系统范围名称。只有当任务在其端口名称空间中具有端口权时,才可以由任务操作端口。端口权由端口名称指定,一个整数索引进入一个32位端口名称空间。每个任务都与它关联一个端口名称空间。

当另一个任务显式地将它们插入它的名称空间时,当它们在消息中接收到权限时,任务通过创建返回对象权限的对象获得端口权限,并通过Mach调用特定的特殊端口(mach_thread_self、mach_task_self和mach_reply_port)获得端口权限。

内存管理

与大多数现代操作系统一样,Mach提供对大型、稀疏的虚拟地址空间的寻址。运行时访问是通过虚拟地址进行的,这些虚拟地址可能与初始访问时物理内存中的位置不对应。Mach负责获取一个请求的虚拟地址,并在物理内存中为它分配一个相应的位置。它通过请求分页来实现这一点。

当内存对象映射到虚拟地址空间的范围时,将用数据填充虚拟地址空间的范围。地址空间中的所有数据最终都是通过内存对象提供的。Mach在物理内存中建立页面时,向内存对象(分页器)的所有者询问页面的内容,并在回收页面之前将可能修改过的数据返回给分页器。OS X包含两个内置分页器——默认分页器和vnode分页器(default pager and the vnode pager)。

默认的分页器处理非持久性内存,称为匿名内存。匿名内存是零初始化的,并且只在任务执行期间存在。vnode分页器将文件映射到内存对象。Mach向内存对象导出一个接口,允许用户模式任务提供内存对象的内容。这个接口称为外部内存管理接口(EMMI)。

内存管理子系统导出称为命名项或命名内存项的虚拟内存句柄。与大多数内核资源一样,这些资源由端口表示。拥有一个命名的内存条目句柄,允许所有者映射底层虚拟内存对象,或者将映射底层对象的权利传递给其他人。在两个不同的任务中映射命名项会在两个任务之间生成共享内存窗口,从而为建立共享内存提供了一种灵活的方法。

从OS X v10.1开始,EMMI系统得到了增强,以支持“无端口”EMMI。在传统的EMMI中,为每个内存区域创建两个Mach端口,同样也为每个缓存的vnode创建两个端口。在最初的实现中,无端口的EMMI用直接内存引用(基本上是指针)替换了这一点。在将来的版本中,端口将用于与内核外部的分页器通信,同时使用直接引用与驻留在内核空间中的分页器通信。这些更改的最终结果是,早期版本的无端口EMMI不支持运行在内核空间之外的分页器。这种支持有望在未来的版本中恢复。

虚拟内存空间的地址范围也可以通过直接分配(使用vm_allocation)来填充。底层虚拟内存对象是匿名的,并由默认分页程序支持。地址空间的共享范围也可以通过继承设置。创建新任务时,将从父任务中克隆它们。这种克隆也属于底层内存地址空间。根据与映射相关的属性,对象的映射部分可以作为副本继承,也可以作为共享,或者根本不可以。Mach采用一种称为“写中复制”的延迟复制形式,以优化任务创建时继承副本的性能。

不是直接复制范围,而是通过受保护的共享来实现写时复制优化。这两个任务共享要复制的内存,但具有只读访问。当任何一个任务试图修改范围的一部分时,该部分将被复制。这种对内存副本的延迟计算是一种重要的优化,它允许在几个方面进行简化,尤其是消息传递api。

Mach还提供了另一种形式的共享,通过导出指定的区域。命名区域是命名条目的一种形式,但是它不是由虚拟内存对象支持的,而是由虚拟映射片段支持的。这个片段可能包含到许多虚拟内存对象的映射。它可以映射到其他虚拟映射,提供了一种方法,不仅可以继承一组虚拟内存对象,还可以继承它们现有的映射关系。该特性在任务设置中提供了重要的优化,例如在共享用于共享库的地址空间的复杂区域时。

进程间通信(IPC)

任务之间的通信是Mach哲学的一个重要元素。Mach支持客户机/服务器系统结构,其中任务(客户机)通过通过通信通道发送的消息请求其他任务(服务器)来访问服务。

Mach中这些通信通道的端点称为端口,而端口权限表示使用该通道的权限。Mach提供的IPC形式包括

  • 消息队列
  • 信号量
  • 通知
  • 锁集
  • 远程过程调用(rpc)

由端口表示的IPC对象的类型决定了该端口上允许的操作,以及数据传输的方式(和是否)。

重要提示:OS X中的IPC设施处于过渡状态。在系统的早期版本中,并不是所有这些IPC类型都可以实现。

对于端口的原始操作,有两种基本不同的Mach api, mach_ipc家族和mach_msg家族。在合理的范围内,这两个系列都可以用于任何IPC对象;然而,在新代码中,mach_ipc调用是首选的。mach_ipc调用在适当的地方维护状态信息,以便支持事务的概念。mach_msg调用支持遗留代码,但不推荐使用;他们是无状态的。

IPC事务和事件调度

当线程调用mach_ipc_dispatch,它反复处理事件设置的注册端口。这些事件可以从RPC参数块对象(如客户调用的结果),一个锁对象被(由于其他线程释放锁),通知或信号量被发布或消息来自一个传统的消息队列。

这些事件通过mach_msg_dispatch的调用来处理。有些事件暗示调用生命周期内存在事务。在锁的情况下,状态是锁的所有权。当callout返回时,锁被释放。在远程过程调用的情况下,状态是客户机的标识、参数块和应答端口。当callout返回时,将发送应答。

当callout返回时,事务(如果有的话)就完成了,线程等待下一个事件。mach_ipc_dispatch工具旨在支持工作循环。

消息队列

最初,Mach中进程间通信的唯一样式是消息队列。只有一个任务可以持有表示消息队列的端口的接收权。这个任务允许从端口队列接收(读取)消息。多个任务可以拥有向队列发送(写)消息的端口的权限。

任务通过构建包含一组数据元素的数据结构与另一个任务通信,然后在其拥有发送权限的端口上执行消息发送操作。稍后,具有该端口接收权限的任务将执行消息接收操作。

一条消息可包括以下部分或全部:

  • 纯数据
  • 存储范围的副本
  • 端口的权利
  • 内核隐式属性,如发送方的安全令牌

消息传输是一个异步操作。消息逻辑上被复制到接收任务中,可能使用写时复制优化。接收任务中的多个线程可以尝试从给定端口接收消息,但是只有一个线程可以接收任何给定的消息。

信号量

信号量IPC对象支持等待、post和post所有操作。这些是计数信号量,如果在该信号量的等待队列中当前没有线程在等待,则保存(计数)post。post all操作将唤醒所有当前等待的线程。

通知

与信号量一样,通知对象也支持post和wait操作,但添加了一个state字段。状态是一个固定大小、固定格式的字段,在创建通知对象时定义。每个post更新状态字段;每个post都覆盖一个状态。

锁是提供对临界区互斥访问的对象。锁的主要接口是面向事务的(参见IPC事务和事件调度)。在事务期间,线程持有锁。当它从事务返回时,锁被释放。

远程过程调用(RPC)对象

顾名思义,RPC对象旨在促进和优化远程过程调用。RPC对象的主要接口是面向事务的(参见IPC事务和事件调度)

当创建RPC对象时,定义一组参数块格式。当客户机发出RPC(对象上的发送)时,它会导致以预定义格式之一的消息创建并在对象上排队,然后最终传递给服务器(接收方)。当服务器从事务返回时,将回复返回给发送方。Mach试图通过使用客户机的资源执行服务器来优化事务;这称为线程迁移

时间管理

Mach中时间的传统抽象是时钟,它提供了一组基于mach_timspec_t的异步报警服务。有一个或多个时钟对象,每个对象定义一个以纳秒为单位的单调递增的时间值。实时时钟是内置的,是最重要的,但是系统中可能还有其他时钟用于其他时间概念。时钟支持获取当前时间、给定时间段的睡眠、设置闹钟(在给定时间发送的通知)等操作。

mach_timespec_t API在OS x中是不推荐的。更新的、首选的API基于计时器对象,而计时器对象又使用AbsoluteTime作为基本数据类型。AbsoluteTime是一种依赖于机器的类型,通常基于平台本机时基。提供了一些例程来将绝对时间值转换为其他数据类型,或者从其他数据类型转换为绝对时间值,比如纳秒。计时器对象支持异步、无漂移通知、取消和提前警报。它们比时钟效率更高,分辨率更高。

我们总结一下

Mach 、端口、Mach消息部分

  • 在Mach中所有东西(Task、线程、虚拟内存等)都是对象
  • 对象与对象之间通信只能(任务虚拟地址空间)通过端口收发(一个接收方,可以多个发送方)消息。

Mach做以下几件事儿:

  • “控制点”或执行单元的管理。
  • 线程或线程组(Task)的资源分配。
  • 虚拟内存的分配和管理。
  • 底层物理资源–即CPU、内存和任何物理设备的分配。

Mach消息结构体

  • 最基本的包含两部分:消息头、消息体。可选的消息尾
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    typedef	struct 
    {
    mach_msg_bits_t msgh_bits;//标志位
    mach_msg_size_t msgh_size;//大小
    mach_port_t msgh_remote_port;//目标端口(发送:接受方,接收:发送方)
    mach_port_t msgh_local_port; //源端口(发送:发送方,接收:接收方)
    mach_port_name_t msgh_voucher_port;
    mach_msg_id_t msgh_id;
    } mach_msg_header_t; //消息头
    typedef struct
    {
    mach_msg_size_t msgh_descriptor_count;
    } mach_msg_body_t;//消息体
    typedef struct
    {
    mach_msg_header_t header;
    mach_msg_body_t body;
    } mach_msg_base_t; //基本消息
    typedef unsigned int mach_msg_trailer_type_t;//消息尾的类型
    typedef struct
    {
    mach_msg_trailer_type_t msgh_trailer_type;
    mach_msg_trailer_size_t msgh_trailer_size;
    } mach_msg_trailer_t; //消息尾
  • 复杂一点的,将消息头的标志位mach_msg_bits_t设置为MACH_MSGH_BITS_COMPLEX,就表示复杂消息。此时消息体里面指定了描述符的个数,接下来就是一个接着一个的描述符:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef struct
    {
    uint64_t address;//数据的大小
    boolean_t deallocate: 8;//发送之后是否接触分配
    mach_msg_copy_options_t copy: 8;//复制指令
    unsigned int pad1: 8;
    mach_msg_descriptor_type_t type: 8;
    mach_msg_size_t size;//数据的大小
    } mach_msg_ool_descriptor64_t;

消息收发

1
2
3
4
5
6
7
8
extern mach_msg_return_t	mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);

步骤如下:
发送消息:

  • 调用current_space()获取当前的IPC空间。
  • 调用current_map()获取虚拟空间
  • 消息大小正确性检查
  • 计算要分配的消息大小
  • 通过ipc_kmsg_alloc分配消息
  • 复制消息
  • 复制消息关联的端口权限,然后通过ipc_kmsg_copyin将所有的out-of-line数据的内存复制到当前虚拟空间。(如果不复制权限可能导致无法访问数据)
  • 调用ipc_kmsg_send()发送消息
    • 获得msgh_remote_port引用并锁定端口
    • 调用ipc_mqueue_send(),将消息直接复制到端口的ipc_messages队列中并唤醒等待的线程。

接收消息

  • 调用current_space()获取当前的IPC空间。
  • 调用current_map()获取虚拟空间
  • 调用ipc_mqueue_copyin()获取IPC队列。
  • 调用ipc_mqueue_receive()从队列中取出消息
  • 执行

端口

端口实际上就是一个整型的标识符,是如下结构(在osfmk/ipc/ipc_port.h中定义)的一个句柄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct ipc_port {
/*
* Initial sub-structure in common with ipc_pset
* First element is an ipc_object second is a
* message queue
*/
struct ipc_object ip_object;
struct ipc_mqueue ip_messages;
natural_t ip_sprequests:1, /* send-possible requests outstanding */
ip_spimportant:1, /* ... at least one is importance donating */
ip_impdonation:1, /* port supports importance donation */
ip_tempowner:1, /* dont give donations to current receiver */
ip_guarded:1, /* port guarded (use context value as guard) */
ip_strict_guard:1, /* Strict guarding; Prevents user manipulation of context values directly */
ip_reserved:2,
ip_impcount:24; /* number of importance donations in nested queue */
union {
struct ipc_space *receiver;
struct ipc_port *destination;
ipc_port_timestamp_t timestamp;
} data;
union {
ipc_kobject_t kobject;
ipc_importance_task_t imp_task;
uintptr_t alias;
} kdata;

struct ipc_port *ip_nsrequest;
struct ipc_port *ip_pdrequest;
struct ipc_port_request *ip_requests;
struct ipc_kmsg *ip_premsg;
mach_vm_address_t ip_context;
mach_port_mscount_t ip_mscount;
mach_port_rights_t ip_srights;
mach_port_rights_t ip_sorights;
#if MACH_ASSERT
#define IP_NSPARES 4
#define IP_CALLSTACK_MAX 16
/* queue_chain_t ip_port_links;*//* all allocated ports */
thread_t ip_thread; /* who made me? thread context */
unsigned long ip_timetrack; /* give an idea of "when" created */
uintptr_t ip_callstack[IP_CALLSTACK_MAX]; /* stack trace */
unsigned long ip_spares[IP_NSPARES]; /* for debugging */
#endif /* MACH_ASSERT */
} __attribute__((__packed__));

Mach接口生成器 MIG

Mach消息传递模型是远程调用(Remote Procedure Call,RPC)的一种现实(类似Thrift)。在/usr/include/mach目录下可以看到一些.defs文件,这些文件包含了Mach子系统(一组操作)的定义。操作类型如下:

IPC

  • Mach的每个Task都包含一个指针,这个指针指向一个IPC命名空间,这个IPC命名空间了包含了Task的端口,当然Task还可以获取系统范围的端口,例如:主机端口、特权端口(可以重启机器等)等。
  • 在用户态下,消息传递都是通过mach_msg()函数实现的,这个函数会触发一个mach陷阱mach_msg_trap(),接下来mach_msg_trap()又会调用mach_msg_overwrite_trap(),它会通过MACH_SEND_MSG和MACH_RCV_MSG来判断是发送操作,还是接收操作。
  • 期中内核态中还可以通过mach_msg_receive()和mach_msg_send()来收发数据。

主机、时钟、处理器、处理器集

主机对象 Host

主机就是一组“特殊”端口的集合,以及一组异常处理程序的集合,同时定义了一个锁用于保护异常处理的并发访问。结构如下:

1
2
3
4
5
struct	host {
decl_lck_mtx_data(,lock) /* lock to protect exceptions */
ipc_port_t special[HOST_MAX_SPECIAL_PORT + 1];
struct exception_action exc_actions[EXC_TYPES_COUNT];
};

时钟对象(Clock)

Mach内核提供了一个简单的“时钟”对象(在osfmk/kern/clock.h中定义)的抽象,这个对象用于计时和闹铃,期中最重要的内部API是clock_deadline_for_periodic_event(),调度器通过它设置了一个重复发生的通知–从而保证了多任务引擎的运转。

处理器对象(Processer)

在多核架构中每一个核心都可以看做是一个CPU,处理器被分配给处理器集,处理器是CPU的简单抽象,被Mach用于一些基本的操作,比如:启动和关闭一个CPU,向CPU分发要执行的线程。结构的定义(在osfmk/kern/processor.h)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct processor {
queue_chain_t processor_queue;/* idle/active queue link,
* MUST remain the first element */
int state; /* See below */
boolean_t is_SMT;
boolean_t is_recommended;
struct thread
*active_thread, /* thread running on processor */
*next_thread, /* next thread when dispatched */
*idle_thread; /* this processor's idle thread. */
processor_set_t processor_set; /* assigned set */
int current_pri; /* priority of current thread */
sched_mode_t current_thmode; /* sched mode of current thread */
sfi_class_id_t current_sfi_class; /* SFI class of current thread */
int cpu_id; /* platform numeric id */
timer_call_data_t quantum_timer; /* timer for quantum expiration */
uint64_t quantum_end; /* time when current quantum ends */
uint64_t last_dispatch; /* time of last dispatch */
uint64_t deadline; /* current deadline */
boolean_t first_timeslice; /* has the quantum expired since context switch */
#if defined(CONFIG_SCHED_TRADITIONAL) || defined(CONFIG_SCHED_MULTIQ)
struct run_queue runq; /* runq for this processor */
#endif
#if defined(CONFIG_SCHED_TRADITIONAL)
int runq_bound_count; /* # of threads bound to this processor */
#endif
#if defined(CONFIG_SCHED_GRRR)
struct grrr_run_queue grrr_runq; /* Group Ratio Round-Robin runq */
#endif
processor_t processor_primary; /* pointer to primary processor for
* secondary SMT processors, or a pointer
* to ourselves for primaries or non-SMT */
processor_t processor_secondary;
struct ipc_port * processor_self; /* port for operations */
processor_t processor_list; /* all existing processors */
processor_data_t processor_data; /* per-processor data */
};

其中最重要的是runq,这是分发到这个处理器的线程队列。

处理器集

处理器集就是一个或多个processor_t的分组,也被称为pset。pset通常维护三个队列:

active_queue:用于保存当前正在执行线程的CPU。
idle_queue:用于保存当前空闲的CPU(例如:正在执行idle_thread)。
pset_runq:保存了在这个集合中的所有CPU上执行的线程。
processor_set的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct processor_set {
queue_head_t active_queue; /* active processors */
queue_head_t idle_queue; /* idle processors */
queue_head_t idle_secondary_queue; /* idle secondary processors */
int online_processor_count;
int cpu_set_low, cpu_set_hi;
int cpu_set_count;
#if __SMP__
decl_simple_lock_data(,sched_lock) /* lock for above */
#endif
#if defined(CONFIG_SCHED_TRADITIONAL) || defined(CONFIG_SCHED_MULTIQ)
struct run_queue pset_runq; /* runq for this processor set */
#endif
#if defined(CONFIG_SCHED_TRADITIONAL)
int pset_runq_bound_count;
/* # of threads in runq bound to any processor in pset */
#endif
/* CPUs that have been sent an unacknowledged remote AST for scheduling purposes */
uint64_t pending_AST_cpu_mask;
#if defined(CONFIG_SCHED_DEFERRED_AST)
/*
* A seperate mask, for ASTs that we may be able to cancel. This is dependent on
* some level of support for requesting an AST on a processor, and then quashing
* that request later.
*
* The purpose of this field (and the associated codepaths) is to infer when we
* no longer need a processor that is DISPATCHING to come up, and to prevent it
* from coming out of IDLE if possible. This should serve to decrease the number
* of spurious ASTs in the system, and let processors spend longer periods in
* IDLE.
*\/
uint64_t pending_deferred_AST_cpu_mask;
#endif
struct ipc_port * pset_self; /* port for operations */
struct ipc_port * pset_name_self; /* port for information */
processor_set_t pset_list; /* chain of associated psets */
pset_node_t node;
};

XNU 怎么加载 App?

iOS 的可执行文件和动态库都是 Mach-O 格式,所以加载 APP 实际上就是加载 Mach-O 文件。
Mach-O header 信息结构代码如下:

1
2
3
4
5
6
7
8
9
10
struct mach_header_64 {
uint32_t magic; //64位还是32位
cpu_type_t cputype; //CPU 类型,比如 arm 或 X86
cpu_subtype_t cpusubtype; //CPU 子类型,比如 armv8
uint32_t filetype; // 文件类型
uint32_t ncmds; // load commands 的数量
uint32_t sizeofcmds; // load commands 大小
uint32_t flags; // 标签
uint32_t reserved; // 保留字段
}

其中,文件类型 filetype 表示了当前 Mach-O 属于哪种类型。Mach-O 包括以下几种类型。

  • OBJECT,指的是 .o 文件或者 .a 文件;
  • EXECUTE,指的是IPA 拆包后的文件;
  • DYLIB,指的是 .dylib 或 .framework 文件;
  • DYLINKER,指的是动态链接器;
  • DSYM,指的是保存有符号信息用于分析闪退信息的文件。

加载 Mach-O 文件,内核会 fork 进程,并对进程进行一些基本设置,比如为进程分配虚拟内存、为进程创 建主线程、代码签名等。用户态 dyld 会对 Mach-O 文件做库加载和符号解析。

官方文档地址

OS X简介

OS X为Macintosh用户和开发人员社区提供了许多好处。这些优点包括改进的可靠性和性能、增强的网络特性、基于对象的系统编程接口以及对行业标准的更多支持。

在创建OS X的过程中,苹果彻底改造了Mac OS核心操作系统。形成OS X的基础是内核。下图说明了OS X体系结构。

内核为OS x提供了许多增强功能,包括抢占、内存保护、增强的性能、改进的网络设施、对Macintosh(扩展版和标准版)和非Macintosh (UFS、ISO 9660等等)文件系统的支持、面向对象的api等等。其中两个特性,抢占和内存保护,导致了更健壮的环境。

在Mac OS 9中,应用程序协作共享处理器时间。类似地,所有应用程序之间共享计算机的内存。Mac OS 9是一个协作的多任务环境。如果一个应用程序不合作,那么所有进程的响应性都会受到影响。另一方面,实时应用程序(如多媒体)需要保证具有可预测的、时间关键的行为。

相比之下,OS X是一个先发制人的多任务环境。在OS X中,内核提供了强制合作,调度进程来共享时间(抢占)。这支持需要实时行为的应用程序。

在OS X中,进程通常不共享内存。相反,内核为每个进程分配自己的地址空间,控制对这些地址空间的访问。此控件确保任何应用程序都不能无意中访问或修改另一个应用程序的内存(保护)。规模不是问题;由于OS X包含了虚拟内存系统,每个应用程序都可以访问自己的4 GB地址空间。

从整体上看,所有应用程序都在用户空间中运行,但这并不意味着它们共享内存。用户空间只是所有用户级应用程序的组合地址空间的术语。内核本身有它自己的地址空间,称为内核空间。在OS X中,没有应用程序可以直接修改系统软件(内核)的内存。

虽然默认情况下用户进程不像Mac OS 9那样共享内存,但应用程序之间的通信(甚至内存共享)仍然是可能的。例如,内核提供了一组丰富的原语,允许进程之间共享一些信息。这些基本类型包括共享库、框架和POSIX共享内存。Mach消息传递提供了另一种方法,将内存从一个进程传递到另一个进程。然而,与Mac OS 9不同的是,如果没有程序员的显式操作,内存共享就无法实现。

达尔文操作系统

OS X内核是一个开源项目。内核以及OS X的其他核心部分统称为Darwin。Darwin是一个完整的操作系统,基于与OS x相同的许多技术。但是,Darwin不包括Apple专有的图形或应用程序层,比如Quartz、QuickTime、Cocoa、Carbon或OpenGL。

下图显示了Darwin和OS X之间的关系,它们都构建在相同的内核之上,但是OS X添加了核心服务、应用程序服务和QuickTime,以及经典的Carbon、Cocoa和Java (JDK)应用程序环境。Darwin和OS X都包含BSD命令行应用程序环境;然而,在OS X中,不需要使用环境,因此它对用户是隐藏的,除非用户选择访问它。

Darwin技术基于BSD、Mach 3.0和苹果技术。最重要的是,Darwin技术是开源技术,这意味着开发人员可以完全访问源代码。实际上,OS X第三方开发人员可以成为Darwin核心系统软件开发团队的一部分。开发人员还可以看到苹果在核心操作系统中是如何工作的,并在自己的产品中采用(或调整)代码。有关详细信息,请参阅Apple公共源许可证(APSL)。

因为相同的软件构成了OS X和Darwin的核心,所以开发人员可以创建在OS X和Darwin上运行的底层软件,而几乎不需要进行任何更改。唯一的区别可能是软件与应用程序环境的交互方式。

达尔文是基于许多来源的成熟技术。这项技术的很大一部分来自FreeBSD,这是4.4BSD的一个版本,提供了先进的网络、性能、安全和兼容性特性。该系统软件的其他部分,如Mach,基于苹果MkLinux项目、OS X服务器以及NeXT收购的技术。大部分代码与平台无关。所有核心操作系统代码都以源代码的形式提供。

选择核心技术有几个原因。Mach提供了一组干净的抽象,用于处理内存管理、进程间(和处理器间)通信(IPC)和其他低级操作系统功能。在当今快速变化的硬件环境中,这在操作系统和底层硬件之间提供了一个有用的隔离层。

BSD是一个精心设计的成熟操作系统,具有许多功能。事实上,当今大多数商业UNIX和类UNIX操作系统都包含大量BSD代码。BSD还提供了一组行业标准api。

新技术,如I/O工具包和网络内核扩展(NKEs),都是由苹果公司设计和设计的,以利用先进的功能,如面向对象编程模型提供的功能。OS X将这些新技术与经过时间检验的行业标准结合起来,创建了一个稳定、可靠、灵活和可扩展的操作系统。

系统架构

Darwin和OS X的基础层由几个体系结构组件组成,如下图所示。这些组件合在一起构成内核环境。

重要提示:注意OS X使用术语内核的方式与您可能期望的有所不同。

在传统的操作系统术语中,内核是一个很小的软件核心,它只提供实现附加操作系统服务所需的最小设施。——摘自《4.4 BSD操作系统的设计与实现》,McKusick, Bostic, Karels和Quarterman, 1996。

类似地,在传统的基于Mach的操作系统中,内核指的是Mach微内核,忽略了额外的底层代码,没有这些底层代码,Mach几乎做不了什么。

然而,在OS X中,内核环境包含的内容比Mach内核本身多得多。OS X内核环境包括Mach内核、BSD、I/O工具包、文件系统和网络组件。这些通常统称为内核。下面几节将简要描述这些组件。有关进一步的详细信息,请参阅具体的组成部分章节或参考书目中列出的参考资料。

因为OS X包含三个基本组件(Mach、BSD和I/O工具包),所以对于某些关键操作,常常有多达三个api。一般来说,所选择的API应该与正在使用它的内核部分匹配,而内核部分又由您的代码试图执行的操作决定。本章的其余部分将介绍Mach、BSD和I/O工具包,并概述这些组件提供的功能。

Mach

Mach管理CPU使用情况和内存等处理器资源,处理调度,提供内存保护,并为其他操作系统层提供以消息为中心的基础设施。

参看我的另外一篇Mach概览一章

Mach组件提供

  • 无类型进程间通信(IPC)
  • 远程过程调用(RPC)
  • 对称多处理(SMP)的调度程序支持
  • 对实时服务的支持
  • 虚拟内存支持
  • 支持寻呼机
  • 模块化的体系结构

有关Mach的一般信息可以在Mach概览一章中找到。有关调度的信息可以在Mach调度和线程接口一章中找到。有关VM系统的信息可以在内存和虚拟内存中找到。

BSD

在Mach层之上,BSD层提供“OS personality”api和服务。BSD层基于BSD内核,主要是FreeBSD。BSD组件提供

  • 文件系统
  • 联网(硬件设备级除外)
  • UNIX的安全模型
  • 系统调用的支持
  • BSD过程模型,包括过程id和信号
  • FreeBSD内核api
  • 许多POSIX api
  • 对pthreads (POSIX线程)的内核支持

BSD组件在BSD概述一章中有更详细的描述。

网络

OS X网络利用BSD的高级网络功能来提供对现代特性的支持,比如网络地址转换(NAT)和防火墙。网络组件提供

  • 4.4BSD TCP/IP栈和套接字api
  • 同时支持IP和DDP (AppleTalk传输)
  • multihoming
  • 路由
  • 多播支持
  • 服务器调优
  • 信息包过滤
  • Mac OS经典支持(通过过滤器)

有关网络的更多信息可以在网络体系结构一章中找到。

文件系统

OS X支持多种类型的文件系统,包括HFS、HFS+、UFS、NFS、ISO 9660等。默认的文件系统类型是HFS+;OS X引导(和“根”)来自HFS+、UFS、ISO、NFS和UDF。OS X文件系统的高级功能包括一个增强的虚拟文件系统(VFS)设计。VFS提供了分层的体系结构(文件系统是可堆叠的)。文件系统组件提供

  • utf - 8 (Unicode)支持
  • 与以前版本的Mac OS相比,性能有所提高。

更多信息可以在章节文件系统概述中找到。

I / O设备

I/O工具包为简化驱动程序开发提供了一个框架,支持多种类型的设备。I/O工具包提供了一个面向对象的I/O体系结构,该体系结构在c++的一个受限子集中实现。I/O工具包框架是模块化和可扩展的。I/O工具包组件提供

  • 真正的即插即用
  • 动态设备管理
  • 动态(按需)加载驱动程序
  • 桌面系统和便携式设备的电源管理
  • 多处理器能力

I/O工具包在I/O工具包概述一章中有更详细的描述。

内核扩展

OS X提供了一种内核扩展机制,作为一种允许将代码片段动态加载到内核空间的方法,而不需要重新编译。这些代码通常称为插件plug-ins,或者在OS X内核环境中称为kernel extensionsKEXTs

因为KEXTs同时提供模块化和动态可加载性,所以对于需要访问不导出到用户空间的接口的任何相对自包含的服务,KEXTs都是一个自然的选择。内核环境的许多组件都支持这种扩展机制,尽管它们以不同的方式支持。

例如,一些新的网络特性涉及到使用网络内核扩展(NKEs)。这些将在网络体系结构一章讨论。

动态添加新文件系统实现的能力基于VFS KEXTs, I/O工具包中的设备驱动程序和设备族使用KEXTs实现。对于编写驱动程序或编写代码以支持新的卷格式或网络协议的开发人员来说,KEXTs使开发变得更加容易。在内核扩展概述一章中将更详细地讨论KEXTs。

置顶

NSOperation的小tip

在ios4以前,只有非并发的情况下,队列会为operation开启一个线程来执行。如果是并发的情况,operation需要自己创建一个线程来执行。所以说,NSoperation的并发和非并发不是传统意义上的串行和并行。
但是在ios4以后,不管是并发还是非并发,队列都会为operation提供一个线程来执行。所以isConcurrent这个变量也就没有用处了。
但是,这里还设涉及到了两个方法,start和main.
按照官方文档所说,如果是非并发就使用main,并发就使用start。

那现在并发和非并发已经没有区别了,start和main的区别在哪里呢?

main方法的话,如果main方法执行完毕,那么整个operation就会从队列中被移除。如果你是一个自定义的operation并且它是某些类的代理,这些类恰好有异步方法,这是就会找不到代理导致程序出错了。
然而start方法就算执行完毕,它的finish属性也不会变,因此你可以控制这个operation的生命周期了。
然后在任务完成之后手动cancel掉这个operation即可。

说句简单的, main不需要自己管理状态,op会自己移出。 start需要自己管理op,包括状态。
参看SDWebImage的实现,多阅读源码,有助于理解。

简介,下面有GCD 源码解析,

进程

  • 1.进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
  • 2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
  • 3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源

线程

  • 1.程序执行流的最小单元,线程是进程中的一个实体.
  • 2.一个进程要想执行任务,必须至少有一条线程.应用程序启动的时候,系统会默认开启一条线程,也就是主线程
    阅读全文 »