Mesquite Software

Home
Products and FAQ
Customers
To Order
Contact Us
Site Map
Documentation
News & Events

Purchase & Download
Or, log in here to
access your account.
 
Documentation
User Guide: C++ : CSIM 19 Tutorial (C++ Version), Part 1

Introduction

This document gives an overview of all of the important objects and other features of the CSIM 19 library. A CSIM+ model is a C++ (or C) program that uses the functions and procedures in the CSIM 19 library to implement process-oriented, discrete-event simulation models. Each model will mimic the behavior of the system being modeled. Using a model helps the user analyze the behavior of a system and can lead to improvements in the operation and performance of that system.

It is assumed that the reader of this document has a working knowledge of the C++ programming language and is familiar with the concepts of discrete-event simulation model

Processes

A CSIM process is used to model the active elements of a system. These could include elements of the workload, clients and servers found in the system, and other components that are active parts of the system model. In CSIM, a process is a C++ procedure that executes the "create()" statement. Every time a "create" statement is executed, a new instance of that process is created. Each instance of a CSIM process has the following attributes:

  • Its own internal state (local variables and registers)
  • A unique process id
  • A process priority
  • One of the following external states:
    • Executing
    • Waiting-to-execute
    • Holding (while some period of time elapses)
    • Waiting (for some event to occur)

CSIM processes should not be confused with processes in the platform operating system (such as UNIX) or operating system supported threads (such as lightweight threads in SunOS). The concepts are similar, but the implementations are separate.

It is very important to understand the flow of control that is used by CSIM processes. When a newly called procedure executes a create() statement, the following actions occur:

  1. A process control block (pcb) for the new procedure (really the new process) is created and put on the "next event list", and
  2. Control is returned immediately to the process which invoked this new process.

So, after a new process is "called", the old process is still executing and the new process will execute only after the current (old) process "gives up" (e,g., does a hold or a statement which results in a wait).

For example, after this CSIM code fragment has executed:

...
for(i = 0; I < 100; I++)
customer(i);
hold(1000.0);
...


100 instances of the process named "customer" will be created, but none of them will start to execute until the calling process executes the hold statement.

Facilities

Facilities are those objects which processes "use" or occupy. They can be defined as:

  • A single server facility (can only service one process at a time)
  • A multi-server facility (can service n processes at once, where n is the number of servers defined for the facility)
  • An array of single server facilities

Each facility is given a name, which is used solely for output (reports, status, and traces).

By default, a facility services processes in priority order. Where multiple processes have the same priority, they will be served on a first-come-first-served basis. A number of other service disciplines can be specified.

The following examples show how a facility can be used from within a process.

  • To declare, initialize, and use a facility with a single queue and a single server:

facility *single_server; /* declare facility variable/
...
single_server = new facility("sngle srvr"); /* initialize facility named sngle srvr*/
...
single_server->use(service_time); /* use facility for length of service_time*/
...
single_server->reserve(); /* reserve (use) facility */
...
hold(service_time);
...
single_server->release(); /* release the facility */
...


The "use()" statement and the sequence "reserve(), hold(), release()" behave in similar ways; the only difference is when (in simulated time) the service_time variable is evaluated. By convention, the "use()" statement is used when the process will be "using" the facility, while the "reserve" statement is used when the process will acquire exclusive use of the facility and then do something other than a "hold" statement.

Processes are ranked in the queue of waiting processes in order of their process priorities, with the highest priority at the head of the list. In the case of equal priorities, the process doing the earliest reserve is ahead of processes doing reserves at later points in time. If all reserving processes have the same priorities, then the resulting scheduling policy (discipline) is first-come, first-served (or FIFO - first in, first out).

  • To declare, initialize, and use a facility with a single queue and three servers:

const long NUM_SRVRS = 3; /* set number of servers to 3 */
facility_ms *multi_server; /* declare facility_ms object ptr */
...
multi_server = new facility_ms("multi srvr", NUM_SRVRS);/*initialize srvr with 3 srvrs*/
...
multi_server->use(service_time); /*use facility for length of service_time*/
....

  • To declare, initialize, and use an array of ten single server facilities:

const long NUM_FACS = 10; /*set number of facilities in array to 10 */
facility_set *facs; /* declare facility array */
...
facs = new facility_set("facs", NUM_FACS); /*initialize set of 10 facilities */
i = random(0, NUM_FACS-1); /*select the facility to be used next*/
(*facs)[i].use(service_time); /*use facility[i] for length of service_time*/

  • To reserve a facility only if it can be obtained within a given length of time:

const double TIME_OUT = 5.0; /*set length of time to wait for facility*/
.....
st = single_server->timed_reserve(TIME_OUT); /*reserve facility in 5 time units*/
if(st != TIMED_OUT) { /*if facility was, in fact, reserved in time*/
hold(service_time); /*simulate servicing customer for service_time*/
single_server->release(); /*release facility since service is now complete*/
}
else { /*request timed out */
....
}

  • To declare, initialize, and use a synchronous facility (a synchronous facility is one in which reserves are granted only at regular points in time (called clock ticks)):

const double PHASE = 0.5; /*set time to onset of first clock cycle to 0.5 */
const double PERIOD = 1.0; /*set length of clock cycle to 1 time unit*/
faciolity *bus; /*declare facility variable bus */
...
bus = new facility("bus"); /*initialize facility and name it bus */
bus->synchronous(PHASE, PERIOD); /*make the facility synchronous */
...
reserve(bus); /*reserve the facility */
...
release(bus); /*release facility since process no longer needs it*/
...

  • To define the preempt-resume service discipline for a facility:

FACILITY cpu; /*declare facility variable cpu */
...
cpu = fnew acility("cpu"); /*initialize facility and name it cpu */
cpu->set_servicefunc(pre_res) /*set service protocol to preempt-resume*/
...
priority = 100; /*make process high priority */
cpu->use(service_time); /*preempt lower priority process and use facility*/
...

It is important to notice that when scheduling disciplines other than first-come, first-served are in use at a facility, then the "use()" method is the only way to make use of the facility. This means that a process cannot reserve such a facility and then do something other than use that facility.

Storages

A CSIM storage is a resource which can be partially allocated to a requesting process. A storage consists of a counter (to indicate the amount of available storage) and a queue for processes waiting to receive their requested allocation. A storage set is an array of these basic storages.

A storage can be designated to be synchronous. In a synchronous storage, each allocate is delayed until the onset of the next clock cycle.

Each storage must be given a name, which is used solely for output (reports, status and traces).

The following examples show how storage can be used from within a process.

  • To declare, initialize, and use a storage:

const long STORE_AMT = 100; /*set amount of storage to 100 units */
storage *mem; /*declare storage variable mem */
...
mem = new storage("mem", STORE_AMT);/*initialize storage named mem with 100 units */
...
amt = random(1, STORE_AMT); /*decide how much storage to allocate this time */
mem->allocate(amt); /*get amount of storage decided upon*/
...
mem->deallocate(amt); /* release storage which is no longer needed */
...

  • To declare, initialize, and use an array of five storage blocks:

const long NUM_STORES = 5; /*set number of storage blocks in array*/
const long STORE_AMT = 100; /*set amount of storage in each storage block*/
storage _set *mems; /*declare storage block array*/
...
mems = new storage_set("mem", STORE_AMT, NUM_STORES); /*initialize stor mem with 100 units per block*/
...
amt = random(1, STORE_AMT); /*decide how much storage to allocate */
(*mems)[3].allocate(amt); /*get storage from the fourth storage block*/
...
(*mems)[3].deallocate(amt); /*release storage which is no longer needed*/
....

  • To get storage only if it can be obtained within a given length of time:

...
st = mem->timed_allocate(amt, 1.0); /*get storage if possible within 1 time unit */
if(st != TIMED_OUT) { /*if storage was gotten within the time limit */
...
mem->deallocate(amt); /*release storage which is no longer needed */
}
else { /* allocate timed out */
...
}

  • To declare, initialize, and use storage synchronously (allocations will take place only at regular points in time (called clock ticks)).

const double PHASE = 0.5; /*set time to onset of first clock cycle to 0.5*/
const double PERIOD = 1.0; /*set length of clock cycle to 1 time unit*/
const long STORE_AMT = 100; /*set amount of storage in block to 100 units*/
storage *mem; /*declare storage variable mem */
mem = new storage("mem", STORE_AMT);/*initialize storage named mem with 100 units*/
mem->synchronous(PHASE, PERIOD); /*make storage allocations synchronous*/
...
mem->allocate(5); /*get 5 units of storage for this process */
...
mem->deallocate(5); /*release storage which is no longer needed*/
...

Buffers

A CSIM buffer is a resource which can be partially allocated to a requesting process. A buffer consists of a counter (to indicate the amount of available capacity, represented as tokens, in the buffer) and a two queues, one for processes waiting to get their requested allocation of space and one for processes waiting for space to put (or return) tokens to the buffer.

Each buffer must be given a name, which is solely used for output (reports, status and traces).

The following examples show how a buffer can be used from within a process

  • To declare, initialize and use a buffer

const long BUFFER_AMT = 100;
buffer *buf;

buff = new buffer("buff", BUFFER_AMT);

amt - random(1, BUFFER_AMT);
buff->get(amt);

buff->put(amt);

  • To get space in a buffer only if it can be obtained with a given length of time:

st = buff->timed_get(amt, 1.0);
if(st != TIMED_OUT) {

buff->put(amt);
} else {

}

Events

Events are used to synchronize and control interactions between different processes. A CSIM event has two states: occurred (OCC) and not occurred (NOT_OCC). A process can either wait for an event to occur or queue on the event.

If a process "waits" for an event:

  • If the event is in the not occurred state, the process is suspended and placed in a queue of processes waiting for the event to happen (occur). When some other process does a "set" operation on that event, all of the waiting processes are re-activated (allowed to proceed) and the event is reset to not occurred.
  • If the event is in the occurred state, the process continues to execute and the event state is changed to not occurred.

If a process queues on an event:

  • If the event is in the not occurred state, the process, is suspended and placed in a queue of processes queued for the event to happen (occur). When some other process does a "set" operation on that event, only the first queued process is re-activated (allowed to proceed) and the event is reset to not occurred.
  • If the event is in the occurred state, and there are no other processes queued on that event, the process continues to execute and the event state is changed to not occurred.

Events can be defined as either an individual event or an array of events.
Each event must be given a name, which is used solely for output (status and traces).

The following examples show how events can be used from within a process.

  • To declare, initialize, and use an event:

event *ev; /*declare event variable ev */
...
ev = new event("ev"); /*initialize an event named ev */
....
ev->wait(); /*wait for event to occur before proceeding */
...
ev->queue(); /*wait for event to occur, for processes to respond before proceeding*/
ev->set(); /*indicate that an event has occurred */
...

  • To declare, initialize, and use an array of twenty-five events:

const long NUM_EVENTS = 25; /*set number of events in array */
event_set *ev_arr; /*declare event array */
....
ev_arr = new event_set("ev arr", NUM_EVENTS); /*initialize array of 25 events*/
....
(*ev_arr)[5].wait(); /*wait for sixth event to occur before proceeding */
...
(*ev_arr)[5].set(); /*indicate that sixth event has occurred*/
...

  • To wait or queue for any event in an array of events to happen:

i = ev_arr -.wait_any(); /*i is index of event which occurred */
OR
i = ev_arr->queue_any(); /*i is index of event which occurred */
...
....

  • To wait for an event only if it occurs within a given length of time:

...
st = ev->timed_wait(50.0); /*wait for a maximum of 50 time units */
if(st ! = TIMED_OUT) { /*did not timed out */
.....
}

Mailboxes

A mailbox allows for the synchronous exchange of data between CSIM processes. Any process may send a message to any mailbox, and any process may attempt to receive a message from any mailbox.

A mailbox is comprised of two FIFO queues: a queue of unreceived messages and a queue of waiting processes. At least one of the queues will be empty at any time. When a process sends a message, the message is given to a waiting process (if one exists) or it is placed in the message queue. When a process attempts to receive a message, it is either given a message from the message queue (if one exists) or it is added to the queue of waiting processes.

A message can be either a single integer or a pointer to some other data object. If a process sends a pointer, it is the responsibility of that process to maintain the integrity of the referenced data until it is received and processed.

Each mailbox must be given a name, which is used solely for output (status and traces).

The following examples show how mailboxes can be used from within a process.

  • To declare, initialize, and use a mailbox:

mailbox *mb; /*declare mailbox variable mb */

long msg_r,msg_s; /*message variables */
...
mb = new mailbox("mb"); /*initialize a mailbox named mb */
...
mb->receive(&&msg_r); /*receive message (in msg_r) from mailbox mb */
...
mb->send(mb, msg_s); /*send message in msg_s to mailbox mb */
...

A message is a single variable. It can be either an integer or a pointer to a (message) structure.

  • To wait for a message only if it comes in within a given length of time:

...
st = mb->timed_receive(&msg_r, 100.0); /*wait for a maximum of 100 time units */
if(st ! = TIMED_OUT) { /*if not timed out */
....
}

  • To declare, initialize and use an array of twenty-five mailboxes:

const long NUM_MBOXES = 25;
mailbox_set *mbox_arr;
. . . .
mbox_arr = new mailbox_set("mbox set", NUM_MBOXES);

  • To receive a message from any mailbox in an array of mailboxes:

i = mbox_arr->receive_any(&msg);

  • To send a message to a mailbox which is member of an array of mailboxes

(*mbox_arr)[3].send(msg);

  • To receive a message from any mailbox in an array of mailboxes within a specified interval of time:

st = mbox_arr->timed_receive_any(&msg, 1.0);
if(st != TIMED_OUT) {
// process message
} else {
// deal with time out
}

 
Home | Products/FAQ | Customers | To Order | Contact Us | Site Map | Documentation
© copyright 2005, Mesquite Software, all rights reserved.