| iMatix home page
| << | < | > | >>
SMT Logo SMT
Version 2.81

Using The SMT Kernel API

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.

SMT Kernel Functions

This is the complete set of functions supported by the SMT kernel:

smt init()
Initialise the SMT kernel.
smt term()
Shut-down the SMT kernel.
smt exec full()
Execute the SMT application until halted.
smt exec step()
Execute just next scheduled thread.
smt active()
Check if application has halted.
smt set console()
Specify an agent to act as console.
smt set timer()
Specify an agent to act as timer.
smt atexit()
Define a termination function.
smt shutdown()
Halt the application prematurely.
agent declare()
Define a new agent.
agent lookup()
Check if a specific agent is defined.
agent destroy()
Remove an agent from the SMT kernel tables.
method declare()
Define a method for an agent.
method lookup()
Check if a specific method is defined.
method destroy()
Remove a method from the SMT kernel tables.
queue create()
Define an event queue for an agent, or a floating queue.
queue lookup()
Check if an event specific queue is defined.
queue destroy()
Remove an event queue from the SMT kernel tables.
queue flush()
Expire out-of-date events in the event queue.
event send()
Send an event to some event queue.
event accept()
Accept the next event from an event queue.
event reject()
Reject the next event from an event queue.
event expire()
Expire the next event from an event queue.
event discard()
Discard the next event from an event queue.
event iterate()
Find the next event in an event queue.
event destroy()
Destroy a specific event.
event wait()
Wait explicitly for an external event.
thread create()
Define a thread for an agent, maybe create a queue.
thread lookup()
Check if a specific thread is defined.
thread destroy()
Remove a thread from the SMT kernel tables.
semaph create()
Create a new semaphore.
semaph lookup()
Check if a specific semaphore is defined.
semaph destroy()
Remove a semaphore from the SMT kernel tables.
semaph wait()
When the semaphore value is > 0, decrement it.
semaph signal()
Add 1 to the semaphore value.
lazy creat()
Create a file, without blocking.
lazy open()
Open a file, without blocking.
lazy read()
Read from a file, without blocking.
lazy write()
Write to a file, without blocking.
lazy close()
Close a file, without blocking.
senderr()
Send the current strerror (errno) to some queue.
sendfmt()
Format a text using printf conventions and send to some queue.
raise exception()
Raise an exception (for dialog programs only).
recycle module()
Repeat execution of the current dialog module.

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).

Initialisation and Termination

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.

Creating A Thread

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);
      }

Sending and Receiving Events

These are the API functions that you can use to send an event:

event send()
Send an event to some event queue.
senderr()
Send the current strerror (errno) to some queue.
sendfmt()
Format a text using printf conventions and send to some queue.

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.

Sending An Initial Event To A Thread

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.

Event Lifespan and Acknowledgment

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.

Using Priorities

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.


| << | < | > | >> iMatix Copyright © 1996-99 iMatix Corporation