变态的libDispatch源码分析-全局队列异步延时任务处理过程-设置计时器与插入ds

13 篇文章 0 订阅
8 篇文章 0 订阅

1. 为ds设置计时器,并将其加入到计时任务队列中

(1) .入口


dispatch_source_set_timer(ds, when, 0, 0);

when参数由用户提供,有下面几种情况:

#define DISPATCH_TIME_NOW 0
#define DISPATCH_TIME_FOREVER (~0ull)

0: 表示立即执行;

-1: 永久等待,若是-1将设置为最大值;

other: 需要等待的时间;

进入到如下代码:

void
dispatch_source_set_timer(dispatch_source_t ds,
	dispatch_time_t start,
	uint64_t interval,
	uint64_t leeway)
{
	struct dispatch_set_timer_params *params;
	
	// we use zero internally to mean disabled
	if (interval == 0) {
		interval = 1;
	} else if ((int64_t)interval < 0) {
		// 6866347 - make sure nanoseconds won't overflow
		interval = INT64_MAX;
	}
(a).
	// Suspend the source so that it doesn't fire with pending changes
	// The use of suspend/resume requires the external retain/release
	dispatch_retain(ds);
	dispatch_suspend(ds);
(b).
	if (start == DISPATCH_TIME_NOW) {
		start = _dispatch_absolute_time();
	} else if (start == DISPATCH_TIME_FOREVER) {
		start = INT64_MAX;
	}
	if ((int64_t)leeway < 0) {
		leeway = INT64_MAX;
	}

	while (!(params = malloc(sizeof(struct dispatch_set_timer_params)))) {
		sleep(1);
	}
(c).
	params->ds = ds;
	params->values.flags = ds->ds_timer.flags;

	if ((int64_t)start < 0) {
		// wall clock
		params->ident = DISPATCH_TIMER_INDEX_WALL;
		params->values.start = -((int64_t)start);
		params->values.target = -((int64_t)start);
		params->values.interval = interval;
		params->values.leeway = leeway;
		params->values.flags |= DISPATCH_TIMER_WALL_CLOCK;
	} else {
		// absolute clock
		params->ident = DISPATCH_TIMER_INDEX_MACH;
		params->values.start = start;
		params->values.target = start;
		params->values.interval = _dispatch_time_nano2mach(interval);
		params->values.leeway = _dispatch_time_nano2mach(leeway);
		params->values.flags &= ~DISPATCH_TIMER_WALL_CLOCK;
	}
(d).
	dispatch_barrier_async_f(&_dispatch_mgr_q, params, _dispatch_source_set_timer2);
}

(a). suspend ds
void
dispatch_suspend(dispatch_object_t dou)
{
	struct dispatch_object_s *obj = DO_CAST(dou);

	if (slowpath(obj->do_ref_cnt == DISPATCH_OBJECT_GLOBAL_REFCNT)) {
		return;
	}
	(void)dispatch_atomic_add(&obj->do_suspend_cnt, DISPATCH_OBJECT_SUSPEND_INTERVAL);
}

将obj->do_suspend_cnt 加上2;

(b). 设置时间值
(c). 封装params,假设提供的when >0;
	} else {
		// absolute clock
		params->ident = DISPATCH_TIMER_INDEX_MACH;
		params->values.start = start;
		params->values.target = start;
		params->values.interval = _dispatch_time_nano2mach(interval);
		params->values.leeway = _dispatch_time_nano2mach(leeway);
		params->values.flags &= ~DISPATCH_TIMER_WALL_CLOCK;
	}

重要关心的是start和Interval:

start: 到时还需要等待的时间;

internval,用于计时等待的粒度,比如还未到时,那么还需要等多久,以此作为粒度;

(d). _dispatch_source_set_timer2

如果熟悉了Libdispatch的风格,我们会知道_dispatch_source_set_timer2又将是下一个要被封装到任务里面的函数,而params将作为它的参数:

static void
_dispatch_source_set_timer2(void *context)
{
	struct dispatch_set_timer_params *params = context;
	dispatch_source_t ds = params->ds;
	ds->ds_ident_hack = params->ident;
	ds->ds_timer = params->values;
	_dispatch_timer_list_update(ds);
	dispatch_resume(ds);
	dispatch_release(ds);
	free(params);
}
这段代码,接下来还会讲到的。总之它会为ds的计时器设置相关的值;同时将ds插入到计时任务队列中去;

(2). _dispatch_mgr_q

dispatch_barrier_async_f(&_dispatch_mgr_q, params, _dispatch_source_set_timer2);

这段代码与众不同在于_dispatch_mgr_q。

(a). 结构
struct dispatch_queue_s _dispatch_mgr_q = {
	.do_vtable = &_dispatch_queue_mgr_vtable,
	.do_ref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
	.do_xref_cnt = DISPATCH_OBJECT_GLOBAL_REFCNT,
	.do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,
	.do_targetq = &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_COUNT - 1],

	.dq_label = "com.apple.libdispatch-manager",
	.dq_width = 1,
	.dq_serialnum = 2,
};
重点:

.do_vtable = &_dispatch_queue_mgr_vtable
.do_targetq = &_dispatch_root_queues[DISPATCH_ROOT_QUEUE_COUNT - 1]:目标队列,凡事存在目标队列的,最先都会将这个队列压进目标队列,然后执行;
dq_width = 1, 宽度只有1,表示是一个FIFO的队列
(b). 封装并压栈
dispatch_barrier_async_f(dispatch_queue_t dq, void *context, dispatch_function_t func)
{
	dispatch_continuation_t dc = fastpath(_dispatch_continuation_alloc_cacheonly());

	if (!dc) {
		return _dispatch_barrier_async_f_slow(dq, context, func);
	}

	dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
	dc->dc_func = func;
	dc->dc_ctxt = context;

	_dispatch_queue_push(dq, dc);
}

进入到

void
_dispatch_queue_push_list_slow(dispatch_queue_t dq, struct dispatch_object_s *obj)
{
	// The queue must be retained before dq_items_head is written in order
	// to ensure that the reference is still valid when _dispatch_wakeup is
	// called. Otherwise, if preempted between the assignment to
	// dq_items_head and _dispatch_wakeup, the blocks submitted to the
	// queue may release the last reference to the queue when invoked by
	// _dispatch_queue_drain. <rdar://problem/6932776>
	_dispatch_retain(dq);
	dq->dq_items_head = obj;
	_dispatch_wakeup(dq);
	_dispatch_release(dq);
}
这个dq是_dispatch_mgr_q

的去obj封装的是_dispatch_source_set_timer2

再次进入到_dispatch_wakeup
_dispatch_wakeup(dispatch_object_t dou)
{
	dispatch_queue_t tq;

	if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) { //    .do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_LOCK,//1U
		return NULL;
	}
	if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
		return NULL;
	}

	if (!_dispatch_trylock(dou._do)) {
		return NULL;
	}
	_dispatch_retain(dou._do);
	tq = dou._do->do_targetq;
	_dispatch_queue_push(tq, dou._do);
	return tq;	// libdispatch doesn't need this, but the Instrument DTrace probe does
}
看看mgr的vtable
static const struct dispatch_queue_vtable_s _dispatch_queue_mgr_vtable = {
	.do_type = DISPATCH_QUEUE_MGR_TYPE,
	.do_kind = "mgr-queue",
	.do_invoke = _dispatch_mgr_invoke,
	.do_debug = dispatch_queue_debug,
	.do_probe = _dispatch_mgr_wakeup,
};

	if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
		return NULL;
	}


这段代码将进入到_dispatch_mgr_wakeup,下面解开Kevent在GCD中的面纱。

(3). kevent

(a).准备注册事件
static bool
_dispatch_mgr_wakeup(dispatch_queue_t dq)
{
	static const struct kevent kev = {
		.ident = 1,
		.filter = EVFILT_USER,
		.fflags = NOTE_TRIGGER,
	};

	_dispatch_debug("waking up the _dispatch_mgr_q: %p", dq);

	_dispatch_update_kq(&kev);

	return false;
}

这里解释Kevent相关的知识了,可以去google查相关知识;其作用就是监听-注册事件的模式,可以参看使用 kqueue 在 FreeBSD 上开发高性能应用服务器
static const struct kevent kev = {
  .ident = 1,
  .filter = EVFILT_USER,
  .fflags = NOTE_TRIGGER,
};

这里创建了一个Kevent,filter为EVFLT_USER: 很重要

接下来进入到_dispatch_update_kq
void
_dispatch_update_kq(const struct kevent *kev)
{
	struct kevent kev_copy = *kev;
	kev_copy.flags |= EV_RECEIPT;

	if (kev_copy.flags & EV_DELETE) {
		switch (kev_copy.filter) {
		case EVFILT_READ:
			if (FD_ISSET((int)kev_copy.ident, &_dispatch_rfds)) {
				FD_CLR((int)kev_copy.ident, &_dispatch_rfds);
				_dispatch_rfd_ptrs[kev_copy.ident] = 0;
				return;
			}
		case EVFILT_WRITE:
			if (FD_ISSET((int)kev_copy.ident, &_dispatch_wfds)) {
				FD_CLR((int)kev_copy.ident, &_dispatch_wfds);
				_dispatch_wfd_ptrs[kev_copy.ident] = 0;
				return;
			}
		default:
			break;
		}
	}
	
	int rval = kevent(_dispatch_get_kq(), &kev_copy, 1, &kev_copy, 1, NULL);
	if (rval == -1) { 
		// If we fail to register with kevents, for other reasons aside from
		// changelist elements.
		(void)dispatch_assume_zero(errno);
		//kev_copy.flags |= EV_ERROR;
		//kev_copy.data = error;
		return;
	}

	// The following select workaround only applies to adding kevents
	if (!(kev->flags & EV_ADD)) {
		return;
	}

	switch (kev_copy.data) {
	case 0:
		return;
	case EBADF:
		break;
	default:
		// If an error occurred while registering with kevent, and it was 
		// because of a kevent changelist processing && the kevent involved
		// either doing a read or write, it would indicate we were trying
		// to register a /dev/* port; fall back to select
		switch (kev_copy.filter) {
		case EVFILT_READ:
			_dispatch_select_workaround = true;
			FD_SET((int)kev_copy.ident, &_dispatch_rfds);
			_dispatch_rfd_ptrs[kev_copy.ident] = kev_copy.udata;
			break;
		case EVFILT_WRITE:
			_dispatch_select_workaround = true;
			FD_SET((int)kev_copy.ident, &_dispatch_wfds);
			_dispatch_wfd_ptrs[kev_copy.ident] = kev_copy.udata;
			break;
		default:
			_dispatch_source_drain_kevent(&kev_copy); 
			break;
		}
		break;
	}
}

因为目前并没有用到其他事件,所以filter为READ。WRITE先不看;我们看:

int rval = kevent(_dispatch_get_kq(), &kev_copy, 1, &kev_copy, 1, NULL);

这段代码作用是注册事件,将Kev_copy注册到kq上去;先获取dq。

(b). 初始化dq
static int _dispatch_kq;
static int
_dispatch_get_kq(void)
{
	static dispatch_once_t pred;

	dispatch_once_f(&pred, NULL, _dispatch_get_kq_init);

	return _dispatch_kq;
}

_dispatch_kq是一个全局的Int型;

dispatch_once_f表示其只操作一次_dispatch_get_kq_init,它的作用就是创建并初始化kevent;
static void
_dispatch_get_kq_init(void *context __attribute__((unused)))
{
static const struct kevent kev = {
.ident = 1,
.filter = EVFILT_USER,
.flags = EV_ADD|EV_CLEAR,
};

_dispatch_kq = kqueue(); //创建
_dispatch_safe_fork = false;
// in case we fall back to select()
FD_SET(_dispatch_kq, &_dispatch_rfds); //设置要监听的fds

if (_dispatch_kq == -1) {
dispatch_assert_zero(errno);
}

(void)dispatch_assume_zero(kevent(_dispatch_kq, &kev, 1, NULL, 0,
NULL));

_dispatch_queue_push(_dispatch_mgr_q.do_targetq, &_dispatch_mgr_q);
}

分两部分:一部分是看看kqueue是怎么创建的,

然后看如何调度_dispatch_mgr_q;

(c). 构造dq
kqueue(void)
{
    struct kqueue *kq;
    int tmp;

    kq = calloc(1, sizeof(*kq));
    if (kq == NULL)
        return (-1);
    kq->kq_ref = 1;
    pthread_mutex_init(&kq->kq_mtx, NULL);

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, kq->kq_sockfd) < 0) 
        goto errout_unlocked;

    if (kqueue_sys_init(kq) < 0)
        goto errout_unlocked;

    pthread_rwlock_wrlock(&kqtree_mtx);
    if (kqueue_gc() < 0)
        goto errout;
    /* TODO: move outside of the lock if it is safe */
    if (filter_register_all(kq) < 0)
        goto errout;
    RB_INSERT(kqt, &kqtree, kq);
    pthread_rwlock_unlock(&kqtree_mtx);

    dbg_printf("created kqueue, fd=%d", kq->kq_sockfd[1]);
    return (kq->kq_sockfd[1]);

errout:
    pthread_rwlock_unlock(&kqtree_mtx);

errout_unlocked:
    if (kq->kq_sockfd[0] != kq->kq_sockfd[1]) {
        tmp = errno;
        (void)close(kq->kq_sockfd[0]);
        (void)close(kq->kq_sockfd[1]);
        errno = tmp;
    }
#if defined(__sun__)
    if (kq->kq_port > 0) 
	close(kq->kq_port);
#endif
    free(kq);
    return (-1);
}
创建一个socketPair,然后返回其中的一个fd,另一个fd自然是用于监听的,这里不深究了;

(d). 调度mgr_queue


_dispatch_queue_push(_dispatch_mgr_q.do_targetq, &_dispatch_mgr_q);


这段代码,也只会在此执行了,因为mgr_q与其他的queue不太一样;

可以知道的是do_targetq是root_queue,按照上一篇的流程,_dispatch_mgr_q将会作为一个任务扔到root_queue里,

然后创建新的线程,然后在新的线程中调度,跳过中间的细节,我们进入到新线程调度的函数里来:

static inline void
_dispatch_continuation_pop(dispatch_object_t dou)
{
	dispatch_continuation_t dc = dou._dc;
	dispatch_group_t dg;

	if (DISPATCH_OBJ_IS_VTABLE(dou._do)) {
		return _dispatch_queue_invoke(dou._dq);
	}

	// Add the item back to the cache before calling the function. This
	// allows the 'hot' continuation to be used for a quick callback.
	//
	// The ccache version is per-thread.
	// Therefore, the object has not been reused yet.
	// This generates better assembly.
	if ((long)dou._do->do_vtable & DISPATCH_OBJ_ASYNC_BIT) {
		_dispatch_continuation_free(dc);
	}
	if ((long)dou._do->do_vtable & DISPATCH_OBJ_GROUP_BIT) {
		dg = dc->dc_group;
	} else {
		dg = NULL;
	}
	dc->dc_func(dc->dc_ctxt);
	if (dg) {
		dispatch_group_leave(dg);
		_dispatch_release(dg);
	}
}
这里从root_queue去出来的dou就是mgr_q;
执行到:

if (DISPATCH_OBJ_IS_VTABLE(dou._do)) {
return _dispatch_queue_invoke(dou._dq);
}

发现DISPATCH_OBJ_IS_VTABLE(dou._do)为true;因此进入_dispatch_queue_invoke(dou._dq);

这里dou就是mgr_q;
DISPATCH_NOINLINE
void
_dispatch_queue_invoke(dispatch_queue_t dq)
{
	dispatch_queue_t tq = dq->do_targetq;

	if (!slowpath(DISPATCH_OBJECT_SUSPENDED(dq)) && fastpath(_dispatch_queue_trylock(dq))) {
		_dispatch_queue_drain(dq);
		if (tq == dq->do_targetq) {
			tq = dx_invoke(dq);
		} else {
			tq = dq->do_targetq;
		}
		// We do not need to check the result.
		// When the suspend-count lock is dropped, then the check will happen.
		dispatch_atomic_dec(&dq->dq_running);
		if (tq) {
			return _dispatch_queue_push(tq, dq);
		}
	}

	dq->do_next = DISPATCH_OBJECT_LISTLESS;
	if (dispatch_atomic_sub(&dq->do_suspend_cnt, DISPATCH_OBJECT_SUSPEND_LOCK) == 0) {
		if (dq->dq_running == 0) {
			_dispatch_wakeup(dq);	// verify that the queue is idle
		}
	}
	_dispatch_release(dq);	// added when the queue is put on the list
}
其他细节不详细述说了,结果会跳入到:

if (tq == dq->do_targetq) {
tq = dx_invoke(dq);
}


也就是进入到mgr_q的do_invoke中,

static const struct dispatch_queue_vtable_s _dispatch_queue_mgr_vtable = {
.do_type = DISPATCH_QUEUE_MGR_TYPE,
.do_kind = "mgr-queue",
.do_invoke = _dispatch_mgr_invoke,
.do_debug = dispatch_queue_debug,
.do_probe = _dispatch_mgr_wakeup,
};


通过vtable函数指针进入_dispatch_mgr_invoke,开始了计时队列的轮询之旅;

(4). 计时队列轮询

参见 变态的libDispatch源码分析-全局队列异步延时任务处理过程-计时轮询


(5).  执行_dispatch_source_set_timer2

_dispatch_source_set_timer2被封装成一个任务放到了mgr_queue中;因此当mgr_queue中的任务被调用时,就会执行_dispatch_source_set_timer2;

之前说了,mgr_queue的任务,通过kevent通知的;再回到(3).(a)中,Kevent小节:_dispatch_update_kq中执行了:

	int rval = kevent(_dispatch_get_kq(), &kev_copy, 1, &kev_copy, 1, NULL);
这段代码就是想kevent_queue注册了事件,而后在计时轮询中,作为监听方,会检测到这个事件:

参看变态的libDispatch源码分析-全局队列异步延时任务处理过程-计时轮询

(3). 监听kevent  
        k_cnt = kevent(_dispatch_kq, NULL, 0, kev, sizeof(kev) / sizeof(kev[0]), timeoutp);
        k_err = errno;

        switch (k_cnt) {
        case -1:
            if (k_err == EBADF) {
                DISPATCH_CLIENT_CRASH("Do not close random Unix descriptors");
            }
            (void)dispatch_assume_zero(k_err);
            continue;
        default:
            _dispatch_mgr_thread2(kev, (size_t)k_cnt);
            // fall through
        case 0:
            _dispatch_force_cache_cleanup();
            continue;
        }

default情况下,说明有事件来临,进入到_dispatch_mgr_thread2(kev, (size_t)k_cnt);

static void
_dispatch_mgr_thread2(struct kevent *kev, size_t cnt)
{
	size_t i;

	for (i = 0; i < cnt; i++) {
		// EVFILT_USER isn't used by sources
		if (kev[i].filter == EVFILT_USER) {
			// If _dispatch_mgr_thread2() ever is changed to return to the
			// caller, then this should become _dispatch_queue_drain()
			_dispatch_queue_serial_drain_till_empty(&_dispatch_mgr_q);
		} else {
			_dispatch_source_drain_kevent(&kev[i]);
		}
    }
}

还记得当时注册事件时的filter吧,就是EVFILT_USER;接下来的流程,直接给出调用堆栈:

#0  _dispatch_continuation_pop (dou=...) at libdispatch/src/queue.c:345
#1  0x408779c6 in _dispatch_queue_drain (dq=0x4087f518) at libdispatch/src/queue.c:1449
#2  0x40877d96 in _dispatch_queue_serial_drain_till_empty (dq=<value optimized out>) at libdispatch/src/queue.c:1303
#3  0x40878228 in _dispatch_mgr_thread2 (kev=<value optimized out>, cnt=1) at libdispatch/src/queue_kevent.c:77
#4  0x40878488 in _dispatch_mgr_invoke (dq=<value optimized out>) at libdispatch/src/queue_kevent.c:173
#5  0x40877ae8 in _dispatch_queue_invoke (dq=0x4087f518) at libdispatch/src/queue.c:1320
#6  0x40878104 in _dispatch_worker_thread2 (context=0x4087f2e8) at libdispatch/src/queue.c:1529

那么进入到了_dispatch_source_set_timer2

static void
_dispatch_source_set_timer2(void *context)
{
	struct dispatch_set_timer_params *params = context;
	dispatch_source_t ds = params->ds;
	ds->ds_ident_hack = params->ident;
	ds->ds_timer = params->values;
	_dispatch_timer_list_update(ds);
	dispatch_resume(ds);
	dispatch_release(ds);
	free(params);
}
ds->ds_timer = params->values; 将计时器设置为params的值;

然后_dispatch_timer_list_update(ds); 将ds插入到计时任务队列中;

void
_dispatch_timer_list_update(dispatch_source_t ds)
{
	dispatch_source_t dsi = NULL;
	int idx;
	
	dispatch_assert(_dispatch_queue_get_current() == &_dispatch_mgr_q);

	// do not reschedule timers unregistered with _dispatch_kevent_release()
	if (!ds->ds_dkev) {
		return;
	}

	// Ensure the source is on the global kevent lists before it is removed and
	// readded below.
	_dispatch_kevent_merge(ds);
	
	TAILQ_REMOVE(&ds->ds_dkev->dk_sources, ds, ds_list);

	// change the list if the clock type has changed
	if (ds->ds_timer.flags & DISPATCH_TIMER_WALL_CLOCK) {
		idx = DISPATCH_TIMER_INDEX_WALL;
	} else {
		idx = DISPATCH_TIMER_INDEX_MACH;
	}
	ds->ds_dkev = &_dispatch_kevent_timer[idx];

	if (ds->ds_timer.target) {
		TAILQ_FOREACH(dsi, &ds->ds_dkev->dk_sources, ds_list) {
			if (dsi->ds_timer.target == 0 || ds->ds_timer.target < dsi->ds_timer.target) {
				break;
			}
		}
	}
	
	if (dsi) {
		TAILQ_INSERT_BEFORE(dsi, ds, ds_list);
	} else {
		TAILQ_INSERT_TAIL(&ds->ds_dkev->dk_sources, ds, ds_list);
	}
}

这段代码就是操作一下链表而已,但是是通过时间的先后顺序插入的;

接下来再次进入ds的resume阶段;计时轮询队列就接管了接下来的计时,调度任务;

(6). 回到(3).(a)

注册kevent事件之后,返回:继续到_dispatch_wakeup中来


dispatch_queue_t
_dispatch_wakeup(dispatch_object_t dou)
{
	dispatch_queue_t tq;

	if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) {
		return NULL;
	}
	if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
		return NULL;
	}

	if (!_dispatch_trylock(dou._do)) {
		return NULL;
	}
	_dispatch_retain(dou._do);
	tq = dou._do->do_targetq;
	_dispatch_queue_push(tq, dou._do);
	return tq;	// libdispatch doesn't need this, but the Instrument DTrace probe does
}

我们刚才时从dx_probe进入的,return false,因此上面这句
	if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
		return NULL;
	}
return NULL返回了;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值