BIND9源码分析之定时器timer

 

        BIND中有一些操作是定时任务,server.c的run_server函数中创建了三个定时任务,分别执行interface_timer_tick、heartbeat_timer_tick和pps_timer_tick;其他模块中还有很多时间任务。

我们知道linux中的定时器可以用条件变量实现(用其中的pthread_cond_timedwait函数),这里有一个简单实现。BIND中的定时器也是用条件变量实现的,只不过稍微复杂一点。

定时器的实现文件时timer.h和timer.c,位于lib/isc目录下。

一、定时器管理器isc_timermgr  ns_g_timermgr

所有的定时器timer由一个全局变量ns_g_timermgr管理,ns_g_timermgr的数据结构如下:

struct isc__timermgr {
	/* Not locked. */
	isc_timermgr_t			common;
	isc_mem_t *			mctx;
	isc_mutex_t			lock;  
	/* Locked by manager lock. */
	isc_boolean_t			done;
	LIST(isc__timer_t)		timers;
	unsigned int			nscheduled;
	isc_time_t			due;
#ifdef USE_TIMER_THREAD
	isc_condition_t			wakeup;
	isc_thread_t			thread;
#endif	/* USE_TIMER_THREAD */
#ifdef USE_SHARED_MANAGER
	unsigned int			refs;
#endif /* USE_SHARED_MANAGER */
	isc_heap_t *			heap;
};

其中common是timermgr的functions;

mtx是timermgr的内存分配器,它负责timermgr的内存分配和回收;

lock是timermgr的互斥锁,用来在多线程中保护done,timers,nscheduled等数据;

timers是timermgr管理timer,BIND中分三类timer,分别是定时执行的ticker、只执行一次的once、限制执行次数的limited和非活动的inactive,刚创建的timer都是inactive类型的,之后用isc_timer_reset将其变为前三种;

due是该事件管理器中所有时间的最小的到期时间(绝对时间);

wakeup是用来实现定时任务的条件变量;

thread是执行定时任务调度的线程;

refs是该timermgr的引用计数

heap是一些供调度的timer按照过期时间组成的一个小根堆。

 

定时器管理器ns_g_timermgr只在server.c的create_managers中创建一次,创建时调用isc_timermgr_create函数。

isc_timermgr_create:

    初始化各变量;

     启用线程thread执行函数run。run线程是真正执行时间调度的线程,它获取当前的绝对时间now,然后调用dispatch(manager, now)做时间调度,之后如果没有要调度的时间了就用pthread_cond_wait等待直到有新的时间到达;如果还有要调度的时间,则调用pthread_cond_timedwait等待最近的那个timer的到期时间(保存在manager->due中)。

    dispatch(manage, now)是真正做timer调度的函数,它从manager的堆heap中取出堆顶的timer,然后判断当前时间now是否大于了timer的过期时间due,如果不大于,则更新manager->due;如果now超过了timer的过期时间,则根据timer的类型做各种判断,判断是否需要重新调度,判断是否需要为此分发事件…..如果需要为此timer分发事件,则调用isc_event_allocate新建一个事件,然后调用isc_task_send将此event分发到任务系统中,此时任务系统中监听的线程就会执行为此timer绑定的函数。

之后将此timer从manager->heap中取出,将它管理的timer数量减一,如果需要重新调度,则调用schedule重新调度此timer。

schedule:

    它的主要任务是将一个timer加入到调度堆中。中间需要做一些时间的修正,如果需要唤醒thread线程,则调用pthread_cond_signal唤醒thread线程。

    与schedule相对应的是deschedule,它将一个线程从manager->heap中删除,并唤醒thread线程

二、时间类timer

时间对象timer是timer manager管理的单位,BIND中分三类timer,分别是定时执行的ticker、只执行一次的once、限制执行次数的limited和非活动的inactive,刚创建的timer一般都是inactive类型的,之后用isc_timer_reset将其变为前三种中的一种。

下面看看timer的数据结构:

struct isc__timer {
	/*! Not locked. */
	isc_timer_t			common;
	isc__timermgr_t *		manager;
	isc_mutex_t			lock;
	/*! Locked by timer lock. */
	unsigned int			references;
	isc_time_t			idle;
	/*! Locked by manager lock. */
	isc_timertype_t			type;
	isc_time_t			expires;
	isc_interval_t			interval;
	isc_task_t *			task;
	isc_taskaction_t		action;
	void *				arg;
	unsigned int			index;
	isc_time_t			due;
	LINK(isc__timer_t)		link;
};

其中common是timer的functions;references是timer的引用计数;idle 空闲时间,它是once类型timer独有的;type是timer的类型;expires 过期时间,它是once类型timer独有的;interval 是时间间隔,比如tick类型的时间需要隔多久执行一次;task是该timer要执行的任务,action是任务要执行的函数,arg是其参数;index是timer在manager的小根堆heap中的索引;due是该timer的到期时间;link指向它在manager的timer链表中的下一个timer。

timer最主要的操作是isc_timer_create和isc_timer_reset。它们分别创建和设置时间对象。

isc_timer_create: 主要任务是为timer对象赋值,并根据不同的timer类型做不同的操作,后面有流程图。

isc_timer_reset:改变timer的一些属性。

三、timer的生命周期

这节看看BIND9中有代表性的几个timer的生命周期

ticker类型

ticker类型的timer每隔interval被分发到事件驱动中

server中有三个ticker类型的timer,他们分别是interface_timer, heartbeat_timer和pps_timer。 拿interface_timer做分析:

1,首先在run_server中被初始化:

isc_timer_create(ns_g_timermgr, isc_timertype_inactive,
NULL, NULL, server->task, interface_timer_tick,server, &server->interface_timer)

在isc_timer_create中,为它初始化各成员后,只是简单地将它加入到timer manager的timers队列中。

2,之后在load_configuration中被reset为ticker类型

isc_timer_reset(server->interface_timer, isc_timertype_ticker,NULL, &interval, ISC_FALSE)

在isc_timter_reset中,为它的interval成员赋值&interval

3,然后调用schedule将其加入调度队列:

schedule(timer, &now, ISC_TRUE)

在schedule中,为其分配到期时间due=now+interval;如果它未被调度过,就调用isc_heap_insert(manager->heap, timer)

将它插入到timer manager的调度堆heap中,如果已经在调度堆中存在,则看它上次的过期时间是否到(timer->due>due?)如果到了,则将它从堆中floatup,否则sinkdown;最后根据情况唤醒timer manager中执行run的thread线程

4,在thead线程中,线程首先检查它的到期时间timer->due是否到(timer->due>now?),如果到了,则为它分配新的事件

event = (isc_timerevent_t *)isc_event_allocate(manager->mctx, timer,type,timer->action,timer->arg,sizeof(*event));

然后调用

isc_task_send(timer->task,ISC_EVENT_PTR(&event))

将它加入到事件驱动器中。

把它从heap中取出,然后重新调用schedule(timer, now, ISC_FALSE);将它加入到下一次调度中。

once类型

once类型的timer在它的空闲时间idle超过interval的时候,或者现在的时间now超过了expires时间的时候,被分发到事件驱动中。

client中有一个执行client_timeout的timer,拿它做分析:

1,首先创建它

isc_timer_create(manager->timermgr, isc_timertype_inactive, NULL, NULL, client->task, client_timeout,client, &client->timer);

2,然后reset它

isc_timer_reset(client->timer, isc_timertype_once, NULL, &interval, ISC_FALSE);

3,在isc_timer_reset中,调用isc_time_add(&now, interval, &timer->idle);将它的等待时间idle置为now+interval;然后调用schedule(timer, &now, ISC_TRUE)将它加入调度队列

4,在schedule中,将它的due设置为0,然后把它加入到timer manager的heap中,然后根据情况唤醒thread线程

5,在thread线程中,为它分配新事件并加入到事件驱动中,之后并不对它做下一次调度。

posted @ 2013-06-11 12:10  CobbLiu  阅读(2696)  评论(1编辑  收藏  举报