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

  • 相关阅读:
    二分查找,你真的学会了吗
    解决Devexpress的RichEditControl控件保存为docx文件后在word里打开字体显示不正确的问题
    protocgengo: unable to determine Go import path for "xxx.proto"
    URL 和 URI 区别
    Ubuntu 强制关闭图形窗口
    centos /lib64/libc.so.6: version `GLIBC_2.28' not found (required by
    Mac OS 修改终端 Terminal 的配色
    Go语言 http 中的 request.Host 和 request.URL.Host 的区别
    Go 中 defer 和 return 执行的先后顺序
    NPOI 将DataSet保存成Excel文件
  • 原文地址:https://www.cnblogs.com/cobbliu/p/3131682.html
Copyright © 2020-2023  润新知