From 3ba8dce95486f5ca70e6491c372886eb3164a8e6 Mon Sep 17 00:00:00 2001 From: Craig Dowell Date: Tue, 26 Aug 2008 15:34:57 -0700 Subject: [PATCH] add realtime simulator and associated tests --- examples/realtime-udp-echo.cc | 125 ++++ examples/wscript | 4 + regression/tests/test-realtime-udp-echo.py | 12 + src/simulator/default-simulator-impl.cc | 4 + src/simulator/event-impl.cc | 15 + src/simulator/event-impl.h | 109 ++- src/simulator/realtime-simulator-impl.cc | 794 +++++++++++++++++++++ src/simulator/realtime-simulator-impl.h | 153 ++++ src/simulator/synchronizer.cc | 158 ++++ src/simulator/synchronizer.h | 334 +++++++++ src/simulator/wall-clock-synchronizer.cc | 490 +++++++++++++ src/simulator/wall-clock-synchronizer.h | 204 ++++++ src/simulator/wscript | 8 +- 13 files changed, 2405 insertions(+), 5 deletions(-) create mode 100644 examples/realtime-udp-echo.cc create mode 100644 regression/tests/test-realtime-udp-echo.py create mode 100644 src/simulator/realtime-simulator-impl.cc create mode 100644 src/simulator/realtime-simulator-impl.h create mode 100644 src/simulator/synchronizer.cc create mode 100644 src/simulator/synchronizer.h create mode 100644 src/simulator/wall-clock-synchronizer.cc create mode 100644 src/simulator/wall-clock-synchronizer.h diff --git a/examples/realtime-udp-echo.cc b/examples/realtime-udp-echo.cc new file mode 100644 index 000000000..6c78c82cb --- /dev/null +++ b/examples/realtime-udp-echo.cc @@ -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 +#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."); +} diff --git a/examples/wscript b/examples/wscript index 90c310af0..c5fd61a2c 100644 --- a/examples/wscript +++ b/examples/wscript @@ -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' diff --git a/regression/tests/test-realtime-udp-echo.py b/regression/tests/test-realtime-udp-echo.py new file mode 100644 index 000000000..76ac10b52 --- /dev/null +++ b/regression/tests/test-realtime-udp-echo.py @@ -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") diff --git a/src/simulator/default-simulator-impl.cc b/src/simulator/default-simulator-impl.cc index 6da5db2f7..ac9bd8cdc 100644 --- a/src/simulator/default-simulator-impl.cc +++ b/src/simulator/default-simulator-impl.cc @@ -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. diff --git a/src/simulator/event-impl.cc b/src/simulator/event-impl.cc index 9f9c39346..a5d7bc627 100644 --- a/src/simulator/event-impl.cc +++ b/src/simulator/event-impl.cc @@ -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) { diff --git a/src/simulator/event-impl.h b/src/simulator/event-impl.h index 6d2835d17..a05a5c398 100644 --- a/src/simulator/event-impl.h +++ b/src/simulator/event-impl.h @@ -21,9 +21,67 @@ #define EVENT_IMPL_H #include +#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; } diff --git a/src/simulator/realtime-simulator-impl.cc b/src/simulator/realtime-simulator-impl.cc new file mode 100644 index 000000000..47674d19e --- /dev/null +++ b/src/simulator/realtime-simulator-impl.cc @@ -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 + +NS_LOG_COMPONENT_DEFINE ("RealtimeSimulatorImpl"); + +namespace ns3 { + +NS_OBJECT_ENSURE_REGISTERED (RealtimeSimulatorImpl); + +TypeId +RealtimeSimulatorImpl::GetTypeId (void) +{ + static TypeId tid = TypeId ("ns3::RealtimeSimulatorImpl") + .SetParent () + .AddConstructor () + .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 (); +} + +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 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) +{ + 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 +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(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 &event) +{ + NS_LOG_FUNCTION (time << event); + + // + // This is a little tricky. We get a Ptr passed down to us in some + // thread context. This Ptr 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 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 into another Ptr 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 destructor is run and the ref + // count is decremented to two. When this function returns to the calling + // template function, the Ptr 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 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 &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 &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 + + diff --git a/src/simulator/realtime-simulator-impl.h b/src/simulator/realtime-simulator-impl.h new file mode 100644 index 000000000..2958b58a4 --- /dev/null +++ b/src/simulator/realtime-simulator-impl.h @@ -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 +#include + +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 &event); + EventId ScheduleNow (const Ptr &event); + EventId ScheduleDestroy (const Ptr &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); + Ptr 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 DestroyEvents; + DestroyEvents m_destroyEvents; + uint64_t m_stopAt; + bool m_stop; + bool m_running; + + // The following variables are protected using the m_mutex + Ptr 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 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 */ diff --git a/src/simulator/synchronizer.cc b/src/simulator/synchronizer.cc new file mode 100644 index 000000000..41d68e652 --- /dev/null +++ b/src/simulator/synchronizer.cc @@ -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 () + ; + 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 + + diff --git a/src/simulator/synchronizer.h b/src/simulator/synchronizer.h new file mode 100644 index 000000000..b15c6dd0f --- /dev/null +++ b/src/simulator/synchronizer.h @@ -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 +#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 */ diff --git a/src/simulator/wall-clock-synchronizer.cc b/src/simulator/wall-clock-synchronizer.cc new file mode 100644 index 000000000..2016b357d --- /dev/null +++ b/src/simulator/wall-clock-synchronizer.cc @@ -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 +#include +#include + +#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 + + diff --git a/src/simulator/wall-clock-synchronizer.h b/src/simulator/wall-clock-synchronizer.h new file mode 100644 index 000000000..ff961b6e0 --- /dev/null +++ b/src/simulator/wall-clock-synchronizer.h @@ -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 */ diff --git a/src/simulator/wscript b/src/simulator/wscript index 1b4d92003..a30e30bb3 100644 --- a/src/simulator/wscript +++ b/src/simulator/wscript @@ -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')