普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月24日首页

__CFRunLoopServiceMachPort函数详解

作者 iOS在入门
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 休眠的核心

  1. 准备消息头:设置接收端口和缓冲区
  2. 调用 mach_msg:线程阻塞等待 ⭐
  3. 被唤醒:检查返回值和 livePort
  4. 处理特殊情况:超时、消息过大、错误
  5. 返回结果:告诉调用者是哪个端口唤醒的

这就是 RunLoop "无事件时不消耗 CPU" 的秘密!

mach_msg_header_t详解

作者 iOS在入门
2026年1月23日 18:01

借助AI能力分析。

mach_msg_header_t - Mach 消息头

作用

这是 Mach 消息的头部结构,用于在 macOS/iOS 的进程间(或线程间)传递消息。

6个字段详解

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;           // 消息ID(自定义)
} mach_msg_header_t;

形象比喻(信封):

字段 对应信封上的 说明
msgh_remote_port 收件人地址 消息发往哪个端口
msgh_local_port 回信地址 如果需要回复,发到这里
msgh_size 信件大小 包括信封和内容
msgh_bits 邮寄方式 挂号信、平信等
msgh_id 信件编号 用于区分不同类型的信
msgh_voucher_port 追踪单号 用于追踪和调试

在 RunLoop 中的使用

1. 发送唤醒消息(CFRunLoopWakeUp)

// 构造消息头
mach_msg_header_t header;
header.msgh_remote_port = rl->_wakeUpPort;  // 发往唤醒端口
header.msgh_local_port = MACH_PORT_NULL;    // 不需要回复
header.msgh_size = sizeof(mach_msg_header_t); // 只有头,无内容
header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
header.msgh_id = 0;

// 发送(唤醒 RunLoop)
mach_msg(&header, MACH_SEND_MSG, ...);

2. 接收消息(RunLoop 休眠)

// 准备缓冲区
uint8_t buffer[3 * 1024];
mach_msg_header_t *msg = (mach_msg_header_t *)buffer;

msg->msgh_local_port = waitSet;  // 在哪个端口等待
msg->msgh_size = sizeof(buffer);  // 缓冲区大小

// 阻塞等待(线程休眠)
mach_msg(msg, MACH_RCV_MSG, ...);

// 被唤醒后,检查消息来源
if (msg->msgh_local_port == _wakeUpPort) {
    // 手动唤醒
} else if (msg->msgh_local_port == _timerPort) {
    // 定时器到期
}

关键理解

mach_msg_header_t 是 Mach IPC 的核心

  1. 通信基础:所有 Mach 消息都以这个头开始
  2. 路由信息:指明消息的来源和去向
  3. RunLoop 休眠/唤醒:通过接收/发送消息实现

完整消息结构

┌──────────────────────┐
│ mach_msg_header_t    │ ← 消息头(必需)
├──────────────────────┤
│ 消息体(可选)        │ ← 实际数据
├──────────────────────┤
│ trailer(可选)       │ ← 附加信息
└──────────────────────┘

RunLoop 的简化消息:只有头部,无消息体(称为 "trivial message"),足以唤醒线程。

❌
❌