NS2的离散事件驱动原理(Scheduler, Handler, Event, Timer)

NS2是离散事件驱动的仿真机制,这一点文献到处都在讲但却始终没有讲到点子上。本文试图从几个NS2的基本类出发探究一下离散事件驱动究竟是怎么回事。
      首先是Scheduler,Handler和Event类的关系。
      在NS2中,事件(Event)是基本的调度单元,比如发送一个Packet、接收一个Packet等等。每个Event都有自己的处理工具,这个工具就是一个Handler类的对象handler_。Handler中仅包含一个函数,描述对Event的处理方法,即handle(Event *e)。
      给定一个事件,Scheduler将调用schedule(Handler* h, Event* e, double delay)函数,该函数设定Event的uid_,并设定用于处理该事件的Handler:e->handler_ = h,然后将该事件插入Scheduler维护的事件队列中。一般说来,处理事件的Handler都是产生该事件的实体本身,所以我们常常可以看到schedule函数在调用的时候*h用到this指针。
      NS2运行仿真时,Scheduler::run()函数不停地运行来处理队列中的事件,队列中的事件逐个dequeue()出来,随后运行Scheduler::dispatch(Event* p, double time)函数,将一个事件从队列中弹出来,调用它对应的Handler的handle()函数处理该它。
      这样就完成了一个事件从产生到排队到派出被处理的过程。
      接下来看一下TimerHandler的作用。
      计时器(Timer)是NS2仿真的关键手段,它用来设置一个未来的事件,在它到时后事件将被dispatch出来进行处理。Timer都是TimerHandler基类的派生类。TimerHandler与Scheduler交互的函数是sched(double delay),它调用Scheduler::schedule(Handler* h, Event* e, double delay)插入一个delay时间后的事件进入队列,Handler设置为TimerHandler本身(用this)。
      delay时间到了后,Scheduler会从事件队列中dequeue出该事件,调用其处理函数,也就是TimerHandler::handle() 。而handle()除了做一些计时器的状态设定工作外,核心的处理由虚函数expire(Event* e)来做。由C++的动态特性不难理解,expire()将在各种自定义的Timer(也就是TimerHandler的派生类)中进行重写,实现各Timer的不同处理方法。
      看一个具体的例子
      以NIST Wimax模块中的DlTimer为例,该Timer用于触发一个下行帧的生成:
class DlTimer : public TimerHandler {
public:
  DlTimer(Mac802_16 *m) : TimerHandler() {m_=m;}
  void    expire(Event *e);
private:
  Mac802_16 *m_;
}; 
在使用该Timer的MAC实体中,Mac802_16BS::init()函数中打开了该Timer:
double stime = getFrameDuration () + Random::uniform(0, getFrameDuration ());
dl_timer_->sched (stime);
这个计时器被定时在stime后触发事件。那么Scheduler在到时后会做些什么呢?由上面知道,将会由schedule()函数会调用该事件的Handler处理该事件。这个事件的Handler是什么呢?查看sched()的代码可以溯源到一个内联函数:
inline void _sched(double delay) {
     (void)Scheduler::instance().schedule(this, &event_, delay);
}
该函数设定event_的Handler为this,也就是调用schedule()的对象,而这个对象回溯回去正是dl_timer_!于是我们知道,该定时器到时后将会调用dl_timer_的handle()函数做事情,而DlTimer又是直接继承了TimerHandler的handle()函数,而TimerHandler::handle()函数是靠虚函数expire(Event* e)做事的。在DlTimer继承TimerHandler时正好重写了expire函数:
void DlTimer::expire (Event *e)
{
  m_->start_dlsubframe();
}
现在事情就变得明朗了,dl_timer_到时后Scheduler触发了一个事件,这个事件引发的是MAC802_16类的start_dlsubframe()动作,这个函数正是“开始下行子帧”。这样就完成了用一个Timer完成一个调度工作的全过程。

上一篇:kubernetes集群内调度与负载均衡


下一篇:将DHTMLX Scheduler与Vue.js框架一起使用指南