NS2中Trace部分的一点认识
NS2追踪部分的源码主要涉及以下几个文件:
ns-2.34/trace/basetrace[.cc][.h]
ns-2.34/trace/trace[.cc][.h]
ns-2.34/trace/cmu-trace[.cc][.h]
ns-2.34/tcl/lib/ns-lib.tcl
ns-2.34/tcl/lib/ns-mobilenode.tcl
仿真输出文件如何与 OTcl 解释层次关联
以标准的仿真脚本文件 ns-2.34/tcl/ex/simple.tcl 为例,在开头部分有如下定义:
set f [open out.tr w]
$ns trace-all $f
第一句的作用是创建并且以只写的方式打开了追踪文件out.tr,第二句则调用了trace-all 这个 tcl 函数将创建的文件的操作句柄传递 tcl 变量。其原型如下(ns/tcl/lib/ns-lib.tcl):
Simulator instproc trace-all file {
$self instvar traceAllFile_
set traceAllFile_ $file
}
由此可知,在 OTcl 类 Simulator 中增加一个成员 traceAllFile_,并将追踪文件的句柄赋值给它。通过这个操作,追踪文件便被传递到解释层次中,可供随时调用。traceAllFile_ 这个成员变量比较重要,后面还会用到。
编译层次C++下的trace函数如何实现追踪数据:
通过阅读 ns-2.34/trace/ 下的 .cc 和 .h 代码,由函数的调用关系大概可以猜测到当我们在追踪 packet 的时候,应该是系统在某个地方调用了类class Trace 的 recv 函数,而 recv 继而会调用 format 函数去整理输出信息。在 format 函数中,有效信息都被输入到 pt_->buffer() 中了,在这里可能会产生疑问,我们最终看到的结果都是在输出文件 out.tr 中,在这怎么都输出到 pt_ 中了?查一下 pt_ 发现是类 class Trace 在构造的时候创建的 pt_ = new BaseTrace; 原来这个 pt_ 是一个由类 class BaseTrace 产生的对象。
Trace 又是怎样和输出文件 out.tr 关联起来的呢?
前面已经提到 out.tr 和解释层次 OTcl 中的成员变量 traceAllFile_ 关联起来了,那我们可以猜测 traceAllFile_ 应该是和 BaseTrace 在某个地方被关联起来了。而最有可能就是在创建节点的时候。
脚本中调用 set n0 [$ns node] 创建节点的过程我就不细说了。在ns-2.34/tcl/lib/ns-lib.tcl 中我们找到
Simulator instproc create-wireless-node 函数,中发现创建节点的时候有这样的操作:
set tracefd [$self get-ns-traceall]
if {$tracefd != "" } {
$node nodetrace $tracefd
$node agenttrace $tracefd
}
set namtracefd [$self get-nam-traceall]
if {$namtracefd != "" } {
$node namattach $namtracefd
}
第一句中又调用了 get-ns-traceall 这个函数,看一下它的原型只可它返回的就是 traceAllFile_。思路开始渐渐清晰,$node nodetrace $tracefd 调用了 nodetrace 这个函数,并将 $tracefd 也就是 traceAllFile_ 作为参数传递过去。在 ns/tcl/lib/ns-mobilenode.tcl 中,定义如下:
Node/MobileNode instproc nodetrace { tracefd } {
#
# This Trace Target is used to log changes in direction
# and velocity for the mobile node.
#
set T [new Trace/Generic]
$T target [[Simulator instance] set nullAgent_]
$T attach $tracefd
$T set src_ [$self id]
$self log-target $T
}
到此我们豁然开朗,在这函数中,创建了一个新的 Trace/Generic 对象,并通过 $T attach $tracefd 将这个新的对象链接到 traceAllFile_ 上,也就是链接到了输出文件 out.tr 上。
最后一句 $self log-target $T,这里 $self 指的是 Node/MobileNode 我们查看编译层次中类 class MobileNode 的 command 函数(ns-2.34/common/mobilenode[.cc][.h])会发现 log-target 这个操作的原型:
else if(strcmp(argv[1], "log-target") == 0) {
log_target_ = (Trace*) TclObject::lookup(argv[2]);
if (log_target_ == 0)
return TCL_ERROR;
return TCL_OK;
}
log_target_ 是在类 class MobileNode 中定义的 Trace* log_target_;
现在我们终于明白了NS2追踪功能的链接过程,在编译层次中声明一个类 class Trace 的指针 log_target_ (通常叫这个名字),然后在解释层次中生成一个新的 Trace 对象,并将它们链接起来。
这部分遗留了一个问题:生成的新的 Trace/Generic 对象从其写法上看应该是从类 Trace 派生出来的一个子类。但是在 ns-2.34/trace/ 文件夹下却没找到它的声明,迷茫中。
链接的过程我们大概已经有了一点认识,贯穿起来想一下,追踪功能是怎么实现的?
无线仿真的脚本中,通常在 $ns_ node-config 的时候会传递一堆参数进去,其中就有追踪功能的开关,我们仅以 -macTrace ON 为例。逐步分析一下。
首先我们在ns/tcl/lib/ns-lib.tcl中找到 Simulator instproc node-config 这个函数,并且在其中找到如下的定义:
if [info exists macTrace_] {
Simulator set MacTrace_ $macTrace_
}
if [info exists routerTrace_] {
Simulator set RouterTrace_ $routerTrace_
}
if [info exists agentTrace_] {
Simulator set AgentTrace_ $agentTrace_
}
由此可以看出,传递进来的变量macTrace_被赋值给MacTrace_,那么也就是说MacTrace_ 就是那个开关。我们继续搜索 MacTrace_ 这个变量,发现它在 ns/tcl/lib/ns-mobilenode.tcl 中的 Node/MobileNode instproc add-interface 函数出现过。源码如下:
if { [Simulator set MacTrace_] == "ON" } {
#
# Trace RTS/CTS/ACK Packets
#
if {$imepflag != ""} {
set rcvT [$self mobility-trace Recv "MAC"]
} else {
set rcvT [cmu-trace Recv "MAC" $self]
}
$mac log-target $rcvT
if { $namfp != "" } {
$rcvT namattach $namfp
}
#
# Trace Sent Packets
#
if {$imepflag != ""} {
set sndT [$self mobility-trace Send "MAC"]
} else {
set sndT [cmu-trace Send "MAC" $self]
}
$sndT target [$mac down-target]
$mac down-target $sndT
if { $namfp != "" } {
$sndT namattach $namfp
}
#
# Trace Received Packets
#
if {$imepflag != ""} {
set rcvT [$self mobility-trace Recv "MAC"]
} else {
set rcvT [cmu-trace Recv "MAC" $self]
}
$rcvT target [$mac up-target]
$mac up-target $rcvT
if { $namfp != "" } {
$rcvT namattach $namfp
}
#
# Trace Dropped Packets
#
if {$imepflag != ""} {
set drpT [$self mobility-trace Drop "MAC"]
} else {
set drpT [cmu-trace Drop "MAC" $self]
}
$mac drop-target $drpT
if { $namfp != "" } {
$drpT namattach $namfp
}
} else {
$mac log-target [$ns set nullAgent_]
$mac drop-target [$ns set nullAgent_]
}
越来越接近问题所在,我们取出最开始的一段代码分析,
if {$imepflag != ""} {
set rcvT [$self mobility-trace Recv "MAC"]
} else {
set rcvT [cmu-trace Recv "MAC" $self]
}
$mac log-target $rcvT
先不管 imepflag 是干嘛的(实际上在 Simulator instproc create-wireless-node 的开头已经被屏蔽),
注意 set rcvT [$self mobility-trace Recv "MAC"] 函数,在ns/tcl/lib/ns-mobilenode.tcl 可以找到 Node/MobileNode instproc mobility-trace 的定义:
Node/MobileNode instproc mobility-trace { ttype atype } {
set ns [Simulator instance]
set tracefd [$ns get-ns-traceall]
if { $tracefd == "" } {
puts "Warning: You have not defined you tracefile yet!"
puts "Please use trace-all command to define it."
return ""
}
set T [new CMUTrace/$ttype $atype]
$T newtrace [Simulator set WirelessNewTrace_]
$T tagged [Simulator set TaggedTrace_]
$T target [$ns nullagent]
$T attach $tracefd
$T set src_ [$self id]
$T node $self
return $T
}
这也是在解释层次创建一个新的 Trace 对象,并完成到 out.tr 的链接。最后一句 $mac log-target $rcvT 就是调用 command 将编译层次声明的指针与解释层次生成的对象连接。
链接完成如何使用?在类 class Mac 中有这样的定义:
private:
void mac_log(Packet *p) {
logtarget_->recv(p, (Handler*) 0);
}
NsObject* logtarget_;
注意这里并没有将 logtarget_ 声明为 Trace*,但是查看追踪的基类 class Trace 你会发现 Trace 就是从 NsObject 继承来的,因此 logtarget_就是在 command 中被链接的指针。
而函数void mac_log(Packet *p){
logtarget_->recv(p, (Handler*)0);
}
就是调用 Trace 的 recv,继而调用 format 记录有效信息。
剩下的工作我们通篇搜索函数mac_log的出现,容易发现有3个地方,在ns-2.34/mac/mac-802_11.cc中,参考其应用即可。
最后有个细节需要注意,基类中的声明:
private:
void mac_log(Packet *p) {
logtarget_->recv(p, (Handler*) 0);
}
NsObject* logtarget_;
是 private,如果你要实现自己的追踪,必须在你新建的类中重新声明这个变量和函数,并且在 command 中实现链接。