| iMatix home page | << | < | > | >> |
SMT Version 2.81 |
The SMT kernel API is aimed mainly at native programs. However, you can also use the event-passing facilities in foreign programs. This can be necessary when interfacing SMT applications to other event-based systems.
This is the complete set of functions supported by the SMT kernel:
The SMT kernel API works with a number of objects - agents, threads, queues - which are defined as C structures. The fields in these structures are the properties of the object. The SMT objects contain private fields, which you should never change or refer to, and public fields, which you are free to change and use. We do not discuss the private fields, except to note that these contain information that is internal to the SMT kernel, or reflect particular implementations that may change.
These are the SMT objects and their public fields:
AGENT /* Agent descriptor */ AGENT *next, *prev /* Doubly-linked list */ NODE methods /* Methods accepted by agent */ NODE queues /* Queues defined for agent */ char *name /* Agent's name */ Bool router /* Default = FALSE */ int priority /* Default = SMT_PRIORITY_NORMAL */ long max_threads /* Default = 0 (no limit) */ long cur_threads /* Current number of threads */ long top_threads /* Max. number threads we had */ long thread_tally /* How many threads created */ long switch_tally /* How many context switches */
An agent defines one program written using the Libero SMT schema. When the generated code initialises, it automatically creates an agent object. You may change the router, priority, and max_threads fields, but not the other fields.
METHOD /* Method descriptor */ METHOD *next, *prev /* Doubly-linked list */ AGENT *agent /* Parent agent descriptor */ char *name /* Name of method */ int priority /* Default = SMT_PRIORITY_NORMAL */ int event_number /* Internal event number */
The start-up code for an agent (in Initialise-The-Program) creates a method for each external event it wants to handle. You may change the priority and event_number fields, but not the other fields.
THREAD /* Thread descriptor */ THREAD *next, *prev /* Doubly-linked list */ QUEUE *queue /* Parent queue descriptor */ long thread_id /* Thread identifier number */ char *name /* Name of thread */ Bool animate /* Animate this thread */ void *tcb /* Thread context block (TCB) */ EVENT *event /* Last-received event */
Any part of the application can create a thread in an agent, if it knows the name of the agent. You may change the animate field, but not the other fields.
QUEUE /* Event queue descriptor */ QUEUE *next, *prev /* Doubly-linked list */ AGENT *agent /* Parent agent descriptor */ NODE events /* Events in queue */ NODE threads /* Threads for queue */ QID qid /* Queue ID descriptor */ int max_events /* Maximum allowed events */ int cur_events /* Current number of events */
The SMT kernel automatically creates event queues as needed. Generally it will create one event queue per thread. When an agent is defined as a router, however, it only creates an event queue for the first thread. You can also create floating event queues. You may change the max_events fields, but not the other fields. Note that each queue has a unique QID; this is the identifier for the queue, and the information you need to send an event to a queue.
EVENT /* Event in queue */ EVENT *next, *prev /* Doubly-linked list */ QUEUE *queue /* Parent queue descriptor */ QID sender /* Replies come back here */ char *name /* Name of event */ size_t body_size /* Size of event body in bytes */ char *body /* Event body */ char *accept_event /* Reply if we accept event */ char *reject_event /* Reply if we reject event */ char *expire_event /* Reply if we expire event */ time_t timeout /* Expires at this time (or 0) */
Any part of the application can send an event to an event queue using the event_send() function. The queue may be served by a thread, or not (if it is a floating event queue). You should not change any of the fields in the event object: the sender, body, and body_size fields are generally interesting.
SEMAPH /* Semaphore definition */ SEMAPH *next, *prev; /* Doubly-linked list */ char *name; /* Name of semaphore */
Semaphores are for synchronising agents, for instance for exclusive access to some resource. We implement general semaphores, i.e. they can take any value of 0 or greater (the value is defined as an integer).
An SMT application must explicitly initialise and shut-down the SMT kernel. This lets the kernel create global objects (such as the symbol table) and then destroy them when finished.
The smt init() function initialises the SMT kernel. The smt term() function shuts-down the SMT kernel. These are generally the first and last SMT functions that the application calls. Usually, we'll put these calls in the stub (main) program.
Any part of the application can call smt atexit() to register a shut-down function; these shut-down functions are called by smt term(), in the order that you declare them.
Any program can create a thread in any agent in the application using the thread_create() function. To create a thread you must know the name of the agent and the name of the thread you want to create.
A thread's name is local within the agent, and lets an outside program look-up the event queue for that thread. There are various ways to name threads. In single-threaded agents, it is useful to leave the thread name empty (specified as an empty string - ""). In multithreaded agents, the thread name can correspond to some resource name. For instance, in the echo agent, the thread uses the connection socket number as its name. In a router agent, several threads can have the same name; since the threads in such an agent share the same event queue, this allows a program to unambigouously find the event queue from the thread name.
This is how a single-threaded agent (e.g. the operator console) creates a thread:
/* Create initial, unnamed thread */ thread create (AGENT_NAME, "");
This is how the echo agent creates a thread to handle a new connection (tcb-> handle contains the handle of the echo master socket):
SOCKET slave_socket; /* Connected socket */ static char thread_name [12]; /* Socket number as string */ slave_socket = accept_socket (tcb-> handle); if (slave_socket != INVALID_SOCKET) { sprintf (thread_name, "%d", slave_socket); thread create (AGENT_NAME, thread_name); }
These are the API functions that you can use to send an event:
In all cases, the target queue is specified as a QID. The QID is a location-independent queue identifier that the SMT kernel creates for each queue. We use a QID instead of the address of the queue object so that events can be sent between processes running in different address spaces. (Although this is not yet implemented.)
When you send an event, you specify an event name. An agent program must have declared a method for each event it can accept. There is no such restriction for other programs that manage event queues themselves.
An event has a body, which is a block of text or binary data that is copied to the receiving event queue. You should always ensure that event bodies are portable, since the receiving event queue could in principle be on a different system. Furthermore, an event body cannot include the address of an object or variable: events can cross address spaces (i.e. be sent to other processes) so that such addresses are not meaningful.
You can send an event to a thread as soon as it has been created. This can be useful if you need to pass arguments to a child thread. The event will be delivered after the initialise_the_thread module, if you do not specify a value for the_next_event. For an example, see the echo agent SMTECHO.
The event send() call lets a program specify these optional arguments:
When a program wants to inspect the events in a queue, it uses event iterate(), which walks through the queue, event by event. To take an event off the queue, a program calls event accept(). This automatically sends an accept event, if specified. If queue is empty it simply returns with an 'not found' feedback. Some programs implement event priorities by combining event iterate() with event accept(). A program may manipulate several event queues.
To remove an event without using it, a program calls event destroy(). The event destroy() call automatically sends a reject event, if specified.
The SMT kernel handles event expiry automatically for native programs. Programs that handle floating event queues must expire old events explicitly by calling queue flush() before they start to process waiting events.
The SMT kernel provides support for event priorities and for thread priorities. These work as follows:
In practice we use priorities rarely, and for specific cases only. In the current version of the SMT kernel we use a high priority for shutdown events, and a low priority for the socket agent. All other events and threads have normal (equal) priority.
| << | < | > | >> | Copyright © 1996-99 iMatix Corporation |