This chapter demonstrates the use of the ChorusOS native application programming interfaces for handling thread exceptions, aborts, faults, traps, timeouts, and so forth. The APIs covered here include those related to each type of handler.
After implementing the examples provided in this chapter, you will understand how these APIs might be used in an application.
The ChorusOS operating system provides three kinds of exceptions:
Generated voluntarily by the current thread -- traps are used to change the thread privilege level to execute system code.
Generated involuntarily by the current thread -- usually due to errors such as division by 0.
Explicitly generated by specific microkernel modules or supervisor actors -- panics correspond to software/hardware faults which are not recoverable at the application level.
The core executive API provides basic services for subsystems to handle these three kinds of exceptions. For this purpose, the core executive exports an interface for subsystems to declare trap, exception, and panic handlers. Exception handlers are declared on a per actor basis, while trap handlers and panic handlers are declared on a site-wide basis.
The core executive API includes a number of exception handler system calls, described in the following table.
Table 13-1 Exception Handler System Calls
System Call |
Purpose |
---|---|
svExcHandler() |
Sets an actor's exception handler (compatible with the ChorusOS 4.x API) |
svActorExcHandlerConnect() |
Connects an actor's exception handler |
svActorExcHandlerDisconnect() |
Disconnects an actor's exception handler |
svActorExcHandlerGetConnected() |
Gets an actor's exception handler |
A variety of microkernel system calls can be used to block a current thread when a request cannot be satisfied immediately or has to await a particular event. Thread blocking and wakeup operations are performed by microkernel features through the invocation of a microkernel internal interface.
A thread can be forced to exit the blocked state. Subsystem managers might need to awaken blocked threads prematurely to enable them to perform their function (to process asynchronous signals, for example). When a thread is blocked, it may be ABORTABLE, depending on the blocking primitive invoked and/or the arguments of this invocation.
The threadAbort() primitive forces a thread which is blocked in an ABORTABLE state to be awakened. Generally, a corresponding blocking call returns a specific error code, indicating an abort.
Because the invoker of threadAbort() may not know whether the thread is currently blocked or not, the microkernel will define the behavior of the threadAbort() call if the thread is active.
Therefore, the effect of threadAbort() depends on the state of the thread.
If a thread is blocked into an ABORTABLE microkernel call without an abort handler for its home actor, the aborted thread returns with the K_EABORT error code. If an abort handler is set, the error code is not returned and the abort handler is invoked.
If a thread is not blocked in an ABORTABLE state, the abort event will be recorded. The thread is said to be in an ABORTED state. The abort will be handled later.
When a thread is in an ABORTED state, it handles the abort if either of the following situations occurs:
The thread executes in its home actor (internal mode). In this case, the thread can execute an abort handler. Supervisor actors can attach abort handlers to other actors to trap the entry of threads of actors in an ABORTED state. If this type of handler is attached to the thread's home actor, it is invoked as soon as the thread executes in its home actor while in the ABORTED state.
If the thread was executing in a different actor or a microkernel call when aborted, and an abort handler is in effect, the handler will be invoked as soon as the thread returns to its home actor execution environment.
After the abort handler has been executed, the abort is considered to have been handled:
The thread invokes an ABORTABLE blocking primitive. In this case, the primitive returns with the K_EABORT error code. The abort has been handled.
The thread invokes the threadAborted() microkernel primitive. This primitive returns its current state (aborted or not) and clears the state.
Asynchronous execution control operations (threadAbort()) take effect immediately only if the target thread is currently running in internal mode, that is, if its current execution actor matches its home actor. In the case, where the thread has changed its execution actor (through a microkernel call, trap, or other) , the operation is deferred until the execution actor is reset to the home actor, on return from the microkernel call or trap, for example. Deletion of a thread is also considered an asynchronous control operation in this sense. Thus, a thread is immune from deletion (except if it deletes itself) when its execution actor is changed for a trap or other cross-actor invocation. It is deleted only when it returns to its home actor.
The following examples illustrate the abort handling capabilities of the ChorusOS operating system.
This example enables an application to abort a thread whose identication is given as an argument. The first four parameters correspond to the capability of the thread home actor, and the fifth argument is the local identication of the thread in the home actor.
#include <utilities.h> KnCap actorCap; int result; int main(int argc, char *argv[ ]) { if (argc != 6) { fprintf(stderr, "bad call\n"); exit(1); } readCap(argv+1, &actorCap); result = threadAbort(&actorCap, atoi(argv[5])); if (result < 0) fprintf(stderr, "error on threadAbort: %d\n", strSysError(result)); }
In the following example, a thread requests an abort of itself before it enters a loop and commits to an infinite abortable delay.
#include <chorus.h> int main( ) { int i, result; threadAbort(K_MYACTOR, K_MYSELF); printf("Before looping\n"); for (i = 0; i < 50000000; i++); printf("After looping\n"); result = threadDelay(K_NOTIMEOUT); if(result == K_EABORT) printf("threadDelay aborted\n") else printf("result = %d\n", result); }
The output of this example is:
neon abortableDelay_u started aid = 23 Before looping After looping threadDelay aborted |
In the previous example the abort request immediately awakens the thread when the abort request calls the threadDelay() primitive. It has no effect on the previously executed loop.
The following example modifies the code used in the previous example to produce a non-abortable application (notAbortDel). The code has been modified as follows:
The request to auto-abort the thread is deleted.
The abortable call threadDelay(K_NOTIMEOUT) is replaced by the equivalent non-abortable call threadDelay(K_NOTIMEOUT_NOABORT).
The notAbortDel application is then executed using the following:
neon-n notAbortDel_u & [1] 16123 started aid = 23 Before looping After looping |
In the previous example, a request for the thread to abort is made using the threadAbort() application created in Example 13-1. To verify that the request has no effect and that the thread or actor must be killed:
$ rsh neon arun cs -la 23 started aid = 22 ChorusOS r5.0.0 Site 0 Time 1d 19h 14m 22 ACTOR-UI KEY LID TYPE STATUS TH# NAME 200000d0 869da80a 00000017 00000000 0023 USER STARTED 001 notAbortDel_u THREAD-LI PRIORITY TT IT CTX SC-MS-PN 0007 140 00000430 00000430 ff6380 0- 1- 0 main ............................ $ neon threadAbort_u 200000d0 869da80a 17 0 7 started aid = 22 $ rsh neon aps grep notAbort 0 23 notAbortDel_u 0 N/A $ rsh neon akill 23 |
In the following example, an application called abortedState is created as follows:.
#include <chorus.h> int main() { int i, result; threadAbort(K_MYACTOR, K_MYSELF); printf("Before looping \n"); for (i=0; i<100000; i++); printf("After looping\n"); threadAbort(K_MYACTOR, K_MYSELF); result = threadAborted( ); if (result == 1) printf("Aborted state\n"); else printf("Non aborted\n"); result = threadAborted( ); if (result == 1) printf("Aborted state\n"); else printf("Non aborted\n"); }
The output of this example is:
$ neon abortedState_u started aid = 23 Before looping After looping Aborted state Non aborted |
Note that abort requests are not accumulated. An abort request for a thread that is already in the aborted state will be ignored.
A specific handler is associated with a given trap. It receives as an argument, the context of the calling thread when the trap occurred. Such a trap handler is defined system-wide, that is, all actors will invoke the same handler after being defined. The handler has access to the information required to identify the system call and to retrieve the arguments.
Only one handler may be attached to a given trap. After the handler is installed, it is automatically invoked. The ChorusOS operating system provides an interface that is based on the LAP mechanism for connecting and disconnecting threads. This interface can be used by supervisor threads only and enables a specific trap handler to be connected to different trap numbers.
When a trap handler is called, its argument is a pointer to a KnSysTrapDesc object, which has the following fields:
The thread's context that is saved when the trap occurs. The values of the processor's registers can be accessed by the trap handler. The trap handler can also modify the register values before returning.
The number of the trap that was invoked.
The core executive API includes the following trap handler system calls:
Table 13-2 Trap Handler System Calls
System Call |
Purpose |
---|---|
svSysTrapHandlerConnect() |
Connects a trap handler |
svSysTrapHandlerDisconnect() |
Disconnects a trap handler |
svSysTrapHandlerGetConnected() |
Gets a trap handler |
svTrapConnect() |
Connects a trap handler |
svTrapDisconnect() |
Disconnects a trap handler |
svTrapConnect() and svTrapDisconnect() are implemented in the library and are provided for backward compatibility only. The new system calls svSysTrapHandlerConnect() and svSysTrapHandlerDisconnect() use a LAP handler for enhanced security.
The following three examples illustrate how to connect, disconnect, and get trap handlers in the ChorusOS operating system.
A trap handler specified as a LAP handler can be connected to a given trap number by calling the primitive:
#include <exec/chTrap.h> int svSysTrapHandlerConnect( unsigned int trapNumber, KnLapDesc *trapLapDesc );
This call returns K_OK if it is successful and returns a negative value if unsuccessful.
Value |
Error |
---|---|
K_EBUSY |
trapNumber is already connected |
K_EINVAL |
trapNumber is invalid |
A trap handler previously connected to a trap number with a call to svSysTrapHandlerConnect() can be disconnected by calling the primitive:
#include <exec/chTrap.h> int svSysTrapHandlerDisconnect( unsigned int trapNumber, KnLapDesc *currentTrapLapDesc );
The value of currentTrapLapDesc can be K_CONNECTED_LAP. If this is not the case, the value must point to a LAP descriptor that is identical to the LAP desciptor currently connected to the trap number.
The function will K_OK if successful or a negative value if an error occurs.
Value |
Error |
---|---|
K_EINVAL |
currentTrapLapDesc is not K_CONNECTED_LAP and does not match the LAP descriptor of the current trap handler. |
A copy of the LAP descriptor corresponding to the trap handler that is currently connected to the trap number can be obtained by calling the primitive:
#include <exec/chTrap.h> int svSysTrapHandlerGetConnected( unsigned int trapNumber, KnLapDesc *currentTrapLapDesc );
The function returns K_OK on success and K_EINVAL if an error occurs.