PSI sics-cvs-psi_pre-ansto
This commit is contained in:
804
doc/programmer/oguide.tex
Normal file
804
doc/programmer/oguide.tex
Normal file
@@ -0,0 +1,804 @@
|
||||
\chapter{Guide To SICS Object Writers}
|
||||
This chapter describes the necessary steps for adding new objects to the SICS server. In order to live
|
||||
happily within SICS an object is obliged to stick to a set of rules and interfaces already defined. Such
|
||||
interfaces will be described here as well. For the following text it is assumed, that the reader has studied
|
||||
the SICS overview and kernel guide.
|
||||
|
||||
In general the following steps will be necessary in order to introduce a new object into SICS:
|
||||
\begin{enumerate}
|
||||
\item Define a new object data structure.
|
||||
\item Define a function capable of deleting the object from memory.
|
||||
\item Define a set of functions which define the operations on this object.
|
||||
\item Create an object wrapper function which defines the user commands
|
||||
the object can handle.
|
||||
\item Create an object creation function, which is capable of initializing
|
||||
the object and creates a new command in the SICS interpreter.
|
||||
\item Add the new objects creation command into the IniInitCommand and
|
||||
KillIniCommands functions defined in ofac.c.
|
||||
\item Document the new object.
|
||||
\end{enumerate}
|
||||
All these steps will now be discussed together with some additional SICS
|
||||
concepts applicable to writing new objects. Not all of the above mentioned
|
||||
steps will be necessary in all cases. If a new general SICS object is defined,
|
||||
no object creation function may be needed. Then it is sufficient to register
|
||||
the new object/command with the SICS interpreter in IniInitCommands, file ofac.c.
|
||||
In the rare cases where an object needs no data structure, the system will provide a dumb
|
||||
default which will not harm the system. Some objects may have more then one
|
||||
command associated with them. Some more care has to be taken when adding a
|
||||
new piece of hardware into the system. If it is just a variant of an already
|
||||
known type (for instance a new type of motor) it will be sufficient to introduce
|
||||
a new driver into the system. The procedures for doing this will be described
|
||||
in the reference sections for the appropriate device. Otherwise the SICS rule
|
||||
to divide a hardware object into a logical object and a driver should be
|
||||
observed. At minimum two drivers are necessary anyway in the first place:
|
||||
the driver for the actual device and a simulation driver which permits some
|
||||
debugging to take place while the hardware is still unavailable.
|
||||
|
||||
\section{Mapping Object Oriented Concepts into ANSI--C}
|
||||
SICS is in principle an object oriented system. However, it is implemented
|
||||
in ANSI--C. Therefore object oriented concepts must be mapped into C. The
|
||||
three object oriented concepts which need to be mapped are:
|
||||
\begin{itemize}
|
||||
\item Data Encapsulation.
|
||||
\item Polymorphism.
|
||||
\item Inheritance.
|
||||
\end{itemize}
|
||||
|
||||
Of these, data encapsulation is by far the most important concept. Objects
|
||||
in computer science can be understood as little boxes which contain some
|
||||
data describing their state and which understand messages sent by other
|
||||
objects. If such a message comes in, the object performs some action,
|
||||
perhaps changes its internal state or sends new messages to other objects.
|
||||
It is understood that changes to the internal data of the object can be
|
||||
achieved only through messages to the object and not by directly manipulating
|
||||
variables from the outside. In ANSI--C an object maps to a structure holding
|
||||
the objects data and the messages map to functions which act upon the data
|
||||
structure. In order to do this, the functions must take a pointer to the
|
||||
objects data structure as first parameter. In order to prevent messing with
|
||||
an objects data structure, only a pointer to a structure is declared in the
|
||||
header file. The actual definition of the data structure happens only in
|
||||
the implementation file. All functions belonging to an object are defined in that
|
||||
implementation file and have full access to the data structure.
|
||||
Users of the object see only the header file and thus only a pointer to the
|
||||
objects data structure which prevents
|
||||
them from messing with the objects data directly.
|
||||
In order to illustrate the concepts lets look at
|
||||
a primitive integer object defined in such a way.
|
||||
\begin{verbatim}
|
||||
/*-----------------------------------------------------------------------
|
||||
ExampleInt.h
|
||||
------------------------------------------------------------------------*/
|
||||
typedef struct __ExampleInt *pExampleInt;
|
||||
|
||||
int GetInt(pExampleInt self);
|
||||
void SetInt(pExampleInt self, int iNew);
|
||||
/*------------------- EOF ExampleInt.h---------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------------------
|
||||
ExampleInt.c, Implementation file
|
||||
-----------------------------------------------------------------------*/
|
||||
typedef struct __ExampleInt {
|
||||
int iExample;
|
||||
} ExampleInt;
|
||||
/*--------------------------------------------------------------------*/
|
||||
int GetInt(pExampleInt self)
|
||||
{
|
||||
return self->iExample;
|
||||
}
|
||||
/*---------------------------------------------------------------------*/
|
||||
void SetInt(pExampleInt self, int iNew)
|
||||
{
|
||||
self->iExample = iNew;
|
||||
}
|
||||
\end{verbatim}
|
||||
Using this scheme all code changing the internal state of an object lives
|
||||
in one file. Changes to the objects data structure affect only the
|
||||
implementation file and no other files.
|
||||
This scheme is used for almost all SICS objects. A few system objects and
|
||||
older SICS objects define their data structures in header files. This is
|
||||
either a relic or had to be done for performance reasons.
|
||||
|
||||
The next concept is polymorphism. This describes the situation when a group
|
||||
of objects respond to the same message but doing different things. For
|
||||
instance a whole set of objects would implement a write functionality
|
||||
which writes the objects state to a file. Higher level would then not need
|
||||
to know of which type the actual object is, it just can send the write message
|
||||
and the rest is taken care of by the object. This concept is used for all
|
||||
hardware drivers in SICS. Mapping this to C requires to expose the objects
|
||||
data structure and let the data structure include a pointer to that polymorphic
|
||||
function. As an example, the ExampleInteger with a write function:
|
||||
\begin{verbatim}
|
||||
/*-----------------------------------------------------------------------
|
||||
ExampleInt.h
|
||||
------------------------------------------------------------------------*/
|
||||
typedef struct __ExampleInt{
|
||||
int iExample;
|
||||
void (*write)(struct __ExampleInt *self, FILE *fd);
|
||||
} *pExampleInt, ExampleInt;
|
||||
|
||||
pExampleInt MakeInt(int iNew);
|
||||
|
||||
int GetInt(pExampleInt self);
|
||||
void SetInt(pExampleInt self, int iNew);
|
||||
/*------------------- EOF ExampleInt.h---------------------------------*/
|
||||
|
||||
/*----------------------------------------------------------------------
|
||||
ExampleInt.c, Implementation file
|
||||
-----------------------------------------------------------------------*/
|
||||
static void ExampleWrite(struct _-ExampleInt *self, FILE *fd)
|
||||
{
|
||||
fprintf(fd,"INT = %d",self->iExample);
|
||||
}
|
||||
/*---------------------------------------------------------------------*/
|
||||
pExampleInt MakeInt(int iNew)
|
||||
{
|
||||
pExampleInt pNew = NULL;
|
||||
|
||||
pNew = (pExampleInt)malloc(sizeof(ExampleInt));
|
||||
pNew->iExample = iNew;
|
||||
pNew->write = ExampleWrite;
|
||||
return pNew;
|
||||
}
|
||||
.
|
||||
.
|
||||
.
|
||||
\end{verbatim}
|
||||
This can then be called:
|
||||
\begin{verbatim}
|
||||
void SomeFunc()
|
||||
{
|
||||
pExampleInt pTest;
|
||||
|
||||
pTest = MakeInt(25);
|
||||
.
|
||||
.
|
||||
.
|
||||
pTest->write(pTest,fd);
|
||||
}
|
||||
\end{verbatim}
|
||||
This example also illustrates the concept of a special function which creates
|
||||
a new object of the appropriate type and initializes its data structure
|
||||
properly.
|
||||
|
||||
The last concept to discuss is inheritance. Inheritance can be used when
|
||||
an object is a derivative of another object. For instance a truck is a
|
||||
derivative of a motor car. Much of the behavior of a motor car will be the
|
||||
same as for a truck. In order to prevent rewriting of code, the truck
|
||||
should use the same data structures and code as the motor car. And add
|
||||
or modify only what is special. Inheritance is not much used in SICS. It can
|
||||
be implemented by overlaying data structures. This means the derived
|
||||
classes data structure has the same fields in the same order as the parent
|
||||
class and adds its specials at the end. For example:
|
||||
\begin{verbatim}
|
||||
typedef struct __MotorCar {
|
||||
int iWheels;
|
||||
float fSpeed;
|
||||
} *pMotorCar, MotorCar;
|
||||
|
||||
|
||||
typedef struct __Truck {
|
||||
int iWheels;
|
||||
float fSpeed; /* same as MotorCar */
|
||||
double dPayLoad; /* special for Truck */
|
||||
} *pTruck, Truck;
|
||||
|
||||
\end{verbatim}
|
||||
Thus functions defined for motor car can operate on trucks as well.
|
||||
For more details study the relationship between the ITC4 controller and
|
||||
general environment controllers. This is the only place where SICS
|
||||
currently uses inheritance.
|
||||
|
||||
|
||||
\section{The Object Data Structure}
|
||||
Nearly all SICS objects require some data. This data is collected in a
|
||||
C structure typical for the object. This is just a normal C struct with one
|
||||
exception:
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 1}}
|
||||
The first item in a SICS object data structure MUST be a pointer to a
|
||||
ObjectDescriptor data structure. This looks like:
|
||||
\begin{verbatim}
|
||||
typedef struct __MyObject {
|
||||
pObjectDescriptor pDes;
|
||||
int iMyExtremlyImportantInt;
|
||||
.
|
||||
.
|
||||
.
|
||||
} MyObject;
|
||||
\end{verbatim}
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
|
||||
Otherwise the SICS server will crash on you sooner or later. The reason for
|
||||
this is that SICS needs a means to identify a object and its capabilities
|
||||
from inside its code. For example a user has typed: {\em drive mot1 26} into
|
||||
the server. Now drive needs to verify that mot1 is really a motor or
|
||||
something else which can be driven. This can be done in the following way:
|
||||
\begin{enumerate}
|
||||
\item First find the object in the SICS interpreter
|
||||
(function FindCommand, SCinter.h).
|
||||
\item Get the pointer to the objects data structure.
|
||||
\item Assign this pointer to a dummy data structure (defined in obdes.h)
|
||||
which has a pointer to the ObjectDescriptor as single element.
|
||||
\item Ask the ObjectDescriptor if the object can be driven.
|
||||
\end{enumerate}
|
||||
In object speak this is called run time type information (RTTI).
|
||||
|
||||
It is interesting to look at the object descriptor in more detail. It is
|
||||
again a data structure and a good example for polymorphism:
|
||||
\begin{verbatim}
|
||||
typedef struct {
|
||||
char *name;
|
||||
int (*SaveStatus)(void *self, char *name,FILE *fd);
|
||||
void *(*GetInterface)(void *self, int iInterfaceID);
|
||||
} ObjectDescriptor, *pObjectDescriptor;
|
||||
|
||||
\end{verbatim}
|
||||
The first field contains a string defining a type name. The next field is a
|
||||
pointer to a save function. This function is automatically called for all objects
|
||||
in the SICS interpreter when the server shuts down. This function is supposed to
|
||||
write all SICS commands necessary to reconfigure the object back into its current state
|
||||
into file fd. The middle parameter is the name of the object in the interpreter
|
||||
which must not necessarily be known to the object.
|
||||
|
||||
The next field, a pointer to a function GetInterface is used in order to inquire
|
||||
the capabilities of an object. Object capabilities in SICS are defined in terms of
|
||||
{\bf {\Large Interfaces}}. An Interface in the SICS sense is again a data structure
|
||||
which contains data fields and pointers to functions implementing the capability. As
|
||||
an example see the Drivable interface which is implemented by motors, environment
|
||||
devices and variables such as lambda which can be driven to a new value:
|
||||
\begin{verbatim}
|
||||
typedef struct {
|
||||
int ID;
|
||||
int (*Halt)(void *self);
|
||||
int (*CheckLimits)(void *self, float fVal,
|
||||
char *error, int iErrLen);
|
||||
long (*SetValue)(void *self, SConnection *pCon,
|
||||
float fVal);
|
||||
int (*CheckStatus)(void *self, SConnection *pCon);
|
||||
float (*GetValue)(void *self, SConnection *pCon);
|
||||
} IDrivable, *pIDrivable;
|
||||
|
||||
\end{verbatim}
|
||||
Each SICS interface is identified by an integer ID number (defined in interface.h).
|
||||
GetInterface returns a pointer to an interface data structure for an ID if the object
|
||||
implements that interface. If not a NULL is returned.
|
||||
|
||||
\section{SICS Interfaces}\label{gow}
|
||||
Thus SICS interfaces define the capabilities of an object. The concept of
|
||||
Interfaces is similar to interfaces in the Java programming language
|
||||
or abstract base classes in C++ or other object oriented languages.
|
||||
As can be seen from the example
|
||||
given above, the interface implements the most common operations on an object with
|
||||
capabilities as defined by the interface. Currently, SICS knows four different kinds of
|
||||
Interfaces:
|
||||
\begin{description}
|
||||
\item[Drivable] The Drivable interface for objects such as motors, adjustable parameters
|
||||
and the like. Anything which can be driven to a value.
|
||||
\item[Countable] The Countable interface used for everything which can count: single
|
||||
counters, histogram memories or whatever may creep up.
|
||||
\item[CallBack] The CallBack interface introduces some component programming techniques
|
||||
into SICS. This technique lets an object, name it object A, issue events when its internal
|
||||
state changes. For example if object A is a motor, any time it drives to a new value
|
||||
an event is issued. Then another object, name it object B, can register an interest in
|
||||
such events with object A. It does so by giving A the address of a function to call
|
||||
when such an event occurs. A now calls all functions thus registered when the event
|
||||
occurs. Object B thus gets automatically notified when A changes its state. Currently
|
||||
this scheme is only used for implementing automatic notifications of clients when a
|
||||
SICS object changes its state. But there is a lot of potential in this scheme.
|
||||
\item[Environment] The Environment device is a interface implemented by all devices which
|
||||
need to be regularly monitored by the environment monitor.
|
||||
\end{description}
|
||||
For more details about the SICS interfaces see the reference section: \ref{interref}.
|
||||
The list of SICS interfaces can be extended when a novel abstract capability of an object
|
||||
is needed. This is also the main reason why this sophisticated scheme was chosen.
|
||||
|
||||
The question may arise how to use SICS interfaces. For example consider an object defining
|
||||
a drivable variable. The following steps are necessary:
|
||||
\begin{enumerate}
|
||||
\item Define functions with matching parameters to the ones requested by the interface.
|
||||
Tip: the first parameter, the pointer to void, is always the pointer to the object data
|
||||
structure to operate on. These functions must implement appropriate actions. What these
|
||||
are: see the documentation of the interface.
|
||||
\item Define a GetInterface function which returns a suitable interface
|
||||
data structure when asked for an interface with the Drivable ID. The fields of the interface
|
||||
structure returned must be set to the addresses of the functions you defined in step 1.
|
||||
\item Create an object initialization function.
|
||||
\item In this object initialization function: assign the object descriptors filed GetInterface
|
||||
to the address of your GetInterface function.
|
||||
\end{enumerate}
|
||||
Usually, the interfaces an object implements are made a field in the objects data
|
||||
structure. This interface is then initialized with appropriate function pointers when
|
||||
creating the object. GetInterface then returns this pointer when the interface is
|
||||
requested. Due to this usage a rule exits:
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 2}}
|
||||
Any interface data structure provided by an object belongs to the object providing
|
||||
it. The owner object is responsible for deleting it. Never free an interface data structure
|
||||
provided by another object.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
|
||||
Note, that the SICS interfaces are not the only interfaces to take care of. As SICS uses
|
||||
this duality between logical hardware objects and hardware drivers, the interfaces defined
|
||||
by the logical hardware devices can been seen as yet another internal interface. This leads to:
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Recommendation}}
|
||||
Watch your neighbors! Use the interfaces provided by the logical hardware objects for
|
||||
your objects business, when applicable.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
|
||||
\section{The Object Deletion Function}
|
||||
When the SICS server exits or when a command is removed from the SICS
|
||||
interpreter the interpreter will call a function which should free the memory
|
||||
associated with the deleted object. This is absolutely necessary in order to
|
||||
prevent memory leakage. Such a function has the form:
|
||||
\begin{verbatim}
|
||||
void ObjectKiller(void *pData);
|
||||
\end{verbatim}
|
||||
with pData being the pointer to the object to delete.
|
||||
|
||||
|
||||
\section{Writing Object Functions}
|
||||
The rules specified in this section apply to all kinds of object functions:
|
||||
those which implement functionality as well as the object wrapper
|
||||
functions. Please note, that any SICS object has two interfaces: an internal
|
||||
C--language interface and a user visible interface defined by the object
|
||||
wrapper functions. There are reasons why those two interfaces might not
|
||||
provide equivalent functionality.
|
||||
|
||||
\subsection{Input and OutPut}
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 3}}
|
||||
All input and output to the client executing the command must go through the
|
||||
connection object functions SCWrite and SCPrompt.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
These function will now be inspected in more detail:
|
||||
\begin{verbatim}
|
||||
int SCWrite(SConnection *pCon, char *pText, int eCode);
|
||||
int SCPrompt(SConnection *pCon, char *pPrompt, char *pBuffer, int iBufLen);
|
||||
\end{verbatim}
|
||||
SCWrite writes the data pText to the connection specified by pCon. The
|
||||
parameter eCode denotes the output code of the data in pText. SICS clients
|
||||
can choose to suppress some I/O from the SICS server. For instance a
|
||||
GUI--client might chooses not to receive scan status reports. For this
|
||||
purpose it was necessary to stick an output code onto each message to the
|
||||
client. Possible output codes are: eError, eWarning, eValue, eStatus and
|
||||
some internal codes. The names are self explaining. eValue denotes a value
|
||||
which has been explicitly asked for by the client. The rule specified above
|
||||
becomes understandable and thus bearable when looking at all the things
|
||||
SCWrite does with the message:
|
||||
\begin{itemize}
|
||||
\item It is written to the client connection socket, subject to the output
|
||||
code specified.
|
||||
\item The message is written to all log files configured for the client
|
||||
connection.
|
||||
\item The message is written to the server log together with the socket
|
||||
number of the connection.
|
||||
\item SCWrite stores the message into the Tcl macro interpreter in order to
|
||||
enable processing of data from SICS commands in Tcl scripts.
|
||||
\item SCWrite suppresses all messages to the client while executing a macro.
|
||||
This stops spurious output to appear at the client connection when running a
|
||||
command defined in the macro language. The exception are messages of type
|
||||
eError and eWarning. Such messages are always sent to the client.
|
||||
\end{itemize}
|
||||
|
||||
SCPrompt prompts the user at the client connection for data. The prompt
|
||||
string pPrompt is sent. Data entered by the user is returned in buffer
|
||||
pBuffer. Maximum iBufLen character are returned. While waiting for client to
|
||||
provide data, the SICS task switcher runs.
|
||||
|
||||
There is another convenience function SCSendOK(SConnection *pCon) which is
|
||||
just a wrapper around SCWrite. SCSendOk sends an 'OK' to the client. It is good
|
||||
practice to let the user know that the operation requested had been
|
||||
performed.
|
||||
|
||||
Given the rule stated above, it follows that connection objects have to
|
||||
be passed through the system to nearly everywhere. Especially where
|
||||
reportable errors are found.
|
||||
|
||||
All other I/O, for instance to files sitting at the computer running the
|
||||
SICS server, is not subjected to Rule 3.
|
||||
|
||||
|
||||
\subsection{Error Handling}
|
||||
Error handling is two thirds of the code written. Errors can be caused by
|
||||
faulty users or faulty hardware. In any case, the first thing to do is:
|
||||
report it using SCWrite! The next thing is to get the system into a state
|
||||
that it can continue execution. This is particularly important for a server
|
||||
program which might run for months on end. This means, free all memory used
|
||||
before the error occurred, get system variables into safe values and the
|
||||
like.
|
||||
|
||||
Sometimes error conditions arise in lower level code which should cause all
|
||||
upper level code to finish execution. Such conditions may be the result of a
|
||||
critical hardware fault or may even be requested by a user who wants to
|
||||
abort an operation. A standard method for communicating such conditions
|
||||
through the system is necessary. SICS uses interrupts for such conditions.
|
||||
The current interrupt active interrupt is located at the connection object
|
||||
and can be retrieved with {\bf SCGetInterrupt} and set with {\bf
|
||||
SCSetInterrupt}. Interrupt codes are defined in interrupt.h and are ordered
|
||||
into a hierarchy:
|
||||
\begin{description}
|
||||
\item[eContinue] Everything is just fine.
|
||||
\item[eAbortOperation] Stop the current scan point or whatever is done,
|
||||
but do not stop altogether.
|
||||
\item[eAbortScan] Abort the current scan, but continue processing of further
|
||||
commands in R\"unbuffers or command files.
|
||||
\item[eAbortBatch] Aborts everything, operations, scans and batch processing
|
||||
and leaves the system ready to enter new commands.
|
||||
\item[eHaltSystem] As eAbortBatch, but lock the system.
|
||||
\item[eFreeSystem] Unlocks a system halted with eHaltSystem.
|
||||
\item[eEndServer] Makes the SICS server run down and exit.
|
||||
For internal usage only.
|
||||
\end{description}
|
||||
|
||||
In most cases interrupts shall only effect the client connection executing
|
||||
the command. In such cases user code should use SCSetInterrupt in order to
|
||||
set an interrupt at the connection executing the command only. Very rarely
|
||||
conditions occur when an interrupt should affect the whole system. Then
|
||||
SetInterrupt (defined in intserv.h) should be used. SetInterrupt forwards
|
||||
the interrupt to all running tasks.
|
||||
|
||||
Objects implementing higher level commands such as scan commands might be
|
||||
able to handle interrupt conditions and thus consume an interrupt. Then the
|
||||
interrupt must be released with SCSetInterrupt and setting the interrupt to
|
||||
eContinue. The interrupt on a connection will automatically be set to
|
||||
eContinue when executing a new command from the command stack.
|
||||
|
||||
Objects are responsible for checking for interrupt conditions when
|
||||
applicable. This is specially true for objects performing complex or
|
||||
lengthy
|
||||
operations with hardware. Whenever a hardware operation finishes, the
|
||||
interrupt must be checked for and handled. The called hardware object or
|
||||
the user might have set an interrupt which needs to be honored.
|
||||
|
||||
Thus two new rules can be defined:
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 4}}
|
||||
Signal critical error conditions with interrupts.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 5}}
|
||||
Check for pending interrupts wherever applicable.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
|
||||
\subsection{Authorisation}
|
||||
Objects are also responsible for checking if the client requesting an
|
||||
operation may actually perform the operation. There are two cases when an
|
||||
operation may be forbidden:
|
||||
\begin{itemize}
|
||||
\item The user is not suitably authorized.
|
||||
\item The user is about to change critical parameters affecting a running
|
||||
measurement.
|
||||
\end{itemize}
|
||||
Both conditions must be checked for by the objects code. Where else then at
|
||||
the object can be known which operations are critical or not? This gives a
|
||||
new rule:
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 6}}
|
||||
Objects have to check authorisation!
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
|
||||
The user rights code for a client connection can be retrieved with
|
||||
SCGetRights. SCGetGrab finds out if a connection has the control token.
|
||||
A match with a value can be checked for with SCMatchRights which
|
||||
returns true (1) if the connection has a user rights equal or higher then the
|
||||
one necessary. SCMatchRights also checks for the status of the control token
|
||||
and prints messages. SCMatchRights is the recommended function for checking
|
||||
access rights in SICS.
|
||||
|
||||
An object can find out if some hardware operation is running by calling
|
||||
the function isInRunMode prototyped in devexec.h.
|
||||
|
||||
An object can find out, if it is executing in a macro by calling SCinMacro.
|
||||
Might be useful if the code is dependent on this condition. Usually all
|
||||
details associated with running in a macro are dealt with by the system.
|
||||
|
||||
\subsection{Driving Hardware}
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 7}}
|
||||
Use the device executor!
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
With respect to hardware, there are two different kinds of objects in SICS:
|
||||
some objects represent hardware. Examples are motors, counters. This group
|
||||
also includes drivable variables which implement coordinated movements of
|
||||
hardware. Such objects have to implement the necessary interfaces: either
|
||||
the drivable or the countable interface. The second class are objects which
|
||||
implement commands which use the hardware. For instance a drive command.
|
||||
This second class of commands has to find the necessary hardware objects and
|
||||
then run the actual hardware through the device executor. The function to
|
||||
use is StartDevice or its derivatives, prototyped in devexec.h. After this
|
||||
is done, there are tow options. In a scan command it might be needed to wait for
|
||||
the hardware to finish before continuing your code. In this case use
|
||||
function TaskWait, prototyped in tasker.h.
|
||||
In other cases execution of the function may just
|
||||
continue. The device executor will the take care of the task of monitoring
|
||||
the hardware operation. The following code snippet gives an example how to
|
||||
start a counter properly and wait for it to finish:
|
||||
\begin{verbatim}
|
||||
/*-------------- count */
|
||||
pDum = (pDummy)self->pCounterData;
|
||||
iRet = StartDevice(pServ->pExecutor, /* the executor, from global pServ */
|
||||
"ScanCounter", /* a name */
|
||||
pDum->pDescriptor, /* the descriptor of the cter */
|
||||
self->pCounterData, /* pointer to counter data */
|
||||
self->pCon, /* pointer to connection object */
|
||||
self->fPreset); /* count preset */
|
||||
if(!iRet)
|
||||
{
|
||||
SCWrite(self->pCon,"ERROR: Cannot Count, Scan aborted",eError);
|
||||
return 0;
|
||||
}
|
||||
/* wait for finish */
|
||||
lTask = GetDevexecID(pServ->pExecutor); /* get ID of device
|
||||
executor task */
|
||||
if(lTask > 0);
|
||||
{
|
||||
TaskWait(pServ->pTasker,lTask);
|
||||
}
|
||||
|
||||
/* finished, check for interrupts. Whatever happened, user
|
||||
interrupt or HW interrupt, it will be on our connection
|
||||
*/
|
||||
iInt = SCGetInterrupt(self->pCon);
|
||||
switch(iInt)
|
||||
{
|
||||
case eContinue:
|
||||
break;
|
||||
case eAbortOperation:
|
||||
continue;
|
||||
break;
|
||||
case eAbortScan:
|
||||
SCWrite(self->pCon,"ERROR: Scan aborted",eError);
|
||||
/* eat the interrupt, the requested op has been
|
||||
done
|
||||
*/
|
||||
SCSetInterrupt(self->pCon,eContinue);
|
||||
return 0;
|
||||
break;
|
||||
default: /* all others */
|
||||
SCWrite(self->pCon,"ERROR: Scan aborted",eError);
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
\end{verbatim}
|
||||
The above code (taken from the implementation of the scan command) also
|
||||
shows an example for handling interrupts as a followup to running hardware.
|
||||
Such a procedure is highly recommended.
|
||||
|
||||
\subsection{Working with the CallBack Interface}
|
||||
Working with the callback interface is usually simple: just use the
|
||||
functions as defined in interface.h. Add new event codes to event.h, in
|
||||
order to make them known. A problem arises if an object which has callbacks
|
||||
registered with other objects gets deleted. For the following discussion:
|
||||
let us denote the object registering the callback as the client object and
|
||||
the object which generates the event (where the callback was registered) as
|
||||
source object. A typical example for a client is a
|
||||
connection object which had configured itself to be automatically notified
|
||||
of motor movements, variable changes and the like. Typically this is a
|
||||
status display client. In such cases the client object has to keep
|
||||
track of the callbacks it registered and delete them when the client objects gets
|
||||
deleted. Otherwise the source object would invoke a callback on an non
|
||||
existing client object. This usually results in a lovely core dump. For the
|
||||
connection object this is taken care of automatically. The only thing needed
|
||||
to be done, is to register the callback on the connection object with
|
||||
SCRegister prototyped in conman.h. If user codes removes a callback later on,
|
||||
it may call SCUnregister on the connection object to remove it from the
|
||||
connection objects callback database as well. All registered callback will
|
||||
be properly removed when the connection object is deleted.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 8}}
|
||||
Remove registered callbacks when deleting an object. For connection objects:
|
||||
register your callbacks!
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
|
||||
|
||||
\section{The Object Wrapper Function}
|
||||
The object wrapper function makes the SICS object visible as a command in
|
||||
the interpreter. It has to analyze parameters given to the command and act
|
||||
accordingly. The object wrapper function has the following form:
|
||||
\begin{verbatim}
|
||||
int ObjectWrapper(SConnection *pCon, SicsInterp *pSics, void *pData,
|
||||
int argc, char *argv[]);
|
||||
\end{verbatim}
|
||||
The parameters are:
|
||||
\begin{itemize}
|
||||
\item A pointer to the connection object and the client invoking the command.
|
||||
\item A pointer to the SICS interpreter in which the command was executed.
|
||||
As the interpreter also doubles as a database for objects it is often needed
|
||||
to locate other objects.
|
||||
\item A pointer to the objects data structure (pData).
|
||||
\item The number of arguments specified to the command.
|
||||
\item The text of the arguments specified. \verb+argv[0]+ is the name of the
|
||||
command invoked. The argc, \verb+argv[]+ are almost the same as the argc,
|
||||
\verb+argv[]+ pair given to a C main program.
|
||||
\end{itemize}
|
||||
In order to give the macro interpreter a hint if things are okay, the object
|
||||
wrapper function has to return 1 for success and 0 if it fails. The rules
|
||||
stated above for normal object functions apply.
|
||||
|
||||
\section{The Object Creation Function}
|
||||
The object creation function has the same form as the object wrapper
|
||||
function. Its purpose is to analyze parameters and to create the requested
|
||||
object from them. In the end the object creation function will probably
|
||||
create a new command in the SICS interpreter. The function to do this is the
|
||||
AddCommand function. The prototype for AddCommand looks like this:
|
||||
\begin{verbatim}
|
||||
int AddCommand(SicsInterp *pSics,
|
||||
char *name,
|
||||
ObjectFunc pObFunc,
|
||||
KillFunc pKill,
|
||||
void *pData);
|
||||
\end{verbatim}
|
||||
The parameters are:
|
||||
\begin{itemize}
|
||||
\item A pointer to the SICS interpreter into which the new command shall be
|
||||
installed.
|
||||
\item The name of the new command.
|
||||
\item The object function implementing the command.
|
||||
\item An optional function which is able to remove the object data structure
|
||||
given as last parameter from memory. This is mandatory for any object with
|
||||
an own data structure.
|
||||
\item A pointer to the object data structure.
|
||||
\end{itemize}
|
||||
|
||||
\section{Writing Data}
|
||||
For simple ASCII files at instruments doing scans, see the SICS manager
|
||||
documentation for the template file description. Other instruments should
|
||||
use the NeXus data format. Especially the NXDICT implementation. For more
|
||||
documentation see the napi and nxdict documents. An example for using NXDICT
|
||||
is given in the file nxsans.c.
|
||||
|
||||
\section{Hardware Objects}
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
\leftline{{\huge Rule 9}}
|
||||
Obey the division between driver code and logical object for hardware
|
||||
objects.
|
||||
\begin{center}
|
||||
\rule{9cm}{2mm}
|
||||
\end{center}
|
||||
The concept of separating a hardware object in two components: a driver
|
||||
which implements primitive operations and a logical object with which SICS
|
||||
interacts internally has proven very valuable. First of all two drivers are
|
||||
needed anyway: a simulation driver for simulating hardware and a
|
||||
driver for the real hardware. Simulating hardware proved invaluable for
|
||||
debugging of higher level code and can be used later on for testing command
|
||||
files.
|
||||
|
||||
Please note that sample environment devices are handled slightly
|
||||
differently. There exists a more general sample environment object which
|
||||
handles many aspects of such a device. If the logical interface provided by
|
||||
this general sample environment objects suffices, a new sample environment
|
||||
device requires just a new driver. If the sample environment needs some
|
||||
specials the study the relationship between the itc4 object and the general sample
|
||||
environment object. Basically such a derived object implements in its
|
||||
functions just the specials and calls the appropriate functions of the
|
||||
general environment device object at the end. This is a simple form of
|
||||
inheritance by delegation. This scheme has been devised in order to reduce
|
||||
the need for code duplication when introducing new environment devices. For
|
||||
more details see the reference section on environment devices.
|
||||
|
||||
\section{Examples}
|
||||
For a deeper understanding of the concepts explained above it may help to
|
||||
look at some source code. A nice example for the whole SICS object setup
|
||||
including a callback interface is the implementation of the SICS variables
|
||||
in files sicsvar.h and sicvar.c. For the implementation of a hardware object
|
||||
including a drivable interface, the code in motor.h, motor.c and the motor
|
||||
drivers: simdriv.c, el734dc.c and el734driv.c may be a suitable example. The
|
||||
motor object creation function also shows how to handle multiple drivers for
|
||||
a logical object.
|
||||
SICS knows a omega-two-theta variable o2t. This variable coordinates two
|
||||
motors in a way that the second motor has exactly the double value of the
|
||||
first. This was meant to implement a omega-two-theta scan. Anyway, o2t is
|
||||
a good example for implementing a drivable interface and how to handle such
|
||||
coordinated movements. An example for the implementation of a countable
|
||||
interface can be found in counter.h, counter.c for a single counter.
|
||||
|
||||
\section{Documentation}
|
||||
If a new object has been defined two documentation files should be provided:
|
||||
\begin{itemize}
|
||||
\item A html file which describes the user interface to the object.
|
||||
\item A latex file which describes the data structures used to implement the
|
||||
object, the interface to the object and as much internal knowledge about the
|
||||
working of the object as you care to provide. Being generous with
|
||||
information helps maintaining the code later on!
|
||||
\end{itemize}
|
||||
Furthermore it is required to update the SICS managers documentation so that
|
||||
the object creation function is known.
|
||||
|
||||
\section{Introducing a new Hardware Driver}
|
||||
A new hardware driver for an existing objects can be introduced into the
|
||||
system with the following two steps:
|
||||
\begin{itemize}
|
||||
\item Write the new driver. Use an existing driver as template.
|
||||
\item Locate the objects factory function. Modify it so that it knows about
|
||||
the new driver and how to initialise it.
|
||||
\item Depending of the implementation of the object, the object deletion
|
||||
function may need to know about the new driver as well in order to delete it
|
||||
properly.
|
||||
\end{itemize}
|
||||
Do not forget to update the SICS manager documentation to include the new
|
||||
driver!
|
||||
|
||||
\section{Coding Considerations}
|
||||
The header file {\em sics.h} includes all the SICS kernel header files in
|
||||
the right order. It is recommended to include sics.h at the top of
|
||||
your implementation file.
|
||||
|
||||
A server program has to run for times on end. Thus a server program is more
|
||||
sensitive than any other program to memory leakage or memory overwrites.
|
||||
Therefore the current SICS implementation utilizes a memory debugging tool
|
||||
called fortify. It is highly recommended to include fortify.h into your
|
||||
implementation file in order to enable memory debugging. More information
|
||||
about the inner working and usage of fortify can be found in the file fortify.doc in
|
||||
the sics source code directory. Fortify has given some false alarms already
|
||||
but it also helped to eliminate a couple of nasty problems at a very early
|
||||
stage. A nasty feature of fortify is that its header file, fortify.h, has
|
||||
to be included in ALL relevant source files. Otherwise nasty pseudo bugs
|
||||
creep up when fortify is enabled. Please note, that fortify is only enabled
|
||||
when all source files have been compiled with the flag -DFORTIFY. Otherwise
|
||||
fortify compiles to nothing. More information about fortify can be found in
|
||||
file fortify.h.
|
||||
|
||||
Names in the SICS source code are often made up along the following pattern:
|
||||
\begin{itemize}
|
||||
\item Use the full name of the thing or sensible and command abbreviations.
|
||||
Write the variable as one word, with word boundaries being marked by capital
|
||||
letters.
|
||||
\item Most variables are prefixed with a letter denoting type. Letters used
|
||||
are:
|
||||
\begin{itemize}
|
||||
\item p for pointer.
|
||||
\item i for integer.
|
||||
\item f for float.
|
||||
\item d for double.
|
||||
\item l for long.
|
||||
\item s for struct.
|
||||
\end{itemize}
|
||||
Combinations of letters are also possible.
|
||||
\end{itemize}
|
||||
|
||||
Some more general data structure implementations are used in SICS: There is
|
||||
a general linked list package from a dutch men documented in lld.h. There is
|
||||
a dynamical array in dynar.* and a parameter array in obpar.*.
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user