There are 2 kinds of discrete event simulation systems:
In process based simulation systems, you write one function for each type of entity in the simulation. The entities schedule their actions themselves using simulation functions. Since there are usually a small number of different types of entities in a simulation, the result of using process based simulation is writing a small number of functions. Another advantage of process based simulation is simplicity. The simulated entity is programmed using very "natural" actions.
void Server(void *param) { void *Client; while (1) { /* -------------------------------------------- Check if there are any clients in the queue -------------------------------------------- */ if (MyQLength() == 0) { Passivate(COMPLETELY, NULL); /* Queue empty, passivate... */ } /* ---------------------------------------- Arriving client will wake up Server() ---------------------------------------- */ Dequeue(HEAD, &Client,NULL,NULL,NULL); /* Remove first client */ Hold(drand48() + 0.2); /* Service delay */ ReActivate(Client, DONE, 0); /* Reactive client */ /* Note: client will receive the "DONE" value when it returns from Hold() */ } }After defining the behavior of the Server entity with the C function above, you can now instantiate one or more Server entities using:
PCBType *myServer; .... myServer = Generate("myServerName", Server, param);This will create a ProSim thread that runs the function Server with the parameter param. The parameter "myServerName" is there for debug purposes during program development. From within any ProSim4 entity, you can use:
MYSELF->Nameto obtain the Generate string value (this value will be the string passed into Generate plus an index).
MYSELF->ArrivalTimeThe following variables are system variables that are necessary for the functioning of ProSim4:
How to use it:
The client waiting for a service from a server will usually call Hold(t) when there is no failure possible. But when the client can "timeout" (get no service within a certain time), it may want to re-try. Client can do:
if ( (Result = Passivate(TimeOut, Param)) == TIMEOUT ) do TimeOut action else do action according the value in Result
How it is implemented:
Result is updated by ReActivate() and is the return value of Passivate() and PassivateUntil(). (See prosim4.c.) The "default" result TIMEOUT (see ProSim4 constants) is returned in Result when Passivate() or PassivateUntil() returns after a timeout.
typedef struct ProSim4Message { double Time; /* Time stamp... (NetSim4 needs it) */ struct ProSim4Message *next; /* For chaining ProSim4 messages */ void *message; /* The actual message... You can link anything here !!! */ } MessageType;The send and receive functions for messages are discussed below: click here.
#include "ProSim4.h" /* ProSim4 header file.... */ /* ------------------------------------------------------------ Write C functions to define behavior of simulation entities ------------------------------------------------------------ */ void Client(void *param) { /* Simulate a client */ .... Terminate(); } void Server(void *param) { /* Simulate a server */ .... Terminate(); } void Generator(void *param) { /* Generates clients at random time intervals (arrival stream) */ while(1) { Hold(some random time); Generate("Client", Client, param); } } /* ------------------------------------------------------ main() creates the simulation entities and waits... ------------------------------------------------------ */ main(int argc, char **argv) { Gen = Generate("ArrivalGenerator", Generator, param1); Serv = Generate("Server", Server, param1); Initialize variables for tallying performance statistics... StartSimulate(warmup-time); /* To get system in a steady state */ Re-Initialize variables for tallying performance statistics... StartSimulate(sim-time); /* To get performance statistics */ Print performance statistics... }
Always call the function InitProsim() before you do any in ProSim4 !!!
After creating all the simulation entities (by using Generate(), click here). the simulation is started by invoking StartSimulate(SimTime). This function must be invoked from within the main() function of the C program.
Make C function ProcName(param) into a ProSim4 co-routine. The name "s" can be retrieved by using MYSELF->Name for debug use.
Terminate a co-routine. Returns the PCB back to the ProSim4 system.
NOTE: there is no primitive in ProSim4 to terminate another thread - too complex to implement (the entity may be a server with clients in the queue...)
Make thread wait T time units before it runs again. The thread will execute again when simulation time is equal to "Now + T".
NOTE: The thread that executed a Hold() call will NOT run until simulation time is "Now+T".
This function is often used to simulate serving a client, see the example above: click here.
This function is similar to Hold(). Make thread wait until the simulation time Now is equal to T before the thread runs again. The thread will execute again when simulation time is equal to "T".
This function - like Hold() -
will also make the thread wait T time units
before it runs again.
The difference between Passivate() and Hold() is
that the thread will run immediately when it is re-activated
(by some other thread using the primitive ReActivate()).
NOTE: If T == COMPLETELY, then the thread will
not run again until it is re-activated by some other thread.
NOTE: you must provide a "reason" for re-activation
in the ReActivate() call that is different than
the default return value TIMEOUT.
The "reason" value is passed as a parameter of ReActivate()
and it is also the return value of Passivate !!!
NOTE: Passivate is also used if you want to
use ProSim4's timeout service.
First, you set the timeout requests using:
This function is often used by a "server" type entity to
wait for the arrivals of clients;
see the example above:
click here.
Works just like Passivate(), except using
time parameter "T-Now".
All notes of Passivate() also apply to
PassivateUntil().
If the thread PCBPtr made a call to
Passivate(T,Param) to stop, the ReActivate()
call will make the thread run immediately.
In addition:
However:
The ReActivate() call has absoultely no effect
if the ProSim4 entity PCBPtr was stopped (paused)
using the Hold() call....
(So you can use ReActivate() on a server entity
that made a Hold() to simulate non-pre-empty
service !!!)
SetTimeOut(Time1,Signature1,&pos); <- sets timeout with cancellation
SetTimeOut(Time2,Signature2,NULL); <- sets timeout with no cancellation
Now you can call:
r = Passivate(COMPLETELY, &out);
If r == TIMEOUTSERVICE, then one of the timeout
values has been reached.
The out (a long variable) will be equal to
one of the values Signature1 or Signature2.
You can use this value to decide what to do next.
Puts the simulation entity that calls the Enqueue() in the queue of the simulation entity Server. The variable pos controls the way you enter the queue (the variable Queue in the PCB) and the value of pos can be one of HEAD or TAIL,
You can pass 3 pointers to your own variables to the Server. What I have in mind is to distinguish "input", "local" and "output" variables. Input variables are read-only and output variables are un-initialized write only variables. The local variables are read/write. But there is nothing in ProSim4 to force you to abide to this policy... If you do not want to pass any pointer, you must pass NULL.
Remove a simulation entity from my queue. The variable pos controls the way the entities are dequeued from the queue (the variable Queue in the PCB). If pos == HEAD, you remove from the start, and if pos == TAIL, you remove from the tail. The FIFO queue is implemented by using pos == TAIL in Enqueue() and pos == HEAD in Dequeue().
Notice that you will pass 3 void** (address of pointer variables) variables. You will obtain the 3 pointer values that was passed in Enqueue(). If you do not want to obtain a pointer for some variable, pass NULL in the corresponding field.
Returns the length of the queue of simulation entity Server.
Returns the length of my own queue.
Re-seed all NUM_STREAMS streams using the value seed.
returns a uniformly distributed random double number in interval (0..1) taken from the stream stream.
returns a uniformly distributed random double number in interval (a..b) taken from the stream stream.
returns a uniformly distributed random long number in interval (a..b) taken from the stream stream.
returns a exponentially distributed random double number with mean of the distribution equal to mean taken from the stream stream.
returns a exponentially distributed random double number with rate equal to rate taken from the stream stream.
NOTE: This function is often used in "Generator"-type simulation entities to generate clients that arrive with an exponential arrival rate (memory-less proporty).
NOTE: Servers with expenential rate service time will also use this function in the simulation.
Puts the "message" m in the MessageQ of the simulation entity ServerPCBPtr. This function will organize MessageQ as FIFO queue. (But you can organize the messages differently in an internal queue after you remove them from the MessageQ).
Remove the "message" at the head of my own MessageQ.
This function will start the ProSim4 timeout server thread. The parameter MAX is the maximum number of ProSim4 entities that can request time out. (To speed up time out processing, the requests are organized into a two-leveled heap. The MAX value is used to allocate memory space for the array containing the heap).
NOTE: if you need to use timeout, you must call InitTimeOutServer(MAX) right after you called InitProsim().
Each ProSim4 entities that want to request time out must call InitTimeOut(MAX_TOs) first. The values MAX_TOs is the maximum number of time out requests that this simulation entity will make. InitTimeOut(MAX_TOs) will allocate space for MAX_TOs timeout request records in the TOHeap of the PCB.
Make a timeout request with expiration time Time. The parameter Sign is a unique "signature" that you may pass to ProSim4 to identify a specific timeout request. This Sign value will be returned by Passivate() upon timeout expiration. (For details of Passivate(): click here.)
The parameter pos is the position of the time out request in the entity's local timeout heap. It is passed in to speed up timeout cancellation (you will need the position to delete quickly from the heap). If you will not need to use the time out cancellation feature, you can set pos==NULL.
InitProsim(.., .., ..); InitTimeOutServer(MAX entities requesting timeout);
InitTimeOut(MAX # timeout request to be made by this entity); .... unsigned short pos; SetTimeOut(Time1, Signature1, &pos); /* Make cancellable request */ .... unsigned short pos; SetTimeOut(Time2, Signature2, NULL); /* Make uncancellable request */ .... CancelTimeOut(pos); /* Cancel request */
Implements a D/U/1 queueing system. There is one server entity and one generator entity in the simulation. The generator entity will client arrivals with constant rate (1 client per time unit). Each client will enqueue itself to the server (to get service). The service time is random, uniform [0.2 - 1.2].
Implements a M/M/2 queueing system. There are 2 server entities and one generator entity in the simulation. The generator entity will client arrivals with exponential rate. Each client will enqueue itself to the both server (to get service) and will only leave when it has receive service from both servers. The service time is exponentially distributed.
is a test program that I made when I added the capability to pass a parameter to the ProSim4 thread in Generate(). This program also tests/illustrates the new return capability of ReActivate()
is a test program for SendMessage_FIFO() and RecvMessage_FIFO().
is a test program for ProSim4's time out facility.
The test programs can be run after you have gotten and installed the ProSim4 library and header file. See below. The accompany Makefile can be obtained here: click here.
The main simulation engine is the "scheduler" and the scheduler is also a thread. User can call the scheduler from the main C program using the StartSimulate primitive defined in prosim4.c. As a user, you don't have to understand the internals of the package, but it will help you write your simulation programs if you do. So let me briefly describe NewProcess and Transfer below.
Each thread in ProSim4 is given a workspace, which is a large array of bytes. The workspace is used for holding the stack (procedure calls & return addresses and local variables of procedures) of the thread. So the workspace is private.
The state of the thread consists of all volatile information, i.e., registers, stack pointer, program pointer, workspace address etc. These info. are stored on the stack (in the workspace). To get to these information, you need a stack pointer. This pointer is stored in the PCB (process control block) of the thread. (See struct PCB in prosim4.h.)
The ProSim4 PCB contains a lot of important information about the simulated entity: arrival time, status (active or waiting), next event time, etc. A thread can be fully determined by giving the address of its PCB, since then the scheduler can determine its workspace and the stack pointer. Using the stack pointer, the scheduler can locate all the register values and program counter of the thread.
To restart the thread where it was halted, the scheduler pops back the saved register values back from the stack. But be careful that there are 2 stacks (2 threads !) involve: one stack from the thread that is going to relinquish control and the second stack is from the thread that will get control. The Transfer(t1,t2) function therefore taks 2 inputs: the 2 PCB's.
The Transfer function perform the following: