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
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:
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:
-
A process control
block (pcb) for the new procedure
(really the new process) is created
and put on the "next event list",
and
-
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 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.
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).
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*/
....
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*/
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 */
....
}
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*/
...
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.
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.
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 */
...
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*/
....
...
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 */
...
}
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*/
...
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
const long BUFFER_AMT
= 100;
buffer *buf;
buff = new buffer("buff",
BUFFER_AMT);
amt - random(1, BUFFER_AMT);
buff->get(amt);
buff->put(amt);
st = buff->timed_get(amt,
1.0);
if(st != TIMED_OUT) {
buff->put(amt);
} else {
}
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.
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 */
...
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*/
...
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 */
...
....
...
st = ev->timed_wait(50.0); /*wait
for a maximum of 50 time units */
if(st ! = TIMED_OUT) { /*did not timed
out */
.....
}
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.
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.
...
st = mb->timed_receive(&msg_r,
100.0); /*wait for a maximum of 100
time units */
if(st ! = TIMED_OUT) { /*if not timed
out */
....
}
const long NUM_MBOXES
= 25;
mailbox_set *mbox_arr;
. . . .
mbox_arr = new mailbox_set("mbox
set", NUM_MBOXES);
i = mbox_arr->receive_any(&msg);
(*mbox_arr)[3].send(msg);
st = mbox_arr->timed_receive_any(&msg,
1.0);
if(st != TIMED_OUT) {
// process message
} else {
// deal with time out
}
|