ProSim4: Process Simulation 4


ProSim4: Process Simulation 4

1. What is ProSim4

ProSim4 is a general purpose Process Simulation system. I learned about a cool simulation language while I was a student in Technical University of Delft. I have designed ProSim4 around this simulation language and added features that I need to do network based simulation (features like time out processing). The ProSim4 system was initially written in August 1989.

There are 2 kinds of discrete event simulation systems:

In event based simulation systems, you write one function that corresponds to one type event and schedule events. Since there are many types of events, the result of using event based simulation is usually a large number of functions. Therefore, we consider this type of simulation system is very low leveled.

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.

2. Intro to ProSim4

In ProSim4, you will use the C programming language to describe the behavior of a simulation entity. For example, here is a server entity that waits for clients and server them:
   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->Name
to obtain the Generate string value (this value will be the string passed into Generate plus an index).

3. ProSim4 System Data Structures

There are 2 main data structures used in ProSim4 that a user must know in order to understand the relationship between the various functions. The user usually do not need to access the individual members, but the user may need to access their values to help debug their programs.

3.1 Process Control Block (PCB)

Each ProSim4 entity has some system information stored in its struct PCB or PCBType (typedef) data structure. A ProSim4 entity can access its own PCB structure by using the MYSELF pointer variable. For example:
	MYSELF->ArrivalTime
The following variables are system variables that are necessary for the functioning of ProSim4:
There is one variable in the PCB to facilitate unconventional simulation activities: The following variable is mainly to help you in developing simulation programs (bugs are very tough to find in process simulation...) These variables allow you to pass values from client to server and back: In the summer of 1999, I started to work on a network simulation package to help students get a quick start in network simulation projects. I added the following features:
  1. I found that having active ProSim4 entities to represent messages to be too heavy-weight.... I added passive messages (struct ProSim4Message).

  2. Timeout !
As a result, I added a TimeOut server (which is a ProSim4 entity, see timeout.c) and the following data structures to the PCB to ProSim4 to support the NetSim4 network simulation package: Finally, the fillowing is a hardware dependent structure that is needed to implement co-routines on which the ProSim4 system is built:

3.2 ProSim4 Messages

This is the message structure that I added in the summer of 1999 to NetSim4 with a support lighter weight - passive - message:
   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.

4. ProSim4 System Constants and Variables

4.1. System Constants

These are the system-defined values that have special meaning to the ProSim4 system. Becareful how you use them when you assign it to the MYSELF->State variable in the PCB !!!

4.2. System Variables

5. ProSim4 Program Structure

A ProSim4 simulation program has the following structure:
	#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...
	}

6. ProSim4 Primitives

6.1. System Initialization

6.2. Thread Creating/Destruction Primitives

6.3. Thread Pausing, Passivation and ReActivation

6.4. Queueing Functions

6.5. Randomization Functions

ProSim4 provides a set of frequently used random number generation functions. There are NUM_STREAMS independent streams of random numbers available (currently, NUM_STREAMS==10).

6.6. Send and Receive (Light Weight) Messages

6.7. Timeout

It is a little bit involved to use the ProSim4 timeout facility, because I have included capabilities to cancel a timeout request. Let me first describe the primitives for doing time out request and cancellation and then describe how to use them.
How to use ProSim4's timeout request:
  1. In main():
       InitProsim(.., .., ..);
       InitTimeOutServer(MAX entities requesting timeout);
    
  2. In each simulation entity that requests 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 */
    

    6.8. Abnormal Abort

    When ProSim4 detects that has reached certain system limits, it will print an error message and call the "OnAbort()" function. ProSim4 provides a dummy OnAbort() function for the user. But if you like to print out values of some internal variables when the system aborts, you can provide your own OnAbort() function.

    7. Debugging Simulation Programs

    Since ProSim4 manipulates the system stack, the use of a debugger if of limited effectiveness. You still can use dbx or gdb, but you will only be able to see the variables of the current active thread - variables of other threads (which are waiting to be executed) cannot be examined. The best way is to structure your program modularly and use a lot of "printf()" calls.

    8. Examples Simulation Programs

    9. Getting ProSim4

    You need to get the following 2 files to install ProSim4:

    X. Implementation of ProSim4

    ProSim4 is built on top of the functions NewProcess() and Transfer() in the sys4.c file which provides an efficient threading system:

    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:

    1. Note: The PC of the thread t1 is already save on the stack because of a procedure call to Transfer() You don't have to save that again.
    2. Save all registers of the first process on his stack. Then save the stack pointer in the process's PCB.
    3. Switch stack. This is accomplished by putting the stack pointer value obtained from the second PCB into the SP register.
    4. Pass control to the second process. This is accomplished by popping all registers back and do a return, which will pop the Program counter, so doing a jump into the code of the second process (recall that the PC was pushed by the process implicitly when it calls Transfer. So this return will make the second process to resume from where it has left off when it did a Transfer call.
    All is well if you have the stack set up. Now, what if the process is not started yet. How do you start it ? The answer is: you fake an initial stack that is exactly the same as when the process did a transfer call. This is the job of NewProcess(). You will see that NewProcess pokes some addresses in the bottom of workspace. It is setting up an initial stack that looks exactly the same as when the process has been halted due to a call to Transfer. For instance, the return address poked is the start address of the procedure. So when a return instruction is executed, it will jump right to the start of the procedure. This is how to make a function into a thread.