add realtime simulator and associated tests

This commit is contained in:
Craig Dowell
2008-08-26 15:34:57 -07:00
parent dab7ef6871
commit 3ba8dce954
13 changed files with 2405 additions and 5 deletions

View File

@@ -0,0 +1,125 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Network topology
//
// n0 n1 n2 n3
// | | | |
// =================
// LAN
//
// - UDP flows from n0 to n1 and back
// - DropTail queues
// - Tracing of queues and packet receptions to file "udp-echo.tr"
#include <fstream>
#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/helper-module.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE ("RealtimeUdpEchoExample");
int
main (int argc, char *argv[])
{
//
// Make the random number generators generate reproducible results.
//
RandomVariable::UseGlobalSeed (1, 1, 2, 3, 5, 8);
//
// Allow the user to override any of the defaults and the above Bind() at
// run-time, via command-line arguments
//
CommandLine cmd;
cmd.Parse (argc, argv);
//
// But since this is a realtime script, don't allow the user to mess with
// that.
//
GlobalValue::Bind ("SimulatorImplementationType",
StringValue ("ns3::RealtimeSimulatorImpl"));
//
// Explicitly create the nodes required by the topology (shown above).
//
NS_LOG_INFO ("Create nodes.");
NodeContainer n;
n.Create (4);
InternetStackHelper internet;
internet.Install (n);
//
// Explicitly create the channels required by the topology (shown above).
//
NS_LOG_INFO ("Create channels.");
CsmaHelper csma;
csma.SetChannelAttribute ("DataRate", DataRateValue (DataRate(5000000)));
csma.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (2)));
csma.SetDeviceAttribute ("MTU", UintegerValue (1400));
NetDeviceContainer d = csma.Install (n);
//
// We've got the "hardware" in place. Now we need to add IP addresses.
//
NS_LOG_INFO ("Assign IP Addresses.");
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer i = ipv4.Assign (d);
NS_LOG_INFO ("Create Applications.");
//
// Create a UdpEchoServer application on node one.
//
uint16_t port = 9; // well-known echo port number
UdpEchoServerHelper server (port);
ApplicationContainer apps = server.Install (n.Get(1));
apps.Start (Seconds (1.0));
apps.Stop (Seconds (10.0));
//
// Create a UdpEchoClient application to send UDP datagrams from node zero to
// node one.
//
uint32_t packetSize = 1024;
uint32_t maxPacketCount = 500;
Time interPacketInterval = Seconds (0.01);
UdpEchoClientHelper client (i.GetAddress (1), port);
client.SetAttribute ("MaxPackets", UintegerValue (maxPacketCount));
client.SetAttribute ("Interval", TimeValue (interPacketInterval));
client.SetAttribute ("PacketSize", UintegerValue (packetSize));
apps = client.Install (n.Get (0));
apps.Start (Seconds (2.0));
apps.Stop (Seconds (10.0));
std::ofstream ascii;
ascii.open ("realtime-udp-echo.tr");
CsmaHelper::EnablePcapAll ("realtime-udp-echo");
CsmaHelper::EnableAsciiAll (ascii);
//
// Now, do the actual simulation.
//
NS_LOG_INFO ("Run Simulation.");
Simulator::Run ();
Simulator::Destroy ();
NS_LOG_INFO ("Done.");
}

View File

@@ -32,6 +32,10 @@ def build(bld):
['csma', 'internet-stack'])
obj.source = 'udp-echo.cc'
obj = bld.create_ns3_program('realtime-udp-echo',
['csma', 'internet-stack'])
obj.source = 'realtime-udp-echo.cc'
obj = bld.create_ns3_program('csma-broadcast',
['csma', 'internet-stack'])
obj.source = 'csma-broadcast.cc'

View File

@@ -0,0 +1,12 @@
#! /usr/bin/env python
"""Generic trace-comparison-type regression test."""
import os
import shutil
import tracediff
def run(verbose, generate, refDirName):
"""Execute a test."""
return tracediff.run_test(verbose, generate, refDirName, "realtime-udp-echo")

View File

@@ -48,6 +48,10 @@ DefaultSimulatorImpl::GetTypeId (void)
DefaultSimulatorImpl::DefaultSimulatorImpl ()
{
// No multithreaded stuff here, make sure EventImpl instances don't try and
// use any stale locking functions.
EventImpl::SetNoEventLock ();
m_stop = false;
m_stopAt = 0;
// uids are allocated from 4.

View File

@@ -23,6 +23,7 @@
namespace ns3 {
EventLock *EventImpl::m_eventLock = 0;
EventImpl::~EventImpl ()
{}
@@ -31,6 +32,19 @@ EventImpl::EventImpl ()
: m_cancel (false),
m_count (1)
{}
void
EventImpl::SetEventLock (EventLock *eventLock)
{
m_eventLock = eventLock;
}
void
EventImpl::SetNoEventLock (void)
{
m_eventLock = 0;
}
void
EventImpl::Invoke (void)
{
@@ -39,6 +53,7 @@ EventImpl::Invoke (void)
Notify ();
}
}
void
EventImpl::Cancel (void)
{

View File

@@ -21,9 +21,67 @@
#define EVENT_IMPL_H
#include <stdint.h>
#include "ns3/system-mutex.h"
namespace ns3 {
/**
* \ingroup simulator
* \brief Base class for locking functionality for events.
*
* This class provides a cheap way (from the perspective of the event) to lock
* an event for multithreaded access. This is a bit of a hack, but it lets us
* use a whole lot of existing mechanism in the multithreaded simulator case.
*
* Here's a taste of the problem. It makes life extraordinarily easier in the
* case of interfacing real network devices to be able to have threads reading
* from real sockets and doing ScheduleNow calls to inject packets into ns-3.
* It is desirable to have Schedule* calls all work similarly as well. That is
* you don't want to have to do different calls when working from an "external"
* thread than you do when working in the context of the main simulation thread.
*
* It turns out that basically all of the Schedule* calls return an EventId.
* Each EventId holds a reference to the underlying event. Clients (see the
* Applications for examples) often schedule events and hold onto the EventId
* in case they need to cancel the event later. The EventImpl that underlies
* all of this is reference counted and sharing an unprotected reference
* counted object between threads is a bad thing.
*
* There were several possible solutions:
*
* - Put a mutex into each event (costs 40 bytes for the mutex and a minumum of
* three system calls to use an event);
* - Work on the inheritance diagram of EventImpl to make a
* MultithreadedEventImpl and pull apart all of the MakeEvent functions to
* teach them to make the right kind of event based on a flag returned by the
* simulator;
* - Rework the entire event mechanism to use raw pointers and avoid the entire
* reference counting approach with its associated problems;
* - Provide a cheap way to use a shared mutex (and avoid using the mutex in
* cases where it is not required).
*
* The original prototype chose the first option since it was easy. I am very
* hesitant to rework the entire event mechanism or even pull apart the MakeEvent
* code at this point. We went with the last option even though it feels a bit
* like a hack.
*
* The result is the EventLock class. If you have a simulator implementation that
* is going to need multithreaded access to events, you need to inherit from
* EventLock and provide a object that does real locking. Give the object to the
* EventImpl code by calling EventImpl::SetEventLock, take it back by calling
* EventImpl::SetNoEventLock or provide a new one. The EventImpl code takes no
* responsibility for the object passed in.
*
* \see EventImpl
*/
class EventLock
{
public:
virtual ~EventLock () {};
virtual void Lock (void) = 0;
virtual void Unlock (void) = 0;
};
/**
* \ingroup simulator
* \brief a simulation event
@@ -34,6 +92,8 @@ namespace ns3 {
* obviously (there are Ref and Unref methods) reference-counted and
* most subclasses are usually created by one of the many Simulator::Schedule
* methods.
*
* \see EventLock
*/
class EventImpl
{
@@ -58,12 +118,31 @@ public:
* Invoked by the simulation engine before calling Invoke.
*/
bool IsCancelled (void);
/**
* Provide a mutex with Lock and Unlock methods to the event implementation
* so that it can do cheap (from the perspective of event memory usage)
* critical sections (mutual exclusion) in the reference counting code.
*
* \param eventLock Pointer to the EventLock object used to contain the
* underlying mutex.
*/
static void SetEventLock (EventLock *eventLock);
/**
* Remove any reference the event implementation code may hold to to an
* existing EventLock and disable the event locking feature.
*
* \see SetEventLock
*/
static void SetNoEventLock (void);
protected:
virtual void Notify (void) = 0;
private:
friend class Event;
bool m_cancel;
mutable uint32_t m_count;
static EventLock *m_eventLock;
};
}; // namespace ns3
@@ -73,13 +152,35 @@ namespace ns3 {
void
EventImpl::Ref (void) const
{
m_count++;
if (m_eventLock)
{
m_eventLock->Lock ();
m_count++;
m_eventLock->Unlock ();
}
else
{
m_count++;
}
}
void
EventImpl::Unref (void) const
{
m_count--;
if (m_count == 0)
register uint32_t c;
if (m_eventLock)
{
m_eventLock->Lock ();
c = --m_count;
m_eventLock->Unlock ();
}
else
{
c = --m_count;
}
if (c == 0)
{
delete this;
}

View File

@@ -0,0 +1,794 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2008 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "simulator.h"
#include "realtime-simulator-impl.h"
#include "wall-clock-synchronizer.h"
#include "scheduler.h"
#include "event-impl.h"
#include "synchronizer.h"
#include "ns3/ptr.h"
#include "ns3/pointer.h"
#include "ns3/assert.h"
#include "ns3/fatal-error.h"
#include "ns3/log.h"
#include "ns3/system-mutex.h"
#include "ns3/boolean.h"
#include "ns3/enum.h"
#include <math.h>
NS_LOG_COMPONENT_DEFINE ("RealtimeSimulatorImpl");
namespace ns3 {
NS_OBJECT_ENSURE_REGISTERED (RealtimeSimulatorImpl);
TypeId
RealtimeSimulatorImpl::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::RealtimeSimulatorImpl")
.SetParent<Object> ()
.AddConstructor<RealtimeSimulatorImpl> ()
.AddAttribute ("ReportSimulatedTime",
"Report simulated time in Simulator::Now if true, otherwise wall-clock time (will be different).",
BooleanValue (true),
MakeBooleanAccessor (&RealtimeSimulatorImpl::m_reportSimulatedTime),
MakeBooleanChecker ())
.AddAttribute ("SynchronizationMode",
"What to do if the simulation cannot keep up with real time.",
EnumValue (SYNC_BEST_EFFORT),
MakeEnumAccessor (&RealtimeSimulatorImpl::SetSynchronizationMode),
MakeEnumChecker (SYNC_BEST_EFFORT, "BestEffort",
SYNC_HARD_LIMIT, "HardLimit"))
.AddAttribute ("HardLimit",
"Maximum acceptable real-time jitter (used in conjunction with SynchronizationMode=HardLimit)",
TimeValue (Seconds (0.1)),
MakeTimeAccessor (&RealtimeSimulatorImpl::m_hardLimit),
MakeTimeChecker ())
;
return tid;
}
void
RealtimeEventLock::Lock (void)
{
m_eventMutex.Lock ();
}
void
RealtimeEventLock::Unlock (void)
{
m_eventMutex.Unlock ();
}
RealtimeSimulatorImpl::RealtimeSimulatorImpl ()
{
NS_LOG_FUNCTION_NOARGS ();
EventImpl::SetEventLock (&m_eventLock);
m_stop = false;
m_stopAt = 0;
m_running = false;
// uids are allocated from 4.
// uid 0 is "invalid" events
// uid 1 is "now" events
// uid 2 is "destroy" events
m_uid = 4;
// before ::Run is entered, the m_currentUid will be zero
m_currentUid = 0;
m_currentTs = 0;
m_unscheduledEvents = 0;
// Be very careful not to do anything that would cause a change or assignment
// of the underlying reference counts of m_synchronizer or you will be sorry.
m_synchronizer = CreateObject<WallClockSynchronizer> ();
}
RealtimeSimulatorImpl::~RealtimeSimulatorImpl ()
{
NS_LOG_FUNCTION_NOARGS ();
while (m_events->IsEmpty () == false)
{
EventId next = m_events->RemoveNext ();
}
m_events = 0;
m_synchronizer = 0;
EventImpl::SetNoEventLock ();
}
void
RealtimeSimulatorImpl::Destroy ()
{
NS_LOG_FUNCTION_NOARGS ();
//
// This function is only called with the private version "disconnected" from
// the main simulator functions. We rely on the user not calling
// Simulator::Destroy while there is a chance that a worker thread could be
// accessing the current instance of the private object. In practice this
// means shutting down the workers and doing a Join() before calling the
// Simulator::Destroy().
//
while (m_destroyEvents.empty () == false)
{
Ptr<EventImpl> ev = m_destroyEvents.front ().PeekEventImpl ();
m_destroyEvents.pop_front ();
NS_LOG_LOGIC ("handle destroy " << ev);
if (ev->IsCancelled () == false)
{
ev->Invoke ();
}
}
}
void
RealtimeSimulatorImpl::SetScheduler (Ptr<Scheduler> scheduler)
{
NS_LOG_FUNCTION_NOARGS ();
{
CriticalSection cs (m_mutex);
if (m_events != 0)
{
while (m_events->IsEmpty () == false)
{
EventId next = m_events->RemoveNext ();
scheduler->Insert (next);
}
}
m_events = scheduler;
}
}
Ptr<Scheduler>
RealtimeSimulatorImpl::GetScheduler (void) const
{
NS_LOG_FUNCTION_NOARGS ();
return m_events;
}
void
RealtimeSimulatorImpl::ProcessOneEvent (void)
{
NS_LOG_FUNCTION_NOARGS ();
//
// The idea here is to wait until the next event comes due. In the case of
// a simulation not locked to realtime, the next event is due immediately and
// we jump time forward -- there is no real time consumed between events. In
// the case of a realtime simulation, we do want real time to be consumed
// between events. It is the synchronizer that causes real time to be
// consumed.
//
// Now, there is a possibility that a wait down in the call to the synchronizer
// will be interrupted. In this case, we need to re-evaluate how long to wait
// until the next event since the interruption may have been caused by an
// event inserted before the one that was on the head of the list when we
// started.
//
// m_synchronizer->Synchronize will return true if the wait was completed
// without interruption, otherwise it will return false. So our goal is to
// sit in a for loop trying to synchronize until it returns true. If this
// happens we will have successfully synchronized the execution time of the
// next event with the wall clock time of the synchronizer.
//
for (;;)
{
uint64_t tsDelay = 0;
uint64_t tsNow = m_currentTs;
uint64_t tsNext = 0;
//
// NextTs peeks into the event list and returns the time stamp of the first
// event in the list. We need to protect this kind of access with a critical
// section.
//
{
CriticalSection cs (m_mutex);
NS_ASSERT_MSG (NextTs () >= m_currentTs,
"RealtimeSimulatorImpl::ProcessOneEvent (): "
"Next event time earlier than m_currentTs (list order error)");
//
// Since we are in realtime mode, the time to delay has got to be the
// difference between the current realtime and the timestamp of the next
// event. Since m_currentTs is actually the timestamp of the last event we
// executed, it's not particularly meaningful for us here since real time has
// since elapsed.
//
// It is possible that the current realtime has drifted past the next event
// time so we need to be careful about that and not delay in that case.
//
NS_ASSERT_MSG (m_synchronizer->Realtime (),
"RealtimeSimulatorImpl::ProcessOneEvent (): Synchronizer reports not Realtime ()");
tsNow = m_synchronizer->GetCurrentRealtime ();
tsNext = NextTs ();
if (tsNext <= tsNow)
{
tsDelay = 0;
}
else
{
tsDelay = tsNext - tsNow;
}
m_synchronizer->SetCondition (false);
}
//
// So, we have a time to delay. This time may actually not be valid anymore
// since we released the critical section and a Schedule may have snuck in just
// after the closing brace above.
//
// It's easiest to understand if you just consider a short tsDelay that only
// requires a SpinWait down in the synchronizer. What will happen is that
// whan Synchronize calls SpinWait, SpinWait will look directly at its
// condition variable. Note that we set this condition variable to false
// inside the critical section above.
//
// SpinWait will go into a forever loop until either the time has expired or
// until the condition variable becomes true. A true condition indicates that
// the wait should stop. The condition is set to true by one of the Schedule
// methods of the simulator; so if we are in a wait down in Synchronize, and
// a Simulator::Schedule is done, the wait down in Synchronize will exit and
// Synchronize will return (false).
//
// So, we set this condition to false in a critical section. The code that
// sets the condition (Simulator::Schedule) also runs protected by the same
// critical section mutex -- there is no race. We call Synchronize -- if no
// Simulator::Schedule is done, the waits (sleep- and spin-wait) will complete
// and Synchronize will return true. If a Schedule is done before we get to
// Synchronize, the Synchronize code will check the condition before going to
// sleep. If a Schedule happens while we are sleeping, we let the kernel wake
// us up.
//
// So the bottom line is that we just stay in this for loop, looking for the
// next timestamp and attempting to sleep until its due. If we've slept until
// the timestamp is due, Synchronize() returns true and we break out of the
//sync loop. If we're interrupted we continue in the loop. We may find that
// the next timestamp is actually earlier than we thought, but we continue
// around the loop and reevaluate and wait for that one correctly.
//
if (m_synchronizer->Synchronize (tsNow, tsDelay))
{
NS_LOG_LOGIC ("Interrupted ...");
break;
}
}
//
// Okay, now more words. We have slept without interruption until the
// timestamp we found at the head of the event list when we started the sleep.
// We are now outside a critical section, so another schedule operation can
// sneak in. What does this mean? The only thing that can "go wrong" is if
// the new event was moved in ahead of the timestamp for which we waited.
//
// If you think about it, this is not a problem, since the best we can
// possibly do is to execute the event as soon as we can execute it. We'll
// be a little late, but we were late anyway.
//
// So we just go ahead and pull the first event off of the list, even though
// it may be the case that it's not actually the one we waited for.
//
// One final tidbit is, "what the heck time is it anyway"? The simulator is
// going to want to get a timestamp from the next event and wait until the
// wall clock time is equal to the timestamp. At the point when those times
// are the same (roughly) we get to this point and set the m_currentTs below.
// That's straightforward here, but you might ask, how does the next event get
// the time when it is inserted from an external source?
//
// The method RealtimeSimulatorImpl::ScheduleNow takes makes an event and
// needs to schedule that event for immediate execution. If the simulator is
// not locked to a realtime source, the current time is m_currentTs. If it is
// locked to a realtime source, we need to use the real current real time.
// This real time is then used to set the event execution time and will be
// read by the next.GetTs below and will set the current simulation time.
//
EventId next;
{
CriticalSection cs (m_mutex);
NS_ASSERT_MSG (m_events->IsEmpty () == false,
"RealtimeSimulatorImpl::ProcessOneEvent(): event queue is empty");
next = m_events->RemoveNext ();
--m_unscheduledEvents;
}
NS_ASSERT_MSG (next.GetTs () >= m_currentTs,
"RealtimeSimulatorImpl::ProcessOneEvent(): "
"next.GetTs() earlier than m_currentTs (list order error)");
NS_LOG_LOGIC ("handle " << next.GetTs ());
m_currentTs = next.GetTs ();
m_currentUid = next.GetUid ();
//
// We're about to run the event and we've done our best to synchronize this
// event execution time to real time. Now, if we're in SYNC_HARD_LIMIT mode
// we have to decide if we've done a good enough job and if we haven't, we've
// been asked to commit ritual suicide.
//
if (m_synchronizationMode == SYNC_HARD_LIMIT)
{
uint64_t tsFinal = m_synchronizer->GetCurrentRealtime ();
uint64_t tsJitter;
if (tsFinal >= m_currentTs)
{
tsJitter = tsFinal - m_currentTs;
}
else
{
tsJitter = m_currentTs - tsFinal;
}
if (tsJitter > static_cast<uint64_t>(m_hardLimit.GetTimeStep ()))
{
NS_FATAL_ERROR ("RealtimeSimulatorImpl::ProcessOneEvent (): "
"Hard real-time limit exceeded (jitter = " << tsJitter << ")");
}
}
EventImpl *event = next.PeekEventImpl ();
m_synchronizer->EventStart ();
event->Invoke ();
m_synchronizer->EventEnd ();
}
bool
RealtimeSimulatorImpl::IsFinished (void) const
{
NS_LOG_FUNCTION_NOARGS ();
bool rc;
{
CriticalSection cs (m_mutex);
rc = m_events->IsEmpty ();
}
return rc;
}
//
// Peeks into event list. Should be called with critical section locked.
//
uint64_t
RealtimeSimulatorImpl::NextTs (void) const
{
NS_LOG_FUNCTION_NOARGS ();
NS_ASSERT_MSG (m_events->IsEmpty () == false,
"RealtimeSimulatorImpl::NextTs(): event queue is empty");
EventId id = m_events->PeekNext ();
return id.GetTs ();
}
//
// Calls NextTs(). Should be called with critical section locked.
//
Time
RealtimeSimulatorImpl::Next (void) const
{
NS_LOG_FUNCTION_NOARGS ();
return TimeStep (NextTs ());
}
void
RealtimeSimulatorImpl::Run (void)
{
NS_LOG_FUNCTION_NOARGS ();
m_running = true;
NS_ASSERT_MSG (m_currentTs == 0,
"RealtimeSimulatorImpl::Run(): The beginning of time is not zero");
m_synchronizer->SetOrigin (m_currentTs);
for (;;)
{
bool done = false;
{
CriticalSection cs (m_mutex);
//
// In all cases we stop when the event list is empty. If you are doing a
// realtime simulation and you want it to extend out for some time, you must
// call StopAt. In the realtime case, this will stick a placeholder event out
// at the end of time.
//
if (m_stop || m_events->IsEmpty ())
{
done = true;
}
//
// We also want to stop the simulator at some time even if there are events
// that have been scheduled out in the future. If we're in realtime mode, we
// actually have time passing, so we must look at the realtime clock to see if
// we're past the end time.
//
if (m_stopAt && m_stopAt <= m_synchronizer->GetCurrentRealtime ())
{
done = true;
}
}
if (done)
{
break;
}
ProcessOneEvent ();
}
//
// If the simulator stopped naturally by lack of events, make a
// consistency test to check that we didn't lose any events along the way.
//
{
CriticalSection cs (m_mutex);
NS_ASSERT_MSG (m_events->IsEmpty () == false || m_unscheduledEvents == 0,
"RealtimeSimulatorImpl::Run(): Empty queue and unprocessed events");
}
m_running = false;
}
bool
RealtimeSimulatorImpl::Running (void) const
{
NS_LOG_FUNCTION_NOARGS ();
return m_running;
}
bool
RealtimeSimulatorImpl::Realtime (void) const
{
NS_LOG_FUNCTION_NOARGS ();
return m_synchronizer->Realtime ();
}
void
RealtimeSimulatorImpl::RunOneEvent (void)
{
NS_LOG_FUNCTION_NOARGS ();
NS_FATAL_ERROR ("DefaultSimulatorImpl::RunOneEvent(): Not allowed in realtime simulations");
}
void
RealtimeSimulatorImpl::Stop (void)
{
NS_LOG_FUNCTION_NOARGS ();
m_stop = true;
}
static void Placeholder (void) {}
void
RealtimeSimulatorImpl::Stop (Time const &time)
{
NS_LOG_FUNCTION (time);
NS_ASSERT_MSG (time.IsPositive (),
"RealtimeSimulatorImpl::Stop(): Negative time");
Time absolute = Simulator::Now () + time;
m_stopAt = absolute.GetTimeStep ();
//
// For the realtime case, we need a real event sitting out at the end of time
// to keep the simulator running (sleeping) while there are no other events
// present. If an "external" device in another thread decides to schedule an
// event, the sleeping synchronizer will be awakened and the new event will
// be run.
//
// The easiest thing to do is to call back up into the simulator to take
// advantage of all of the nice event wrappers. This will call back down into
// RealtimeSimulatorImpl::Schedule to do the work.
//
Simulator::Schedule (absolute, &Placeholder);
}
EventId
RealtimeSimulatorImpl::Schedule (Time const &time, const Ptr<EventImpl> &event)
{
NS_LOG_FUNCTION (time << event);
//
// This is a little tricky. We get a Ptr<EventImpl> passed down to us in some
// thread context. This Ptr<EventImpl> is not yet shared in any way. It is
// possible however that the calling context is not the context of the main
// scheduler thread (eg. it is in the context of a separate device thread).
// It would be bad (TM) if we naively wrapped the EventImpl up in an EventId
// that would be accessible from multiple threads without considering thread
// safety.
//
// Having multiple EventId containing Ptr<EventImpl> in different thread
// contexts creates a situation where atomic reference count decrements
// would be required (think about an event being scheduled with the calling
// yielding immediately afterward. The scheduler could become ready and
// fire the event which would eventually want to release the EventImpl. If
// the calling thread were readied and executed in mid-release, it would also
// want to release the EventImpl "at the same time." If we are careless about
// this, multiple deletes are sure to eventually happen as the threads
// separately play with the EventImpl reference counts.
//
// The way this all works is that we created an EventImpl in the template
// function that called us, which may be in the context of a thread separate
// from the simulator thread. We are managing the lifetime of the EventImpl
// with an intrusive reference count. The count was initialized to one when
// it was created and is still one right now. We're going to "wrap" this
// EventImpl in an EventId in this method. There's an assignment of our
// Ptr<EventImpl> into another Ptr<EventImpl> inside the EventId which will
// bump the ref count to two. We're going to return this EventId to the
// caller so the calling thread will hold a reference to the underlying
// EventImpl. This is what causes the first half of the multithreading issue.
//
// We are going to call Insert() on the EventId to put the event into the
// scheduler. Down there, it will do a PeekEventImpl to get the pointer to
// the EventImpl and manually increment the reference count (to three). From
// there, the sheduler works with the EventImpl pointer. When the EventId
// below goes out of scope, the Ptr<EventImpl> destructor is run and the ref
// count is decremented to two. When this function returns to the calling
// template function, the Ptr<EventImpl> there will go out of scope and we'll
// decrement the EventImpl ref count leaving it to be the one held by our
// scheduler directly.
//
// The scheduler removes the EventImpl in Remove() or RemoveNext(). In the
// case of Remove(), the scheduler is provided an Event reference and locates
// the corresponding EventImpl in the event list. It gets the raw pointer to
// the EventImpl, erases the pointer in the list, and manually calls Unref()
// on the pointer. In RemoveNext(), it gets the raw pointer from the list and
// assigns it to a Ptr<EventImpl> without bumping the reference count, thereby
// transferring ownership to a containing EventId. This is the second half of
// the multithreading issue.
//
// It's clear that we cannot have a situation where the EventImpl is "owned" by
// multiple threads. The calling thread is free to hold the EventId as long as
// it wants and manage the reference counts to the underlying EventImpl all it
// wants. The scheduler is free to do the same; and will eventually release
// the reference in the context of thread running ProcessOneEvent(). It is
// "a bad thing" (TM) if these two threads decide to release the underlying
// EventImpl "at the same time."
//
// The answer is to make the reference counting of the EventImpl thread safe;
// which we do. We don't want to force the event implementation to carry around
// a mutex, so we "lend" it one using a RealtimeEventLock object (m_eventLock)
// in the constructor and take it back in the destructor.
//
EventId id;
{
CriticalSection cs (m_mutex);
NS_ASSERT_MSG (time.IsPositive (),
"RealtimeSimulatorImpl::Schedule(): Negative time");
NS_ASSERT_MSG (
time >= TimeStep (m_currentTs),
"RealtimeSimulatorImpl::Schedule(): time < m_currentTs");
uint64_t ts = (uint64_t) time.GetTimeStep ();
id = EventId (event, ts, m_uid);
m_uid++;
++m_unscheduledEvents;
m_events->Insert (id);
m_synchronizer->Signal ();
}
return id;
}
EventId
RealtimeSimulatorImpl::ScheduleNow (const Ptr<EventImpl> &event)
{
NS_LOG_FUNCTION_NOARGS ();
EventId id;
{
CriticalSection cs (m_mutex);
id = EventId (event, m_synchronizer->GetCurrentRealtime (), m_uid);
m_uid++;
++m_unscheduledEvents;
m_events->Insert (id);
m_synchronizer->Signal ();
}
return id;
}
EventId
RealtimeSimulatorImpl::ScheduleDestroy (const Ptr<EventImpl> &event)
{
NS_LOG_FUNCTION_NOARGS ();
EventId id;
{
CriticalSection cs (m_mutex);
//
// Time doesn't really matter here (especially in realtime mode). It is
// overridden by the uid of 2 which identifies this as an event to be
// executed at Simulator::Destroy time.
//
id = EventId (event, m_currentTs, 2);
m_destroyEvents.push_back (id);
m_uid++;
}
return id;
}
Time
RealtimeSimulatorImpl::Now (void) const
{
//
// The behavior of Now depends on the setting of the "ReportSimulatedTime"
// attribute. If this is set to true, then Now will pretend that the realtime
// synchronization is perfect and that event execution consumes no time. This
// allows models written with this kind of assumption to work as they did in
// non-real-time cases. However, if the attribute is set to false, we're going
// to give the caller the bad news right in the face. If it sets an event to
// be executed at 10.0000000 seconds, and calls Simulator::Now it will get the
// realtime clock value which almost certainly will *not* be ten seconds. We'll
// get as close as possible, but we won't be perfect and we won't pretend to be.
// Also, if the client calls Simulator::Now multiple times in an event, she will
// get different times as the underlying realtime clock will have moved. However
// if the simulation is not actually running, we'll use the last event timestamp
// as the time, which will be a precise simulation time (0 sec before the sim
// starts, or the end time after the simulation ends).
//
if ((m_reportSimulatedTime == false) && Running ())
{
return TimeStep (m_synchronizer->GetCurrentRealtime ());
}
else
{
return TimeStep (m_currentTs);
}
}
Time
RealtimeSimulatorImpl::GetDelayLeft (const EventId &id) const
{
if (IsExpired (id))
{
return TimeStep (0);
}
else
{
return TimeStep (id.GetTs () - m_synchronizer->GetCurrentRealtime ());
}
}
void
RealtimeSimulatorImpl::Remove (const EventId &ev)
{
if (ev.GetUid () == 2)
{
// destroy events.
for (DestroyEvents::iterator i = m_destroyEvents.begin ();
i != m_destroyEvents.end ();
i++)
{
if (*i == ev)
{
m_destroyEvents.erase (i);
break;
}
}
return;
}
if (IsExpired (ev))
{
return;
}
{
CriticalSection cs (m_mutex);
m_events->Remove (ev);
--m_unscheduledEvents;
Cancel (ev);
}
}
void
RealtimeSimulatorImpl::Cancel (const EventId &id)
{
if (IsExpired (id) == false)
{
id.PeekEventImpl ()->Cancel ();
}
}
bool
RealtimeSimulatorImpl::IsExpired (const EventId &ev) const
{
if (ev.GetUid () == 2)
{
// destroy events.
for (DestroyEvents::const_iterator i = m_destroyEvents.begin ();
i != m_destroyEvents.end (); i++)
{
if (*i == ev)
{
return false;
}
}
return true;
}
if (ev.PeekEventImpl () == 0 ||
ev.GetTs () < m_currentTs ||
(ev.GetTs () == m_currentTs &&
ev.GetUid () <= m_currentUid) ||
ev.PeekEventImpl ()->IsCancelled ())
{
return true;
}
else
{
return false;
}
}
Time
RealtimeSimulatorImpl::GetMaximumSimulationTime (void) const
{
// XXX: I am fairly certain other compilers use other non-standard
// post-fixes to indicate 64 bit constants.
return TimeStep (0x7fffffffffffffffLL);
}
void
RealtimeSimulatorImpl::SetSynchronizationMode (enum SynchronizationMode mode)
{
NS_LOG_FUNCTION (mode);
m_synchronizationMode = mode;
}
RealtimeSimulatorImpl::SynchronizationMode
RealtimeSimulatorImpl::GetSynchronizationMode (void) const
{
NS_LOG_FUNCTION_NOARGS ();
return m_synchronizationMode;
}
void
RealtimeSimulatorImpl::SetHardLimit (Time limit)
{
NS_LOG_FUNCTION (limit);
m_hardLimit = limit;
}
Time
RealtimeSimulatorImpl::GetHardLimit (void) const
{
NS_LOG_FUNCTION_NOARGS ();
return m_hardLimit;
}
}; // namespace ns3

View File

@@ -0,0 +1,153 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2008 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef REALTIME_SIMULATOR_IMPL_H
#define REALTIME_SIMULATOR_IMPL_H
#include "simulator-impl.h"
#include "scheduler.h"
#include "synchronizer.h"
#include "event-impl.h"
#include "ns3/ptr.h"
#include "ns3/assert.h"
#include "ns3/log.h"
#include <list>
#include <fstream>
namespace ns3 {
class RealtimeEventLock : public EventLock
{
public:
void Lock (void);
void Unlock (void);
private:
SystemMutex m_eventMutex;
};
class RealtimeSimulatorImpl : public SimulatorImpl
{
public:
static TypeId GetTypeId (void);
/**
* Enumeration of the types of packets supported in the class.
*
*/
enum SynchronizationMode {
SYNC_BEST_EFFORT, /** Make a best effort to keep synced to real-time */
SYNC_HARD_LIMIT, /** Keep to real-time within a tolerance or die trying */
};
RealtimeSimulatorImpl ();
~RealtimeSimulatorImpl ();
void Destroy ();
void EnableLogTo (char const *filename);
bool IsFinished (void) const;
Time Next (void) const;
void Stop (void);
void Stop (Time const &time);
EventId Schedule (Time const &time, const Ptr<EventImpl> &event);
EventId ScheduleNow (const Ptr<EventImpl> &event);
EventId ScheduleDestroy (const Ptr<EventImpl> &event);
void Remove (const EventId &ev);
void Cancel (const EventId &ev);
bool IsExpired (const EventId &ev) const;
virtual void RunOneEvent (void);
void Run (void);
Time Now (void) const;
Time GetDelayLeft (const EventId &id) const;
Time GetMaximumSimulationTime (void) const;
void SetScheduler (Ptr<Scheduler> scheduler);
Ptr<Scheduler> GetScheduler (void) const;
void SetSynchronizationMode (RealtimeSimulatorImpl::SynchronizationMode mode);
RealtimeSimulatorImpl::SynchronizationMode GetSynchronizationMode (void) const;
void SetHardLimit (Time limit);
Time GetHardLimit (void) const;
private:
bool Running (void) const;
bool Realtime (void) const;
void ProcessOneEvent (void);
uint64_t NextTs (void) const;
typedef std::list<EventId> DestroyEvents;
DestroyEvents m_destroyEvents;
uint64_t m_stopAt;
bool m_stop;
bool m_running;
// The following variables are protected using the m_mutex
Ptr<Scheduler> m_events;
int m_unscheduledEvents;
uint32_t m_uid;
uint32_t m_currentUid;
uint64_t m_currentTs;
mutable SystemMutex m_mutex;
RealtimeEventLock m_eventLock;
Ptr<Synchronizer> m_synchronizer;
/*
* In calls to Simulator::Now we have a basic choice to make. We can either
* report back the time the simulator thinks it should be, or we can report
* the time it actually is.
*
* The synchronizer will make an attempt to cause these two numbers to be as
* close as possible to each other, but they will never be exactly the same.
* We give the client a choice in this respect.
*
* If the client sets m_reportSimulatedTime to true, the behavior will be that
* the simulator runs as close as possible to real time, but reports back to the
* client that it is running at exactly real time, and consuming no real time
* as each event executes. This allows for deterministic execution times and
* repeatable trace files.
*
* If the client sets m_reportSimulatedTime to false, the behavior will be that
* the simulator runs as close as possible to real time, and reports back to the
* client the real wall-clock time whenever it asks. Real time will be consumed
* as each event executes. This allows for non-deterministic execution times and
* real variations in event executions. Simulation time will be influenced by
* variations in host process scheduling, for example.
*/
bool m_reportSimulatedTime;
/**
* The policy to use if the simulation cannot keep synchronized to real-time.
*/
SynchronizationMode m_synchronizationMode;
/**
* The maximum allowable drift from real-time in SYNC_HARD_LIMIT mode.
*/
Time m_hardLimit;
};
} // namespace ns3
#endif /* REALTIME_SIMULATOR_IMPL_H */

View File

@@ -0,0 +1,158 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2008 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "synchronizer.h"
namespace ns3 {
TypeId
Synchronizer::GetTypeId (void)
{
static TypeId tid = TypeId ("Synchronizer")
.SetParent<Object> ()
;
return tid;
}
Synchronizer::Synchronizer ()
: m_realtimeOriginNano (0), m_simOriginNano (0)
{
}
Synchronizer::~Synchronizer ()
{
}
bool
Synchronizer::Realtime (void)
{
return DoRealtime ();
}
uint64_t
Synchronizer::GetCurrentRealtime (void)
{
return NanosecondToTimeStep (DoGetCurrentRealtime ());
}
void
Synchronizer::SetOrigin (uint64_t ts)
{
m_simOriginNano = TimeStepToNanosecond (ts);
DoSetOrigin (m_simOriginNano);
}
uint64_t
Synchronizer::GetOrigin (void)
{
return NanosecondToTimeStep (m_simOriginNano);
}
int64_t
Synchronizer::GetDrift (uint64_t ts)
{
int64_t tDrift = DoGetDrift (TimeStepToNanosecond (ts));
if (tDrift < 0)
{
return -NanosecondToTimeStep (-tDrift);
} else {
return NanosecondToTimeStep (tDrift);
}
}
bool
Synchronizer::Synchronize (uint64_t tsCurrent, uint64_t tsDelay)
{
return DoSynchronize (TimeStepToNanosecond (tsCurrent),
TimeStepToNanosecond (tsDelay));
}
void
Synchronizer::Signal (void)
{
DoSignal ();
}
void
Synchronizer::SetCondition (bool cond)
{
DoSetCondition (cond);
}
void
Synchronizer::EventStart (void)
{
DoEventStart ();
}
uint64_t
Synchronizer::EventEnd (void)
{
return NanosecondToTimeStep (DoEventEnd ());
}
uint64_t
Synchronizer::TimeStepToNanosecond (uint64_t ts)
{
switch (TimeStepPrecision::Get ()) {
case TimeStepPrecision::S:
return ts * 1000000000;
case TimeStepPrecision::MS:
return ts * 1000000;
case TimeStepPrecision::US:
return ts * 1000;
case TimeStepPrecision::NS:
return ts;
case TimeStepPrecision::PS:
return ts / 1000;
case TimeStepPrecision::FS:
return ts / 1000000;
default:
NS_ASSERT_MSG (false, "Synchronizer::TimeStepToNanosecond: "
"Unexpected precision not implemented");
return 0;
}
}
uint64_t
Synchronizer::NanosecondToTimeStep (uint64_t ns)
{
switch (TimeStepPrecision::Get ()) {
case TimeStepPrecision::S:
return ns / 1000000000;
case TimeStepPrecision::MS:
return ns / 1000000;
case TimeStepPrecision::US:
return ns / 1000;
case TimeStepPrecision::NS:
return ns;
case TimeStepPrecision::PS:
return ns * 1000;
case TimeStepPrecision::FS:
return ns * 1000000;
default:
NS_ASSERT_MSG (false, "Synchronizer::NanosecondToTimeStep: "
"Unexpected precision not implemented");
return 0;
}
}
}; // namespace ns3

View File

@@ -0,0 +1,334 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2008 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef SYNCHRONIZER_H
#define SYNCHRONIZER_H
#include <stdint.h>
#include "nstime.h"
#include "ns3/object.h"
namespace ns3 {
/**
* @brief Base class used for synchronizing the simulation events to some
* real time "wall clock."
*
* The simulation clock is maintained as a 64-bit integer in a unit specified
* by the user through the TimeStepPrecision::Set function. This means that
* it is not possible to specify event expiration times with anything better
* than this user-specified accuracy. We use this clock for the simulation
* time.
*
* The real-time clock is maintained as a 64-bit integer count of nanoseconds.
*
* The synchronization between the simulation clock and the real-time clock
* is maintained using a combination of sleep-waiting, busy-waiting and a
* feedback loop.
*/
class Synchronizer : public Object
{
public:
static TypeId GetTypeId (void);
Synchronizer ();
virtual ~Synchronizer ();
/**
* @brief Return true if this synchronizer is actually synchronizing to a
* realtime clock. The simulator sometimes needs to know this.
* @returns True if locked with realtime, false if not.
*/
bool Realtime (void);
/**
* @brief Retrieve the value of the origin of the underlying normalized wall
* clock time in simulator timestep units.
*
* @returns The normalized wall clock time (in simulator timestep units).
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
*/
uint64_t GetCurrentRealtime (void);
/**
* @brief Establish a correspondence between a simulation time and the
* synchronizer real time.
*
* This method is expected to be called at the "instant" before simulation
* begins. At this point, simulation time = 0, and a
* set = 0 in this method. We then associate this time with the current
* value of the real time clock that will be used to actually perform the
* synchronization.
*
* Subclasses are expected to implement the corresponding DoSetOrigin pure
* virtual method to do the actual real-time-clock-specific work of making the
* correspondence mentioned above.
*
* @param ts The simulation time we should use as the origin (in simulator
* timestep units).
* @see TimeStepPrecision::Get
* @see TimeStepPrecision::DoSetOrigin
*/
void SetOrigin (uint64_t ts);
/**
* @brief Retrieve the value of the origin of the simulation time in
* simulator timestep units.
*
* @returns The simulation time used as the origin (in simulator timestep
* units).
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
*/
uint64_t GetOrigin (void);
/**
* @brief Retrieve the difference between the real time clock used to
* synchronize the simulation and the simulation time (in simulator timestep
* units).
*
* @param ts Simulation timestep from the simulator interpreted as current time
* in the simulator.
* @returns Simulation timestep (in simulator timestep units) minus origin
* time (stored internally in nanosecond units).
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
* @see Synchronizer::DoGetDrift
*/
int64_t GetDrift (uint64_t ts);
/**
* @brief Wait until the real time is in sync with the specified simulation
* time or until the synchronizer is Sigalled.
*
* This is where the real work of synchronization is done. The Time passed
* in as a parameter is the simulation time. The job of Synchronize is to
* translate from simulation time to synchronizer time (in a perfect world
* this is the same time) and then figure out how long in real-time it needs
* to wait until that synchronizer / simulation time comes around.
*
* Subclasses are expected to implement the corresponding DoSynchronize pure
* virtual method to do the actual real-time-clock-specific work of waiting
* (either busy-waiting or sleeping, or some combination thereof) until the
* requested simulation time.
*
* @param tsCurrent The current simulation time (in simulator timestep units).
* @param tsDelay The simulation time we need to wait for (in simulator
* timestep units).
* @returns True if the function ran to completion, false if it was interrupted
* by a Signal.
* @see TimeStepPrecision::Get
* @see Synchronizer::DoSynchronize
* @see Synchronizer::Signal
*/
bool Synchronize (uint64_t tsCurrent, uint64_t tsDelay);
/**
* @brief Tell a posible simulator thread waiting in the Synchronize method
* that an event has happened which demands a reevaluation of the wait time.
* This will cause the thread to wake and return to the simulator proper
* where it can get its bearings.
*
* @see Synchronizer::Synchronize
* @see Synchronizer::DoSignal
*/
void Signal (void);
/**
* @brief Set the condition variable that tells a posible simulator thread
* waiting in the Synchronize method that an event has happened which demands
* a reevaluation of the wait time.
*
* @see Synchronizer::Signal
*/
void SetCondition (bool);
/**
* @brief Ask the synchronizer to remember what time it is. Typically used
* with EventEnd to determine the real execution time of a simulation event.
*
* @see Synchronizer::EventEnd
* @see TimeStepPrecision::Get
*/
void EventStart (void);
/**
* @brief Ask the synchronizer to return the time step between the instant
* remembered during EventStart and now. Used in conjunction with EventStart
* to determine the real execution time of a simulation event.
*
* @see Synchronizer::EventStart
* @see TimeStepPrecision::Get
*/
uint64_t EventEnd (void);
protected:
/**
* @brief Establish a correspondence between a simulation time and a
* wall-clock (real) time.
*
* @internal
*
* There are three timelines involved here: the simulation time, the
* (absolute) wall-clock time and the (relative) synchronizer real time.
* Calling this method makes a correspondence between the origin of the
* synchronizer time and the current wall-clock time.
*
* This method is expected to be called at the "instant" before simulation
* begins. At this point, simulation time = 0, and synchronizer time is
* set = 0 in this method. We then associate this time with the current
* value of the real time clock that will be used to actually perform the
* synchronization.
*
* Subclasses are expected to implement this method to do the actual
* real-time-clock-specific work of making the correspondence mentioned above.
* for example, this is where the differences between Time parameters and
* parameters to clock_nanosleep would be dealt with.
*
* @param ns The simulation time we need to use as the origin (normalized to
* nanosecond units).
* @see Synchronizer::SetOrigin
* @see TimeStepPrecision::Get
*/
virtual void DoSetOrigin (uint64_t ns) = 0;
/**
* @brief Return true if this synchronizer is actually synchronizing to a
* realtime clock. The simulator sometimes needs to know this.
*
* @internal
*
* Subclasses are expected to implement this method to tell the outside world
* whether or not they are synchronizing to a realtime clock.
*
* @returns True if locked with realtime, false if not.
*/
virtual bool DoRealtime (void) = 0;
/**
* @brief Retrieve the value of the origin of the underlying normalized wall
* clock time in simulator timestep units.
*
* @internal
*
* Subclasses are expected to implement this method to do the actual
* real-time-clock-specific work of getting the current time.
*
* @returns The normalized wall clock time (in nanosecond units).
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
*/
virtual uint64_t DoGetCurrentRealtime (void) = 0;
/**
* @brief Wait until the real time is in sync with the specified simulation
* time.
*
* @internal
*
* This is where the real work of synchronization is done. The Time passed
* in as a parameter is the simulation time. The job of Synchronize is to
* translate from simulation time to synchronizer time (in a perfect world
* this is the same time) and then figure out how long in real-time it needs
* to wait until that synchronizer / simulation time comes around.
*
* Subclasses are expected to implement this method to do the actual
* real-time-clock-specific work of waiting (either busy-waiting or sleeping,
* or some combination) until the requested simulation time.
*
* @param nsCurrent The current simulation time (normalized to nanosecond
* units).
* @param nsDelay The simulation time we need to wait for (normalized to
* nanosecond units).
* @returns True if the function ran to completion, false if it was interrupted
* by a Signal.
* @see Synchronizer::Synchronize
* @see TimeStepPrecision::Get
* @see Synchronizer::Signal
*/
virtual bool DoSynchronize (uint64_t nsCurrent, uint64_t nsDelay) = 0;
/**
* @brief Declaration of the method used to tell a posible simulator thread
* waiting in the DoSynchronize method that an event has happened which
* demands a reevaluation of the wait time.
*
* @see Synchronizer::Signal
*/
virtual void DoSignal (void) = 0;
/**
* @brief Declaration of the method used to set the condition variable that
* tells a posible simulator thread waiting in the Synchronize method that an
* event has happened which demands a reevaluation of the wait time.
*
* @see Synchronizer::SetCondition
*/
virtual void DoSetCondition (bool) = 0;
/**
* @brief Declaration of method used to retrieve drift between the real time
* clock used to synchronize the simulation and the current simulation time.
*
* @internal
*
* @param ns Simulation timestep from the simulator normalized to nanosecond
* steps.
* @returns Drift in nanosecond units.
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
* @see Synchronizer::GetDrift
*/
virtual int64_t DoGetDrift (uint64_t ns) = 0;
virtual void DoEventStart (void) = 0;
virtual uint64_t DoEventEnd (void) = 0;
uint64_t m_realtimeOriginNano;
uint64_t m_simOriginNano;
private:
/**
* @brief Convert a simulator time step (which can be steps of time in a
* user-specified unit) to a normalized time step in nanosecond units.
*
* @internal
*
* @param ts The simulation time step to be normalized.
* @returns The simulation time step normalized to nanosecond units.
* @see TimeStepPrecision::Get
*/
uint64_t TimeStepToNanosecond (uint64_t ts);
/**
* @brief Convert a normalized nanosecond count into a simulator time step
* (which can be steps of time in a user-specified unit).
*
* @internal
*
* @param ns The nanosecond count step to be converted
* @returns The simulation time step to be interpreted in appropriate units.
* @see TimeStepPrecision::Get
*/
uint64_t NanosecondToTimeStep (uint64_t ns);
};
}; // namespace ns3
#endif /* SYNCHRONIZER_H */

View File

@@ -0,0 +1,490 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2008 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <time.h>
#include <sys/time.h>
#include <sched.h>
#include "ns3/log.h"
#include "ns3/system-condition.h"
#include "wall-clock-synchronizer.h"
NS_LOG_COMPONENT_DEFINE ("WallClockSynchronizer");
namespace ns3 {
WallClockSynchronizer::WallClockSynchronizer ()
{
NS_LOG_FUNCTION_NOARGS ();
//
// In Linux, the basic timekeeping unit is derived from a variable called HZ
// HZ is the frequency in hertz of the system timer. The system timer fires
// every 1/HZ seconds and a counter, called the jiffies counter is incremented
// at each tick. The time between ticks is called a jiffy (American slang for
// a short period of time). The ticking of the jiffies counter is how the
// the kernel tells time.
//
// Now, the shortest time the kernel can sleep is one jiffy since a timer
// has to be set to expire and trigger the process to be made ready. The
// Posix clock CLOCK_REALTIME is defined as a 1/HZ clock, so by doing a
// clock_getres () on the realtime clock we can infer the scheduler quantum
// and the minimimum sleep time for the system. This is most certainly NOT
// going to be one nanosecond even though clock_nanosleep () pretends it is.
//
// The reason this number is important is that we are going to schedule lots
// of waits for less time than a jiffy. The clock_nanosleep function is
// going to guarantee that it will sleep for AT LEAST the time specified.
// The least time that it will sleep is a jiffy.
//
// In order to deal with this, we are going to do a spin-wait if the simulator
// requires a delay less than a jiffy. This is on the order of one millisecond
// (999848 ns) on the ns-regression machine.
//
struct timespec ts;
clock_getres (CLOCK_REALTIME, &ts);
m_jiffy = ts.tv_sec * NS_PER_SEC + ts.tv_nsec;
NS_LOG_INFO ("Jiffy is " << m_jiffy << " ns");
#if 0
//
// DANGER DANGER WILL ROBINSON
//
// Don't enable this code, su root and run a sim unless you really know what
// you are doing.
//
struct sched_param sp;
sp.sched_priority = sched_get_priority_max (SCHED_FIFO);
sched_setscheduler (0, SCHED_FIFO, &sp);
#endif
}
WallClockSynchronizer::~WallClockSynchronizer ()
{
NS_LOG_FUNCTION_NOARGS ();
}
bool
WallClockSynchronizer::DoRealtime (void)
{
NS_LOG_FUNCTION_NOARGS ();
return true;
}
uint64_t
WallClockSynchronizer::DoGetCurrentRealtime (void)
{
NS_LOG_FUNCTION_NOARGS ();
return GetNormalizedRealtime ();
}
void
WallClockSynchronizer::DoSetOrigin (uint64_t ns)
{
NS_LOG_FUNCTION_NOARGS ();
//
// In order to make sure we're really locking the simulation time to some
// wall-clock time, we need to be able to compare that simulation time to
// that wall-clock time. The wall clock will have been running for some
// long time and will probably have a huge count of nanoseconds in it. We
// save the real time away so we can subtract it from "now" later and get
// a count of nanoseconds in real time since the simulation started.
//
m_realtimeOriginNano = GetRealtime ();
NS_LOG_INFO ("origin = " << m_realtimeOriginNano);
}
int64_t
WallClockSynchronizer::DoGetDrift (uint64_t ns)
{
NS_LOG_FUNCTION_NOARGS ();
//
// In order to make sure we're really locking the simulation time to some
// wall-clock time, we need to be able to compare that simulation time to
// that wall-clock time. In DoSetOrigin we saved the real time at the start
// of the simulation away. This is the place where we subtract it from "now"
// to a count of nanoseconds in real time since the simulation started. We
// then subtract the current real time in normalized nanoseconds we just got
// from the normalized simulation time in nanoseconds that is passed in as
// the parameter ns. We return an integer difference, but in reality all of
// the mechanisms that cause wall-clock to simuator time drift cause events
// to be late. That means that the wall-clock will be higher than the
// simulation time and drift will be positive. I would be astonished to
// see a negative drift, but the possibility is admitted for other
// implementations; and we'll use the ability to report an early result in
// DoSynchronize below.
//
uint64_t nsNow = GetNormalizedRealtime ();
if (nsNow > ns)
{
//
// Real time (nsNow) is larger/later than the simulator time (ns). We are
// behind real time and the difference (drift) is positive.
//
return (int64_t)(nsNow - ns);
}
else
{
//
// Real time (nsNow) is smaller/earlier than the simulator time (ns). We are
// ahead of real time and the difference (drift) is negative.
//
return -(int64_t)(ns - nsNow);
}
}
bool
WallClockSynchronizer::DoSynchronize (uint64_t nsCurrent, uint64_t nsDelay)
{
NS_LOG_FUNCTION_NOARGS ();
//
// This is the belly of the beast. We have received two parameters from the
// simulator proper -- a current simulation time (nsCurrent) and a simulation
// time to delay which identifies the time the next event is supposed to fire.
//
// The first thing we need to do is to (try and) correct for any realtime
// drift that has happened in the system. In this implementation, we realize
// that all mechanisms for drift will cause the drift to be such that the
// realtime is greater than the simulation time. This typically happens when
// our process is put to sleep for a given time, but actually sleeps for
// longer. So, what we want to do is to "catch up" to realtime and delay for
// less time than we are actually asked to delay. DriftCorrect will return a
// number from 0 to nsDelay corresponding to the amount of catching-up we
// need to do. If we are more than nsDelay behind, we do not wait at all.
//
// Note that it will be impossible to catch up if the amount of drift is
// cumulatively greater than the amount of delay between events. The method
// GetDrift () is available to clients of the syncrhonizer to keep track of
// the cumulative drift. The client can assert if the drift gets out of
// hand, print warning messages, or just ignore the situation and hope it will
// go away.
//
uint64_t ns = DriftCorrect (nsCurrent, nsDelay);
NS_LOG_INFO ("Synchronize ns = " << ns);
//
// Once we've decided on how long we need to delay, we need to split this
// time into sleep waits and busy waits. The reason for this is described
// in the comments for the constructor where jiffies and jiffy resolution is
// explained.
//
// Here, I'll just say that we need that the jiffy is the minimum resolution
// of the system clock. It can only sleep in blocks of time equal to a jiffy.
// If we want to be more accurate than a jiffy (we do) then we need to sleep
// for some number of jiffies and then busy wait for any leftover time.
//
uint64_t numberJiffies = ns / m_jiffy;
NS_LOG_INFO ("Synchronize numberJiffies = " << numberJiffies);
//
// This is where the real world interjects its very ugly head. The code
// immediately below reflects the fact that a sleep is actually quite probably
// going to end up sleeping for some number of jiffies longer than you wanted.
// This is because your system is going to be off doing other unimportant
// stuff during that extra time like running file systems and networks. What
// we want to do is to ask the system to sleep enough less than the requested
// delay so that it comes back early most of the time (coming back early is
// fine, coming back late is bad). If we can convince the system to come back
// early (most of the time), then we can busy-wait until the requested
// completion time actually comes around (most of the time).
//
// The tradeoff here is, of course, that the less time we spend sleeping, the
// more accurately we will sync up; but the more CPU time we will spend busy
// waiting (doing nothing).
//
// I'm not really sure about this number -- a boss of mine once said, "pick
// a number and it'll be wrong." But this works for now.
//
// XXX BUGBUG Hardcoded tunable parameter below.
//
if (numberJiffies > 3)
{
NS_LOG_INFO ("SleepWait for " << numberJiffies * m_jiffy << " ns");
NS_LOG_INFO ("SleepWait until " << nsCurrent + numberJiffies * m_jiffy
<< " ns");
//
// SleepWait is interruptible. If it returns true it meant that the sleep
// went until the end. If it returns false, it means that the sleep was
// interrupted by a Signal. In this case, we need to return and let the
// simulator re-evaluate what to do.
//
if (SleepWait ((numberJiffies - 3) * m_jiffy) == false)
{
NS_LOG_INFO ("SleepWait interrupted");
return false;
}
}
NS_LOG_INFO ("Done with SleepWait");
//
// We asked the system to sleep for some number of jiffies, but that doesn't
// mean we actually did. Let's re-evaluate what we need to do here. Maybe
// we're already late. Probably the "real" delay time left has little to do
// with what we would calculate it to be naively.
//
// We are now at some Realtime. The important question now is not, "what
// would we calculate in a mathematicians paradise," it is, "how many
// nanoseconds do we need to busy-wait until we get to the Realtime that
// corresponds to nsCurrent + nsDelay (in simulation time). We have a handy
// function to do just that -- we ask for the time the realtime clock has
// drifted away from the simulation clock. That's our answer. If the drift
// is negative, we're early and we need to busy wait for that number of
// nanoseconds. The place were we want to be is described by the parameters
// we were passed by the simulator.
//
int64_t nsDrift = DoGetDrift (nsCurrent + nsDelay);
//
// If the drift is positive, we are already late and we need to just bail out
// of here as fast as we can. Return true to indicate that the requested time
// has, in fact, passed.
//
if (nsDrift >= 0)
{
NS_LOG_INFO ("Back from SleepWait: IML8 " << nsDrift);
return true;
}
//
// There are some number of nanoseconds left over and we need to wait until
// the time defined by nsDrift. We'll do a SpinWait since the usual case
// will be that we are doing this Spinwait after we've gotten a rough delay
// using the SleepWait above. If SpinWait completes to the end, it will
// return true; if it is interrupted by a signal it will return false.
//
NS_LOG_INFO ("SpinWait until " << nsCurrent + nsDelay);
return SpinWait (nsCurrent + nsDelay);
}
void
WallClockSynchronizer::DoSignal (void)
{
NS_LOG_FUNCTION_NOARGS ();
m_condition.SetCondition (true);
m_condition.Signal ();
}
void
WallClockSynchronizer::DoSetCondition (bool cond)
{
NS_LOG_FUNCTION_NOARGS ();
m_condition.SetCondition (cond);
}
void
WallClockSynchronizer::DoEventStart (void)
{
NS_LOG_FUNCTION_NOARGS ();
m_nsEventStart = GetNormalizedRealtime ();
}
uint64_t
WallClockSynchronizer::DoEventEnd (void)
{
NS_LOG_FUNCTION_NOARGS ();
return GetNormalizedRealtime () - m_nsEventStart;
}
bool
WallClockSynchronizer::SpinWait (uint64_t ns)
{
NS_LOG_FUNCTION_NOARGS ();
//
// Do a busy-wait until the normalized realtime equals the value passed in
// or the condition variable becomes true. The condition becomes true if
// an outside entity (a network device receives a packet, sets the condition
// and signals the scheduler it needs to re-evaluate).
//
// We just sit here and spin, wasting CPU cycles until we get to the right
// time or are told to leave.
//
for (;;)
{
if (GetNormalizedRealtime () >= ns)
{
return true;
}
if (m_condition.GetCondition ())
{
return false;
}
}
// Quiet compiler
return true;
}
bool
WallClockSynchronizer::SleepWait (uint64_t ns)
{
NS_LOG_FUNCTION_NOARGS ();
//
// Put our process to sleep for some number of nanoseconds. Typically this
// will be some time equal to an integral number of jiffies. We will usually
// follow a call to SleepWait with a call to SpinWait to get the kind of
// accuracy we want.
//
// We have to have some mechanism to wake up this sleep in case an external
// event happens that causes a schedule event in the simulator. This newly
// scheduled event might be before the time we are waiting until, so we have
// to break out of both the SleepWait and the following SpinWait to go back
// and reschedule/resynchronize taking the new event into account. The
// SystemCondition we have saved in m_condition takes care of this for us.
//
// This call will return if the timeout expires OR if the condition is
// set true by a call to WallClockSynchronizer::SetCondition (true) followed
// by a call to WallClockSynchronizer::Signal(). In either case, we are done
// waiting. If the timeout happened, we TimedWait returns true; if a Signal
// happened, false.
//
return m_condition.TimedWait (ns);
}
uint64_t
WallClockSynchronizer::DriftCorrect (uint64_t nsNow, uint64_t nsDelay)
{
int64_t drift = DoGetDrift (nsNow);
//
// If we're running late, drift will be positive and we need to correct by
// delaying for less time. If we're early for some bizarre reason, we don't
// do anything since we'll almost instantly self-correct.
//
if (drift < 0)
{
return nsDelay;
}
//
// If we've drifted out of sync by less than the requested delay, then just
// subtract the drift from the delay and fix up the drift in one go. If we
// have more drift than delay, then we just play catch up as fast as possible
// by not delaying at all.
//
uint64_t correction = (uint64_t)drift;
if (correction <= nsDelay)
{
return nsDelay - correction;
}
else
{
return 0;
}
}
uint64_t
WallClockSynchronizer::GetRealtime (void)
{
//
// I originally wrote this code to use CLOCK_PROCESS_CPUTIME_ID. In Linux
// this is a highly accurate realtime clock. It turns out, though, that this
// is a Linux bug. This is supposed to be (posix says it is) a highly
// accurate cumulative running time of the process. In Linux it is (or at
// least was -- a bug is filed) a highly accurate wall-clock time since the
// process was created. Posix defines the wall clock you must use as the
// CLOCK_REALTIME clock. As you can see in the constructor, the resolution
// of the CLOCK_REALTIME clock is a jiffy. This is not fine-grained enough
// for us. So, I'm using the gettimeofday clock even though it is an
// expensive call.
//
// I could write some inline assembler here to get to the timestamp counter
// (RDTSC instruction). It's not as trivial as it sounds to get right.
// Google for "rdtsc cpuid" to see why. I'm leaving this for another day.
//
// N.B. This returns the value of the realtime clock. This does not return
// the current normalized realtime that we are attempting to make equal to
// the simulation clock. To get that number, use GetNormalizedRealtime ().
//
#if 0
// This will eventually stop working in linux so don't use it
struct timespec tsNow;
clock_gettime (CLOCK_REALTIME, &tsNow);
return TimespecToNs (&tsNow);
#endif
struct timeval tvNow;
gettimeofday (&tvNow, NULL);
return TimevalToNs (&tvNow);
}
uint64_t
WallClockSynchronizer::GetNormalizedRealtime (void)
{
return GetRealtime () - m_realtimeOriginNano;
}
void
WallClockSynchronizer::NsToTimespec (int64_t ns, struct timespec *ts)
{
NS_ASSERT ((ns % US_PER_NS) == 0);
ts->tv_sec = ns / NS_PER_SEC;
ts->tv_nsec = ns % NS_PER_SEC;
}
void
WallClockSynchronizer::NsToTimeval (int64_t ns, struct timeval *tv)
{
NS_ASSERT ((ns % US_PER_NS) == 0);
tv->tv_sec = ns / NS_PER_SEC;
tv->tv_usec = (ns % NS_PER_SEC) / US_PER_NS;
}
uint64_t
WallClockSynchronizer::TimespecToNs (struct timespec *ts)
{
uint64_t nsResult = ts->tv_sec * NS_PER_SEC + ts->tv_nsec;
NS_ASSERT ((nsResult % US_PER_NS) == 0);
return nsResult;
}
uint64_t
WallClockSynchronizer::TimevalToNs (struct timeval *tv)
{
uint64_t nsResult = tv->tv_sec * NS_PER_SEC + tv->tv_usec * US_PER_NS;
NS_ASSERT ((nsResult % US_PER_NS) == 0);
return nsResult;
}
void
WallClockSynchronizer::TimespecAdd (
struct timespec *ts1,
struct timespec *ts2,
struct timespec *result)
{
result->tv_sec = ts1->tv_sec + ts2->tv_sec;
result->tv_nsec = ts1->tv_nsec + ts2->tv_nsec;
if (result->tv_nsec > (int64_t)NS_PER_SEC)
{
++result->tv_sec;
result->tv_nsec %= NS_PER_SEC;
}
}
void
WallClockSynchronizer::TimevalAdd (
struct timeval *tv1,
struct timeval *tv2,
struct timeval *result)
{
result->tv_sec = tv1->tv_sec + tv2->tv_sec;
result->tv_usec = tv1->tv_usec + tv2->tv_usec;
if (result->tv_usec > (int64_t)US_PER_SEC)
{
++result->tv_sec;
result->tv_usec %= US_PER_SEC;
}
}
}; // namespace ns3

View File

@@ -0,0 +1,204 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2008 University of Washington
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef WALL_CLOCK_CLOCK_SYNCHRONIZER_H
#define WALL_CLOCK_CLOCK_SYNCHRONIZER_H
#include "ns3/system-condition.h"
#include "synchronizer.h"
namespace ns3 {
/**
* @brief Class used for synchronizing the simulation events to a real-time
* "wall clock" using Posix Clock functions.
*
* Enable this synchronizer using:
*
* DefaultValue::Bind ("Synchronizer", "WallClockSynchronizer");
*
* before calling any simulator functions.
*
* The simulation clock is maintained as a 64-bit integer in a unit specified
* by the user through the TimeStepPrecision::Set function. This means that
* it is not possible to specify event expiration times with anything better
* than this user-specified accuracy.
*
* There are a couple of more issues at this level. Posix clocks provide
* access to several clocks we could use as a wall clock. We don't care about
* time in the sense of 0430 CEST, we care about some piece of hardware that
* ticks at some regular period. The most accurate posix clock in this
* respect is the CLOCK_PROCESS_CPUTIME_ID clock. This is a high-resolution
* register in the CPU. For example, on Intel machines this corresponds to
* the timestamp counter (TSC) register. The resolution of this counter will
* be on the order of nanoseconds.
*
* Now, just because we can measure time in nanoseconds doesn't mean we can
* put our process to sleep to nanosecond resolution. We are eventualy going
* to use the function clock_nanosleep () to sleep until a simulation Time
* specified by the caller.
*
* MORE ON JIFFIES, SLEEP, PROCESSES, etc., as required
*
* Nanosleep takes a struct timespec as an input so we have to deal with
* conversion between Time and struct timespec here. They are both
* interpreted as elapsed times.
*/
class WallClockSynchronizer : public Synchronizer
{
public:
WallClockSynchronizer ();
virtual ~WallClockSynchronizer ();
static const uint64_t US_PER_NS = (uint64_t)1000;
static const uint64_t US_PER_SEC = (uint64_t)1000000;
static const uint64_t NS_PER_SEC = (uint64_t)1000000000;
protected:
/**
* @brief Return true if this synchronizer is actually synchronizing to a
* realtime clock. The simulator sometimes needs to know this.
*
* @internal
*
* Subclasses are expected to implement this method to tell the outside world
* whether or not they are synchronizing to a realtime clock.
*
* @returns True if locked with realtime, false if not.
*/
virtual bool DoRealtime (void);
/**
* @brief Retrieve the value of the origin of the underlying normalized wall
* clock time in nanosecond units.
*
* @internal
*
* Subclasses are expected to implement this method to do the actual
* real-time-clock-specific work of getting the current time.
*
* @returns The normalized wall clock time (in nanosecond units).
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
*/
virtual uint64_t DoGetCurrentRealtime (void);
/**
* @brief Establish a correspondence between a simulation time and a
* wall-clock (real) time.
*
* @internal
*
* There are three timelines involved here: the simulation time, the
* (absolute) wall-clock time and the (relative) synchronizer real time.
* Calling this method makes a correspondence between the origin of the
* synchronizer time and the current wall-clock time.
*
* This method is expected to be called at the "instant" before simulation
* begins. At this point, simulation time = 0, and synchronizer time is
* set = 0 in this method. We then associate this time with the current
* value of the real time clock that will be used to actually perform the
* synchronization.
*
* Subclasses are expected to implement this method to do the actual
* real-time-clock-specific work of making the correspondence mentioned above.
* for example, this is where the differences between Time parameters and
* parameters to clock_nanosleep would be dealt with.
*
* @param ns The simulation time we need to use as the origin (normalized to
* nanosecond units).
*/
virtual void DoSetOrigin (uint64_t ns);
/**
* @brief Declaration of method used to retrieve drift between the real time
* clock used to synchronize the simulation and the current simulation time.
*
* @internal
*
* @param ns Simulation timestep from the simulator normalized to nanosecond
* steps.
* @returns Drift in nanosecond units.
* @see TimeStepPrecision::Get
* @see Synchronizer::SetOrigin
* @see Synchronizer::GetDrift
*/
virtual int64_t DoGetDrift (uint64_t ns);
/**
* @brief Wait until the real time is in sync with the specified simulation
* time.
*
* @internal
*
* This is where the real work of synchronization is done. The Time passed
* in as a parameter is the simulation time. The job of Synchronize is to
* translate from simulation time to synchronizer time (in a perfect world
* this is the same time) and then figure out how long in real-time it needs
* to wait until that synchronizer / simulation time comes around.
*
* Subclasses are expected to implement this method to do the actual
* real-time-clock-specific work of waiting (either busy-waiting or sleeping,
* or some combination) until the requested simulation time.
*
* @param ns The simulation time we need to wait for (normalized to nanosecond
* units).
* @see TimeStepPrecision::Get
*/
virtual bool DoSynchronize (uint64_t nsCurrent, uint64_t nsDelay);
virtual void DoSignal (void);
virtual void DoSetCondition (bool cond);
virtual void DoEventStart (void);
virtual uint64_t DoEventEnd (void);
bool SpinWait (uint64_t);
bool SleepWait (uint64_t);
uint64_t DriftCorrect (uint64_t nsNow, uint64_t nsDelay);
uint64_t GetRealtime (void);
uint64_t GetNormalizedRealtime (void);
void NsToTimespec (int64_t ns, struct timespec *ts);
void NsToTimeval (int64_t ns, struct timeval *tv);
uint64_t TimespecToNs (struct timespec *ts);
uint64_t TimevalToNs (struct timeval *tv);
void TimespecAdd(
struct timespec *ts1,
struct timespec *ts2,
struct timespec *result);
void TimevalAdd (
struct timeval *tv1,
struct timeval *tv2,
struct timeval *result);
uint64_t m_cpuTick;
uint64_t m_realtimeTick;
uint64_t m_jiffy;
uint64_t m_nsEventStart;
SystemCondition m_condition;
};
}; // namespace ns3
#endif /* WALL_CLOCK_SYNCHRONIZER_H */

View File

@@ -22,7 +22,7 @@ def configure(conf):
else:
conf.env['USE_HIGH_PRECISION_DOUBLE'] = 0
highprec = '128-bit integer'
conf.check_message_custom('high precision time', 'implementation', highprec)
conf.check_message_custom('high precision time','implementation',highprec)
e = conf.create_header_configurator()
e.mandatory = False
@@ -59,8 +59,11 @@ def build(bld):
'event-impl.cc',
'simulator.cc',
'default-simulator-impl.cc',
'realtime-simulator-impl.cc',
'timer.cc',
'watchdog.cc',
'synchronizer.cc',
'wall-clock-synchronizer.cc',
]
headers = bld.create_obj('ns3header')
@@ -73,6 +76,7 @@ def build(bld):
'simulator.h',
'simulator-impl.h',
'default-simulator-impl.h',
'realtime-simulator-impl.h',
'scheduler.h',
'list-scheduler.h',
'map-scheduler.h',
@@ -81,6 +85,8 @@ def build(bld):
'timer.h',
'timer-impl.h',
'watchdog.h',
'synchronizer.h',
'wall-clock-synchronizer.h',
]
env = bld.env_of_name('default')