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线程中,为它分配新事件并加入到事件驱动中,之后并不对它做下一次调度。