__CFRunLoopServiceMachPort函数详解
2026年1月23日 18:45
借助AI辅助。
__CFRunLoopServiceMachPort 函数逐行注释
这是 RunLoop 在 macOS 上休眠和唤醒的核心函数,通过 mach_msg() 系统调用实现线程阻塞。
完整注释代码
static Boolean __CFRunLoopServiceMachPort(
mach_port_name_t port, // 要等待的端口(或端口集合)
mach_msg_header_t **buffer, // 消息缓冲区指针的地址
size_t buffer_size, // 缓冲区大小
mach_port_t *livePort, // [输出] 被唤醒的端口
mach_msg_timeout_t timeout, // 超时时间(毫秒,TIMEOUT_INFINITY=无限)
voucher_mach_msg_state_t *_Nonnull voucherState, // voucher 状态(追踪用)
voucher_t *voucherCopy, // voucher 副本
CFRunLoopRef rl, // RunLoop(用于追踪)
CFRunLoopModeRef rlm // Mode(用于追踪)
) {
// ========================================
// 函数返回值说明:
// • true: 收到消息,livePort 指向唤醒的端口
// • false: 超时或错误
// ========================================
Boolean originalBuffer = true;
// 标记是否使用原始缓冲区
// true: 使用调用者提供的栈上缓冲区
// false: 使用动态分配的堆缓冲区(消息太大时)
kern_return_t ret = KERN_SUCCESS;
// Mach 内核调用的返回值
// 初始化为成功状态
for (;;) {
// 无限循环,直到:
// 1. 成功接收消息(return true)
// 2. 超时(return false)
// 3. 致命错误(break 后 HALT)
/* In that sleep of death what nightmares may come ... */
// 莎士比亚《哈姆雷特》引用:"在死亡的睡眠中会有什么噩梦来临..."
// 暗示线程即将进入"休眠"状态
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
// 获取消息指针
// *buffer 是指向缓冲区的指针
// ========================================
// 步骤 1: 初始化消息头
// ========================================
msg->msgh_bits = 0;
// 消息标志位,初始化为 0
// mach_msg 会设置适当的接收标志
msg->msgh_local_port = port;
// 设置本地端口(接收端口)
// 这是我们要等待的端口(或端口集合)
msg->msgh_remote_port = MACH_PORT_NULL;
// 远程端口(发送目标)设为空
// 因为我们只接收,不发送
msg->msgh_size = buffer_size;
// 设置缓冲区大小
// 告诉内核我们能接收多大的消息
msg->msgh_id = 0;
// 消息 ID,初始化为 0
// 接收后会包含实际的消息 ID
// ========================================
// 步骤 2: 记录追踪事件(调试用)
// ========================================
if (TIMEOUT_INFINITY == timeout) {
// 如果是无限等待
CFRUNLOOP_SLEEP();
// 探针宏:记录休眠事件(DTrace)
cf_trace(KDEBUG_EVENT_CFRL_SLEEP, port, 0, 0, 0);
// 内核追踪:记录 RunLoop 即将休眠
} else {
// 如果有超时时间(轮询模式)
CFRUNLOOP_POLL();
// 探针宏:记录轮询事件
cf_trace(KDEBUG_EVENT_CFRL_POLL, port, 0, 0, 0);
// 内核追踪:记录 RunLoop 轮询
}
cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_END, rl, rlm, port, timeout);
// 追踪:RunLoop 运行阶段结束
cf_trace(KDEBUG_EVENT_CFRL_IS_WAITING | DBG_FUNC_START, rl, rlm, port, timeout);
// 追踪:开始等待阶段
// ========================================
// 步骤 3: 调用 mach_msg 等待 ⭐⭐⭐
// 【这是整个 RunLoop 最核心的一行代码!】
// ========================================
ret = mach_msg(
msg, // 消息缓冲区
// 选项组合:
MACH_RCV_MSG | // 接收消息模式
MACH_RCV_VOUCHER | // 接收 voucher(追踪信息)
MACH_RCV_LARGE | // 支持大消息(自动重新分配缓冲区)
((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0) |
// 如果有超时,添加 MACH_RCV_TIMEOUT 标志
MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
// 接收 trailer(消息尾部附加信息)
MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
// trailer 包含 audit token 和 voucher
0, // 发送大小(不发送,所以为 0)
msg->msgh_size, // 接收缓冲区大小
port, // 接收端口(或端口集合)
timeout, // 超时时间(毫秒)
MACH_PORT_NULL // 通知端口(不使用)
);
// 【线程在这里阻塞】
// 等待以下情况之一:
// 1. port 收到消息 → 返回 MACH_MSG_SUCCESS
// 2. 超时 → 返回 MACH_RCV_TIMED_OUT
// 3. 消息太大 → 返回 MACH_RCV_TOO_LARGE
// 4. 其他错误 → 返回错误码
cf_trace(KDEBUG_EVENT_CFRL_IS_WAITING | DBG_FUNC_END, rl, rlm, port, timeout);
// 追踪:等待阶段结束(被唤醒)
cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_START, rl, rlm, port, timeout);
// 追踪:RunLoop 运行阶段开始
// ========================================
// 步骤 4: 处理 voucher(性能追踪)
// ========================================
// Take care of all voucher-related work right after mach_msg.
// 在 mach_msg 之后立即处理所有 voucher 相关工作
// If we don't release the previous voucher we're going to leak it.
// 如果不释放之前的 voucher,会造成内存泄漏
voucher_mach_msg_revert(*voucherState);
// 恢复之前的 voucher 状态
// 释放上次收到的 voucher(如果有)
// Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
// 调用者负责调用 voucher_mach_msg_revert
// 这个调用让接收到的 voucher 成为当前的
*voucherState = voucher_mach_msg_adopt(msg);
// 采用(adopt)消息中的 voucher
// 返回新的 voucher 状态
// voucher 用于追踪消息的来源和上下文
if (voucherCopy) {
// 如果调用者需要 voucher 副本
*voucherCopy = NULL;
// 重置为 NULL
// 调用者可以在需要时拷贝
}
CFRUNLOOP_WAKEUP(ret);
// 探针宏:记录唤醒事件,传入返回值
cf_trace(KDEBUG_EVENT_CFRL_DID_WAKEUP, port, 0, 0, 0);
// 内核追踪:记录 RunLoop 被唤醒
// ========================================
// 步骤 5: 处理返回结果
// ========================================
if (MACH_MSG_SUCCESS == ret) {
// 情况 1: 成功接收到消息
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
// 返回被唤醒的端口
// 调用者通过这个值判断唤醒源:
// • _wakeUpPort → 手动唤醒
// • _timerPort → 定时器
// • dispatchPort → GCD 主队列
// • 其他 → Source1
return true;
// 返回成功,结束函数
}
if (MACH_RCV_TIMED_OUT == ret) {
// 情况 2: 接收超时(正常情况)
if (!originalBuffer) free(msg);
// 如果使用了动态分配的缓冲区,释放它
*buffer = NULL;
// 将缓冲区指针设为 NULL
*livePort = MACH_PORT_NULL;
// 没有唤醒端口(超时)
return false;
// 返回失败(超时)
}
if (MACH_RCV_TOO_LARGE != ret) {
// 情况 3: 其他错误(非 "消息太大")
// 这些是致命错误,需要崩溃
if (((MACH_RCV_HEADER_ERROR & ret) == MACH_RCV_HEADER_ERROR) ||
(MACH_RCV_BODY_ERROR & ret) == MACH_RCV_BODY_ERROR) {
// 如果是消息头错误或消息体错误
kern_return_t specialBits = MACH_MSG_MASK & ret;
// 提取特殊错误位
if (MACH_MSG_IPC_SPACE == specialBits) {
// IPC 空间不足
CRSetCrashLogMessage("Out of IPC space");
// 设置崩溃日志消息
// 可能原因:Mach 端口泄漏
} else if (MACH_MSG_VM_SPACE == specialBits) {
// 虚拟内存空间不足
CRSetCrashLogMessage("Out of VM address space");
// 内存耗尽
} else if (MACH_MSG_IPC_KERNEL == specialBits) {
// 内核 IPC 资源短缺
CRSetCrashLogMessage("Kernel resource shortage handling IPC");
// 内核资源不足
} else if (MACH_MSG_VM_KERNEL == specialBits) {
// 内核 VM 资源短缺
CRSetCrashLogMessage("Kernel resource shortage handling out-of-line memory");
// 内核内存不足
}
} else {
// 其他类型的错误
CRSetCrashLogMessage(mach_error_string(ret));
// 设置错误字符串为崩溃日志
}
break;
// 跳出循环,准备崩溃
}
// ========================================
// 步骤 6: 处理 MACH_RCV_TOO_LARGE(消息太大)
// ========================================
// 如果执行到这里,说明 ret == MACH_RCV_TOO_LARGE
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
// 计算需要的缓冲区大小
// round_msg: 向上取整到合适的大小
// msg->msgh_size: 实际消息大小(已在 msg 中设置)
// MAX_TRAILER_SIZE: trailer 的最大大小
if (originalBuffer) *buffer = NULL;
// 如果之前使用的是原始缓冲区(栈上的)
// 将指针设为 NULL,下面会分配新的
originalBuffer = false;
// 标记不再使用原始缓冲区
*buffer = __CFSafelyReallocate(*buffer, buffer_size, NULL);
// 重新分配更大的缓冲区
// 如果 *buffer 是 NULL,相当于 malloc
// 否则相当于 realloc
// 下次循环会使用新缓冲区重新接收
if (voucherCopy != NULL && *voucherCopy != NULL) {
// 如果有 voucher 副本
os_release(*voucherCopy);
// 释放 voucher(引用计数 -1)
}
}
// 继续循环,使用新缓冲区重新调用 mach_msg
HALT;
// 如果跳出循环(因为致命错误),停止程序
// HALT 宏会触发断点或终止进程
return false;
// 这行代码实际不会执行(HALT 不会返回)
// 但保留以满足编译器要求
}
函数执行流程图
┌─────────────────────────────────────────────────────────────┐
│ 开始 __CFRunLoopServiceMachPort │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 初始化消息头 │
│ • msgh_local_port = port (等待的端口) │
│ • msgh_size = buffer_size │
│ • 其他字段置 0 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 记录追踪事件 │
│ • SLEEP (无限等待) 或 POLL (有超时) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ⭐ 调用 mach_msg() - 线程在此阻塞 ⭐ │
│ │
│ 等待事件: │
│ • Timer 端口有消息 │
│ • Source1 端口有消息 │
│ • dispatch 端口有消息 │
│ • _wakeUpPort 有消息 │
│ • 超时 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 被唤醒,检查返回值 │
└─────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────────┐
│ SUCCESS │ │ TIMED_OUT│ │ TOO_LARGE │
└─────────┘ └──────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────────┐
│处理voucher│ │ 清理缓冲 │ │ 扩大缓冲区 │
│返回true │ │返回false │ │ 重新接收 │
└─────────┘ └──────────┘ └──────────────┘
│
▼
┌──────────┐
│返回循环开始│
└──────────┘
关键点说明
1. mach_msg 的两种模式
| 模式 | timeout 值 | 行为 |
|---|---|---|
| 休眠模式 | TIMEOUT_INFINITY |
永久阻塞,直到收到消息 |
| 轮询模式 |
0 或小值 |
立即返回或短暂等待 |
2. 可能的返回值
| 返回值 | 说明 | 处理方式 |
|---|---|---|
MACH_MSG_SUCCESS |
成功收到消息 | 返回 true |
MACH_RCV_TIMED_OUT |
超时 | 返回 false |
MACH_RCV_TOO_LARGE |
消息太大 | 扩大缓冲区重试 |
| 其他错误 | 致命错误 | 崩溃(HALT) |
3. voucher 的作用
voucher 是 macOS 的性能追踪机制:
├── 追踪消息来源
├── 记录 QoS(服务质量)
├── 性能分析(Instruments)
└── 调试辅助
4. 缓冲区管理
初始: 使用栈缓冲区(3KB)
↓
mach_msg 返回 TOO_LARGE
↓
计算实际大小: msg->msgh_size + MAX_TRAILER_SIZE
↓
动态分配堆缓冲区
↓
重新调用 mach_msg
↓
成功接收大消息
总结
__CFRunLoopServiceMachPort 是 RunLoop 休眠的核心:
- 准备消息头:设置接收端口和缓冲区
- 调用 mach_msg:线程阻塞等待 ⭐
- 被唤醒:检查返回值和 livePort
- 处理特殊情况:超时、消息过大、错误
- 返回结果:告诉调用者是哪个端口唤醒的
这就是 RunLoop "无事件时不消耗 CPU" 的秘密!