美文网首页RunLoop
runloop底层实现(-)weakup&sleep

runloop底层实现(-)weakup&sleep

作者: 无边小猪 | 来源:发表于2016-09-02 18:05 被阅读0次
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ......
    dispatch_source_t timeout_timer = NULL;
        struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
        if (seconds <= 0.0) { // instant timeout
            seconds = 0.0;
            timeout_context->termTSR = 0ULL;
        } else if (seconds <= TIMER_INTERVAL_LIMIT) {
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
            timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
            dispatch_retain(timeout_timer);
            timeout_context->ds = timeout_timer;
            timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
            timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
            dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
            dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
            dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
            uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
            dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
            dispatch_resume(timeout_timer);
        } else { // infinite timeout
            seconds = 9999999999.0;
            timeout_context->termTSR = UINT64_MAX;
        }
    ......
     
    }
    

    上述代码执行了__CFRunLoopTimeout,下面是__CFRunLoopTimeout的实现

    static void __CFRunLoopTimeout(void *arg) {
        struct __timeout_context *context = (struct __timeout_context *)arg;
        context->termTSR = 0ULL;
        CFRUNLOOP_WAKEUP_FOR_TIMEOUT();
        CFRunLoopWakeUp(context->rl);
        // The interval is DISPATCH_TIME_FOREVER, so this won't fire again
    }
    

    __CFRunLoopTimeout又调用了CFRunLoopWakeUp,CFRunLoopWakeUp的实现如下:

    void CFRunLoopWakeUp(CFRunLoopRef rl) {
    ......
        kern_return_t ret;
        ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);
        if (ret != MACH_MSG_SUCCESS && ret != MACH_SEND_TIMED_OUT) CRASH("*** Unable to send message to wake up port. (%d) ***", ret);
        __CFRunLoopUnlock(rl);
    }
    

    其中__CFSendTrivialMachMessage的实现如下

    static uint32_t __CFSendTrivialMachMessage(mach_port_t port, uint32_t msg_id, CFOptionFlags options, uint32_t timeout) {
        kern_return_t result;
        mach_msg_header_t header;
        header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
        header.msgh_size = sizeof(mach_msg_header_t);
        header.msgh_remote_port = port;
        header.msgh_local_port = MACH_PORT_NULL;
        header.msgh_id = msg_id;
        result = mach_msg(&header, MACH_SEND_MSG|options, header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
        if (result == MACH_SEND_TIMED_OUT) mach_msg_destroy(&header);
        return result;
    }
    

    上面的代码只看一句mach_msg就可以了,这句发送了一个消息给rl->_wakeUpPort唤醒了runloop

    而_wakeUpPort是如何工作的呢?
    在看下面runloop创建的代码

    static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
       CFRunLoopRef loop = NULL;
       CFRunLoopModeRef rlm;
       uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
       loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
       if (NULL == loop) {
    return NULL;
       }
      (void)__CFRunLoopPushPerRunData(loop);
       __CFRunLoopLockInit(&loop->_lock);
       loop->_wakeUpPort = __CFPortAllocate();
       if (CFPORT_NULL == loop->_wakeUpPort) HALT;
       __CFRunLoopSetIgnoreWakeUps(loop);
       loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
       CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
       loop->_commonModeItems = NULL;
       loop->_currentMode = NULL;
       loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
       loop->_blocks_head = NULL;
       loop->_blocks_tail = NULL;
       loop->_counterpart = NULL;
       loop->_pthread = t;
       loop->_winthread = 0;
      rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
       if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
       return loop;
    }
    

    其中有一句 loop->_wakeUpPort = __CFPortAllocate();
    那么接下来再看__CFPortAllocate:

    static __CFPort __CFPortAllocate(void) {
       __CFPort result = CFPORT_NULL;
       kern_return_t ret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &result);
       if (KERN_SUCCESS != ret) {
           char msg[256];
           snprintf(msg, 256, "*** The system has no mach ports available. You may be able to diagnose which application(s) are using ports by using 'top' or Activity Monitor. (%d) ***", ret);
           CRSetCrashLogMessage(msg); 
           __THE_SYSTEM_HAS_NO_PORTS_AVAILABLE__(ret); 
           return CFPORT_NULL;
       }
       ret = mach_port_insert_right(mach_task_self(), result, result, MACH_MSG_TYPE_MAKE_SEND);
       if (KERN_SUCCESS != ret) CRASH("*** Unable to set send right on mach port. (%d) ***", ret);
       mach_port_limits_t limits;
       limits.mpl_qlimit = 1;
       ret = mach_port_set_attributes(mach_task_self(), result, MACH_PORT_LIMITS_INFO, (mach_port_info_t)&limits, MACH_PORT_LIMITS_INFO_COUNT);
       if (KERN_SUCCESS != ret) CRASH("*** Unable to set attributes on mach port. (%d) ***", ret);    
       return result;
    }
    

    上述方法完成了wakeupport的创建
    那么wakeupport收到消息后由谁来处理呢?
    看下面的方法

    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) {
        Boolean originalBuffer = true;
        kern_return_t ret = KERN_SUCCESS;
        for (;;) { /* In that sleep of death what nightmares may come ... */
            mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
            msg->msgh_bits = 0;
            msg->msgh_local_port = port;
            msg->msgh_remote_port = MACH_PORT_NULL;
            msg->msgh_size = buffer_size;
            msg->msgh_id = 0;
            if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
            ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT :0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
            CFRUNLOOP_WAKEUP(ret);
            if (MACH_MSG_SUCCESS == ret) {
                *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
                return true;
            }
            if (MACH_RCV_TIMED_OUT == ret) {
                if (!originalBuffer) free(msg);
                *buffer = NULL;
                *livePort = MACH_PORT_NULL;
                return false;
            }
            if (MACH_RCV_TOO_LARGE != ret) break;
            buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
            if (originalBuffer) *buffer = NULL;
            originalBuffer = false;
            *buffer = realloc(*buffer, buffer_size);
        }
        HALT;
        return false;
    }
    

    上述类执行之后回停留在ret = mach_msg... 这一句等待消息,而当最前面的那行代码ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);执行后 ret = mach_msg... 将收到消息继续执行。
    闭环---

    总结:
    sleep本质上来说就是通过调用mach_msg使runloop进入等待消息的状态。
    wakeup本质上来说就是通过发送一套消息给mach_msg监听的端口打破mach_msg等待的状态继续向下执行。

    相关文章

      网友评论

        本文标题:runloop底层实现(-)weakup&sleep

        本文链接:https://www.haomeiwen.com/subject/zoqkettx.html