你必须非常努力,才能看起来毫不费力!
分类: 系统运维
2009-06-09 19:42:18
学习NS2重要要把握几个重要的类。现在简单分析下。
1.Tcl类
这个类封装是OTcl解释器的真正实例,可以当成解释器理解。 其中定义了解释器访问及通信的方法。这个类是在~tclcl/tclcl.h和~tclcl/tcl.cc中定义的,提供了以下的操作方法:
1.1 获得 Tcl 实例的一个指针
在类定义有 static Tcl instance_;获取的方法是通过一个静态的内联函数static inline Tcl& instance() { return (instance_); }实现的。
1.2 通过解释器调用 OTcl 过程
通过解释器实例tcl来调用OTcl命令,他们在调用参数方面有本质的区别。每个函数都传递一个字串(string)给解
释器,然后解释器通过一个全局文本来识别这个字符串。如果解释器返回TCL_OK,则这些函数将会返一个相应的OTcl
过程。反过来,如果解释器返回TCL_ERROR,则这些函数将调用tkerror{}。用户可以重载(overload)个过程,以便有选
择地忽略某些类型的错误。
1.3 取出或将结果返回解释器
//tcl的解释器
Tcl_Interp* tcl_; Tcl_Interp是在~include/tcl.h中的定义的,
typedef struct Tcl_Interp {
char *result; /*指向命令的返回值*/
void (*freeProc) _ANSI_ARGS_((char *blockPtr));
int errorLine; /* 当返回TCL_ERROR时,该值指示出出现错误的行数(从1开始) */
} Tcl_Interp;
结果就保存在tcl_->result中。
取出结果:tcl.result(void)必须用于取回结果。注意这里的结果是一个字符串,它必须被转化成一个适合结果类型的内部格式。
结果传给解释器,即设置tcl_->result的值,a)tcl.result(const char* s),b)tcl.resultf(const char* fmt, . . . )
1.4 报告错误状态并以统一的方法退出
编译代码中提供了一种统一的报告错误的方法。
tcl.error(const char* s)执行以下功能: 将s写入stdout;将tcl_->result写入stdout;退出,并将错误代码(error code)置1。
tcl.resultf("cmd = %s", cmd);
tcl.error("invalid command specified");
1.5 存储并查找“TclObjects“
ns将每个TclObject在编译层次的一个指针(reference)存在一个hash表中;这样就能快速地访问该对象了。该hash
表在解释器的内部。Ns用户以TclObject的名字为关键字(key)在hash表中进行插入、查找或者删除TclObject的操作。
tcl.enter(TclObject* o)将在hash表插入一个指向TclObject o的指针(pointer)。它被TclClass::create_shadow()
用来在对象建立时,将其插入表中。
tcl.lookup(char* s)将取回名为s的TclObject。可以这样被使用:TclObject::lookup()。
tcl.remove(TclObject* o)将删除hash表中TclObject o的指针。可以用TclClass::delete_shadow()来移出hash表中
存在的入口,此时该对象已经被删除。
2.TclObject类
TclObject类是解释和编译层次大多数其它类的基类(base class)。TclObject类中的每个对象都由用户从解释器中创
建。编译层次中同时有一个与之对应的影子对象(shadow object)被创建。这两个对象相互紧密联系。下一小节描述的
TclClass类,包含了执行这种投射(shadowing)的机制。
TclObject类包含了早期的NsObject类的函数。因此,它储存了变量绑定(bindings)接口,这些变量绑定绑定了解释对象中的实例变量 (instance variables)和相应的编译对象中的C++成员变量(member variables)。这种绑定比ns版本1要强,因为OTcl变量的任何变化都是被跟踪(trapped)的,而且每次当前的C++和OTcl的值被解释器访问后都要保持一致。这种一致性是由InstVar类来完成的。同样,和ns版本1不同的是,tclObject类的对象不再存储在一个全局链表 (link list)中。而是存储在Tcl类的一个hash表中。
2.1 创建(creating)和撤销(Destroying)TclObjects
当用户创建一个新的TclObject时,通常调用new{}和delete{}过程 (procedures) 这些过程定义在~tclcl/tcl-object.tcl中。 它们可以用于创建和撤销所有类的对象。
创建TclObjects 用户可以调用new{}来创建一个解释类的TclObject。这时解释器将执行这个对象的构造函数
(constructor)init{},同时给它传递用户提供的任何参数。ns自动创建相应的编译对象。 shadow对象是通过基类TclObject
的构造函数被创建的。因此,新的TclObject的构造函数必须首先调用父类的构造函数。new{}方法返回一个对象的handle,
用户可以通过这个handle对对象进行进一步的操作。
如下面是Agent/SRM/Adaptive的构造函数:
Agent/SRM/Adaptive instproc init args {
eval $self next $args
$self array set closest_ "requestor 0 repairor 0"
$self set eps_ [$class set eps_]
}
创建一个gent/SRM/Adaptive对象时执行的步骤:
2.1.1 从TclObject的名字空间(name space)获取一个惟一的新的对象的handle。这个handle将返回给用户。ns中大多数handle都是以_o
在TclObject类中定义:
#if 0
/* allocate in-line rather than with new to avoid pointer and malloc overhead. */
#define TCLCL_NAME_LEN 12
char name_[TCLCL_NAME_LEN];
#else /* ! 0 */
char *name_;
#endif /* 0 */
inline const char* name() { return (name_); }
void name(const char*);
2.2.2 执行新对象的构造函数。任何的特定的用户输入参数都将作为构造函数的参数传入。这个构造函数必调用其父类的构造函数。
在上面的例子里,Agent/SRM/Adaptive在第一行就调用了其父类。
需要注意的是,每个构造函数,依次调用其父类的构造函数。 那么ns中的最后一个构造函数就是TclObject的构造函数。
这个构造函数用来创建对应的shadow对象,并执行其它的初始化工作与绑定。因此最好在构造函数先调用父类的构造函数,然后再初始化。这样可以使shadow对象先被建立,从而有变量可以绑定。
2.2.3 TclObject的构造函数为Agent/SRM/Adaptive类调用create-shadow{}实例过程。
2.2.4 当shadow对象建立以后,ns为编译对象调用所有的构造函数,它们每个都有可能为类中的对象建立相应的变量绑定,同时执行其它的必要的初始化工作。因此我们最好把调用父类的构造函数的语句放在类初始化语句之前。
2.2.5 在shadow对象成功创建后。这样就通过create_shadow(void)将创建的TclObject对象添加到tcl类的hash表中,如上文。使cmd{}成为一个新创建的解释对象的实例化过程。这个实例化过程会调用编译对象中的command()方法。在接下来的一节里,我们将介绍 command方法是如何定义并被调用的。
2.2.5 注意映射机制(shadowing mechanisms)只有当用户通过解释器创建新的TclObject的时候才有效。但如果程序员只是单向创建编译TclObject,这个机制将会失效。因此,程序员不要直接用C++的新方法来创建编译对象。
TclObject的撤销 delete操作将同时撤销解释对象和相当的shadow对象,
delete过程来移除默认的链表计划(list scheduler),同时在原处实例化一个替代的计划。
Simulator instproc use-scheduler type {
$self instvar scheduler_
delete scheduler_ # 首先删除已有的list scheduler
set scheduler_ [new Scheduler/$type]
}
同构造函数一样,对象的析构函数必须明确地调用其父类的析构函数,作为该析构函数的声明的最后部分。TclObject的析构数将调用delete-shadow实例过程,从而依次调用对应的编译方法来撤销shadow对象。解释对象将由解释器自身撤销。
2.3 变量绑定(Variable Bindings)
大多数情况下,访问编译成员变量只能通过编译代码,同样地,访问解释成员变量只能通过解释代码;但是,在它们之建立一个双向绑定是有可能的,这就使得解释成员变量和编译成员变量都可以访问同样的数据,而且它们中的任何一个变值的变化均可以使另一个相应地变为同样的值。
这种绑定是对象在实例化的时候,由编译对象的构造函数建立起来的;它也作为一个实例变量被解释对象自动访问。ns持五种不同的数据类型:实型 (reals) 带宽变量 , (bandwidth valued variables) 时间变量,(time valued variables),整型(integers),布尔型(booleans)。
如下例:ASRMAgent类(这是个编译类) 的构造函数:
SRMAgent::ASRMAgent() {
bind("pdistance_", &pdistance_);/* 实变量 */
bind("requestor_", &requestor_);/* 整型变量 */
bind_time("lastSent_", &lastSessSent_);/* 时间型变量 */
bind_bw("ctrlLimit_", &ctrlBWLimit_);/* 带宽型变量 */
bind_bool("running_", &running_);/* 布尔型变量 */
}
上述所有的函数都需要两个参数:OTcl变量的名字和绑定的相应的编译成员变量的地址。通常来说,这
些绑定是在由构造函数建立的,但是也可以由其它方式完成。如可以通过InstVar类来完成。
每个被绑定的变量在对象创建的初始化时候,自动地被赋予默认值。这些默认值被指定为解释类变量。这个初始化过程是在init-instvar{}中被执行的,而这又是被Instvar类中的方法调用的。init-instvar{}检查(check)解释对象的类和该对象所有的父类,从而找到定义变量的第一个类。它利用那个类中的变量值来初始化这个对象。大部分绑定的初始化值定义在~ns/tcl/lib/ns-default.tcl中。
需要注意的是,实际的绑定过程是在InstVar类的对象实例化过程中完成的。InstVar类中的每个对象绑定一个编译成员变量和一个解释成员变量。一个TclObject存储一个InstVar对象及相应的成员变量的链表。这个链表头被存储在TclObject的成员变量instvar_里。
2.4 变量跟踪(Variable Tracing)
除变量绑定以外,TclObject同时也支持对C++和Tcl实例变量的跟踪。一个被跟踪的变量既可以在C++又可以在Tcl中建和配置。如果在Tcl 层建立变量跟踪,变量必须在Tcl下是可视的,这也就意味着它必须是一个绑定的C++/Tcl,或者是一个纯Tcl实例变量。
如果一个TclObject去跟踪变量,它必须扩展C++中的trace方法,那本来是定义在TclObject中的一个虚函数。Trace类只实现一个简单的trace方法,因此,它可以作为一个一般的变量跟踪函数。
下面是一个在Tcl中设置跟踪变量的简单的例子:
# \$tcp跟踪它自己的变量cwnd_
\$tcp trace cwnd_
# \$tcp中的变量ssthresh_ 被一个一般的\$tracer跟踪
set tracer [new Trace/Var]
\$tcp trace ssthresh_ \$tracer
2.5 command方法: 定义与调用(Invocation)
对于每个建立的TclObject,ns都将建立cmd{}实例过程,作为一个挂钩(hook)通过编译的shadow对象来执行一些方法。cmd{}过程自动调用shadow对象的command()方法,并将command()方法的参数以矢量的形式传递给cmd{}过程。用户可以通过下面两种方式中的一种来调用cmd{}:通过显式地调用过程,指定所需的操作作为第一个参数,或者隐式地调用,就好象有一个同名的实例过程作为所需的操作。模拟脚本大多都采用后者。
3 TclClass类
这个编译类(class TclClass)是一个纯虚类。从这个基类派生的类提供两种功能:构建与编译类层次镜像的解释类层次;提供实例化新TclObjects的方法。每一个这样的派生类与编译类层次中的特定的编译类相互关联,同时可以实例化这个关联类的新对象。
static class RenoTcpClass: public TclClass {
public:
RenoTcpClass() : TclClass("Agent/TCP/Reno") {}
TclObject* create(int argc, const char*const* argv) {
return (new RenoTcpAgent());
}
} class_reno;
当ns初次启动时,执行对象的构造函数。
这个构造函数以解释类名为其参数,调用TclClass的构造函数。
TclClass的构造函数存储这个类的名字,并把这个对象插入到TclClass对象的链表中。
在模拟器的初始化过程中,Tcl_AppInit(void)调用TclClass::bind(void)。
对TclClass对象链表中的每一个对象,bind()调用register{},以解释器名作为其参数。
register{}建立层次类,建立那些必需,却还创建的类。
最后,bind()为新类定义create-shadow和delete-shadow两个实例过程。
4 TclCommand类
该类(TclCommand类)仅仅为ns提供向解释器输出简单命令的机制,然后由解释器在全局范围内执行。
5 EmbeddedTcl类
ns允许对编译代码或者解释代码的功能扩展,这个扩展代码将在初始化的时候被执行。
6 InstVar类
该类定义了将编译的shadow对象中的C++成员变量绑定到与之对应的解释对象中的Tcl实例变量上的方法与机制。这种绑定的建立可以使变量的值在任何时候都能从解释器或编译代码中设置和访问。