NAME | FEATURE SUMMARY | Actors | Threads | Thread scheduling | Execution control | Thread and actor states | Thread context | Per-thread and per-actor kernel data | Interrupts | Locks | Interrupt Level Execution Mode | Exceptions | Local invocation | API | ATTRIBUTES
The Core Executive implements the basic ChorusOS execution model and provides the framework for all other features which can be configured. Every system must include a Core Executive.
The Core Executive exports the basic set of kernel abstractions and services:
The unit of application modularization (actor).
The unit of execution (thread).
Thread control operations.
Exception management services.
A minimal interrupt management service
No synchronization, scheduling, time, communication or memory management policies are wired into the core executive. These policies and services are provided by additional features, which the user may select depending on the particular hardware and software requirements.
The first abstraction exported by the Core Executive is the actor. The actor is the unit of program modularization for both applications and subsystems; subsystem servers and individual applications programs are examples of actors. The actor is also the unit of resource encapsulation. Through internal kernel interfaces, each kernel feature may attach particular resources to actors. For example, at the Core Executive level, threads are resources attached to actors. Memory management features will attach address spaces to actors.
In the core executive, actors may be created and deleted dynamically. When an actor is created, those kernel features which manage per-actor resources will be invoked by the core executive. At actor destruction time, these features will again be invoked in order to free their allocated resources.
Actors are designated by capabilities. An actor capability is an opaque identifier. The knowledge of the capability of an actor yields all rights regarding the actor (creating, deleting and modifying its resources, through the various feature interfaces). By default, only the creator of an actor knows the actor capability, though the creator can give the name to others.
The Core Executive offers a very basic framework for actor typing. In most cases, the Core Executive does not exploit this information; it acts as a repository of the typing information and makes it available to other kernel features when relevant:
First, an actor may be either a supervisor actor or a user actor. This information defines the nature of the actor address space (for memory management features). Note that the actor privilege is not relevant for a memory management feature which does not support multiple protected address spaces. In this case, user actors are equivalent to supervisor actors.
Second, an actor may be trusted. Trusted actors are also referred to as system actors. A supervisor actor is by definition trusted. When trusted, a user actor gets access to certain additional services.
The second important abstraction is the thread. The thread is the unit of execution, and represents a single flow of sequential execution of a program.
Threads execute at either user or supervisor privilege. Supervisor threads are trusted by the kernel. They execute in the privileged processor mode, with access to restricted processor instructions, and in the system address space. Certain kernel services are restricted to supervisor threads. User threads execute in non privileged mode and in isolated address spaces. However, user threads may execute temporarily with supervisor privilege. This can occur as a result of a trap or exception, as handlers for these events are necessarily created within supervisor actors, and in other cases.
A thread is created within a specific actor, but may execute in the context of several different actors during its lifetime. The current environment of an executing thread may involve two different actors:
The actor in which the thread was created. The home actor of a thread is constant over the life of the thread.
The actor on behalf of which the thread currently executes. Any thread has the right to access and manage the resources of its execution actor. Local resources of an actor are named using local identifiers (handles), whose scope is limited to the local actor. When an operation applies to an actor other than the current thread's execution actor, the actor capability must be provided in addition to the local identifier of the resource.
Certain operations during the life of a thread depend on its execution actor. For example, an exception in a thread is managed according to the exception handling set for its execution actor.
When a thread is created, its home and execution actors are identical. During a local cross-actor invocation (see LAP(5FEA)), the execution actor changes for the duration of handler execution, then is reset to its original setting: the execution actor is set to the actor that owns the handler being invoked.
In this document the ``current actor'' refers to the execution actor unless otherwise noted. The interface constant K_MYACTOR and the actorSelf system call also refer to the execution actor of the currently executing thread.
When the home actor and execution actor of a thread are identical, it is said to be executing internally, or at application level. During periods when the execution actor is different from the home actor, the thread executes externally, or at server or kernel level. The distinction between internal and external is important in several areas of the API, within the Core Executive and in other features. Unlike the execution privilege, it allows uniform treatment of application actor execution regardless of whether the application actor is a user actor or a supervisor actor.
When the system is configured with MEM_FLM, only supervisor threads are supported.
The Core Executive only implements a very basic scheduling policy, and may be complemented by a scheduler feature. The interface between the Core Executive and a scheduler feature is a kernel internal interface.
From the Core Executive point of view, threads are independent entities. For example, a given scheduler may schedule multiple threads of an actor to run in parallel on a multiprocessor node.
The Core Executive exports primitives allowing threads to coordinate their execution with other threads or with external events, as well as functions to asynchronously influence the execution of other threads.
It is possible to:
Wait until an event is posted to the current thread.
Stop (and restart) another thread.
Stop (and restart) all of the threads of an actor.
Abort another thread.
The semantics of these operations are the following:
CORE exports a simple thread synchronization interface built around the threadSem (thread semaphore) object. A threadSem is a user-defined object, logically equivalent to a binary semaphore, which is bound to a particular thread. The primitives threadSemWait and threadSemPost may be used to block the current thread on its threadSem, and to awaken a blocked thread, respectively. The binary semaphore state is effectively a ``post pending'' flag, which eliminates the possibility of a race condition in case a post operation executes before the corresponding wait.
This interface serves as a base for sleep (context switching) locks and other synchronization functions. CORE also exports non-sleep locks (see below). Higher-level sleep synchronization functions are provided by the optional SEM and EVENT features.
The remainder of this section applies only to CORE.
A variety of kernel system calls may block the current thread when a request cannot be satisfied immediately or to await a particular event. Thread block and wakeup operations are performed by kernel features (for example synchronization features), through the invocation of a kernel internal interface.
It is possible to force a thread to exit the blocked state. Subsystems managers might need to awaken blocked threads prematurely, for example to allow them to process asynchronous signals.
When a thread is blocked, it may be ABORTABLE, depending on the blocking primitive it invoked and/or the arguments of this invocation.
The threadAbort primitive forces a thread which is blocked in an ABORTABLE state to be awakened. In general, the corresponding blocking call will return with a specific error code indicating the abort.
Because the invoker of threadAbort may not know whether the thread is currently blocked or not, the kernel also defines the behavior of threadAbort if the thread is active.
The effect of threadAbort thus depends on the state of the thread:
ABORTABLE blocking operation, the thread is awakened (with the K_EABORT error code). The abort is said to have been handled.
If the thread is not blocked in an ABORTABLE state, the abort event is recorded. The thread is said to be in the ABORTED state. The abort will be handled later.
When the thread is in the ABORTED state, it will handle the abort when one of the following situations occurs:
The thread executes in its home actor, that is, in internal mode. In this case, the thread may execute an abort handler. Supervisor actors may attach abort handlers to other actors, in order to trap the entry of threads of these actors in the ABORTED state. If this type of handler is attached to the thread's home actor, it will be 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 kernel 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 execution of the abort handler, the abort is considered as having been handled.
The thread invokes an ABORTABLE blocking primitive. In this case, this primitive will return immediately with the K_EABORT error code. The abort has been handled.
The thread invokes the threadAborted kernel primitive. This primitive returns its current state (aborted or not) and clears it.
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 kernel call, trap, or other) the operation will be deferred until the execution actor is reset to the home actor (for example, at return from kernel call or trap). 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 will be deleted only when it returns to its home actor.
A thread, once created, may always be found in one of the following states:
The thread is running or ready to run.
The thread was just created and has not yet been activated (CORE only).
The thread (or the thread's actor) has been stopped (CORE only).
The thread is blocked, waiting for a particular event or time interval. A WAITING thread may be ABORTABLE or NONABORTABLE.
A thread may be created either in the ACTIVE state or in the INACTIVE state. In the latter case, it must be activated with a threadActivate call before it can run.
Threads enter the WAITING state voluntarily, by invoking a blocking system call. The transition back to the ACTIVE state is normally triggered by a corresponding wakeup-style system call. In addition, a WAITING thread that is also ABORTABLE may be forced to exit its waiting state by the use of the threadAbort call. In certain cases this call also diverts the execution of an active thread.
Along with these four major states, there are several sets of substates which are orthogonal to the major states.
The thread was in the ABORTED state when it was aborted using threadAbort, but the abort has not yet been taken into account by the thread. (An abort is "consumed" by a thread either when it is forced to return from a blocking call, or when it executes an abort handler, or when it calls threadAborted to explicitly query and clear the ABORTED state.)
As described earlier in this section, a thread is in the INTERNAL state when it executes in its home actor, and in the EXTERNAL state when its execution actor is different from its home actor.
The register context of a thread is initialized when the thread is created. The thread creation primitive permits portable parts of the context to be initialized, such as the program counter and the stack pointer. At thread creation time, machine--specific registers are set to machine--specific default values.
Once a thread has been created, the threadContext primitive allows all registers to be modified, including software registers (see below).
The kernel modules often need to record data specific to actors and threads. For example, a communication module will have to store the set of ports attached to an actor. Similarly, an executive module will have to manage thread lists. For this purpose, the Core Executive provides an efficient mechanism for each kernel module to allocate its own portion of the thread and actor Core Executive structures, and to retrieve the address of its portion efficiently, given a thread identifier. This is a kernel internal service.
Applications and libraries also often need to allocate per thread data structures. The Core Executive also provides support for this function. The intention is to allow the programmer to logically associate an area within the actor address space with a specific thread, and to provide a way for a thread to access this data at any step of its execution, efficiently and without the need for special language constructs.
This mechanism facilitates the programming of pools of threads that share a code segment and share memory within their actor, but where each individual thread also wants to manage some private data, outside of its stack, in the global data section. No means are provided for protecting a thread's private data against access by other threads. This can be considered an advantage, as it allows flexible and low-cost communication policies between threads of an actor.
Two registers are associated with each thread, called software registers A software register may be considered as an extension of the hardware registers: it is part of the thread context, and is saved/restored at each context switch. By analogy with stack pointers on certain hardware architectures, two different registers are defined, one per execution mode (user or supervisor). Two kernel calls read and write the value of the software register associated with the current execution mode of the calling thread. These calls (threadLoadR and threadStoreR) are the new ``machine instructions'' that allow use of these registers. (On some targets, threadLoadR is available as a macro, thus bypassing the cost of a kernel call.)
A common way to use a software register is to initialize it with a pointer to a thread's private data structure when the thread is initialized, using threadStoreR. The pointer value may then be obtained at any step of the thread execution, using threadLoadR. Note that the software registers of a thread may also be initialized by the creator of a thread using threadContext. Typically, this is used at thread creation time in order to pass the address of the thread's private data structure to the newly created thread.
The PRIVATE-DATA feature provides a higher-level API on top of the software registers. Independent sets of per-thread or per-actor data may be declared by separate program units (or library functions) within a single program. When available, it is recommended that applications use this feature rather than interact with the software registers directly.
The Core Executive does not intervene within the interrupt processing path. Interrupt management modules connect their handlers directly to the interrupt vector. This ensures optimal performance for critical real-time applications.
The Core Executive provides two categories of non-sleep synchronization objects (on a multiprocessor, this is often referred to as ``spinning'' or ``busy-waiting'' synchronization).
Spin locks disable preemption on the current processor when they are held. While preemption is disabled, the current thread has access to only a few kernel system calls. These calls are indicated in the corresponding sections. Most of them are simple operations that awaken blocked threads, set timeouts, and so on. If any other system calls are invoked with preemption disabled, results are unpredictable.
Masked locks disable interrupts on the current processor when they are held.
While executing within an interrupt handler, or within a thread that has disabled preemption, one is not allowed to block. This condition also applies when a thread holds a spin lock, a masked spin lock or masks interrupts, since these implicitly disable the preemption. In this interrupt handler execution mode, the list of system calls permitted for invocation is restricted to the following:
semV threadSemPost eventPost msgAllocate msgPut svMaskedLockGet svMaskedLockRel svMaskAll svUnmaskAll svUnmask svPreemptable svIntrLevel svTimeoutSet svTimeoutCancel |
Note that the system does not enforce this restriction. However, if non-permitted system calls are issued, the system will hang non-deterministically.
The lapInvoke invoked in this execution mode is subject to the restriction that the invoked lap must have been created as a raw lap.
Three kinds of exceptions may be encountered within an operating system kernel context:
Generated voluntarily by the currently running thread, they are used to change the thread privilege level in order to execute system code.
Generated involuntarily by the current thread, generally due to errors such as division by 0.
Explicitly generated by some kernel modules or supervisor actors, the panics correspond to software/hardware faults which are not recoverable at the application level.
The core executive 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 provides a low-overhead mechanism for invocation of service routines in supervisor actors on the local site, by both user and supervisor actor callers. In addition, optional extensions provide safe on-the-fly shutdown of local service routines and a local name binding service (see the LAPSAFE and LAPBIND features).
Local service routines are used for two purposes. First, various subsystems and individual servers require a means of exporting an API to their clients on the local site. Second, supervisor actors connect handlers for certain hardware and software events (for example, exceptions and timeouts). Because the kernel is effectively the client in the latter case, a minimal local invocation mechanism is mandatory in any kernel instance implementing those handlers.
The local access point, or lap, is a generic software interface for kernel-mediated calls from one actor to another on the local site. A lap is a kernel object which represents a handler function that has been exported by a supervisor actor for invocation by client actors. Once a lap has been created, both server and clients refer to that lap via an opaque name called a lap descriptor. When the server creates a lap, the kernel generates and returns the lap descriptor. It can then be exported to clients, either directly to a specific client, via an established communication path, or for general use, via a nameserver. Any client that obtains a lap descriptor may invoke the lap, that is, call the handler function, via a kernel call. At invocation, the kernel performs certain actions to ensure a correct execution environment for the handler. In particular, the execution actor and host actor of the current thread are set to the handler's home actor for the duration of the handler invocation.
The basic or ``raw'' lap service provides a lightweight invocation with a minimum of functionality. A server creates a lap and obtains a lap descriptor using the svLapCreate system call, passing the handler entry address and a ``cookie'' value as arguments. At handler invocation, the cookie will be passed as an argument. This is useful when several lap objects use the same function entry point. The lap descriptor, once in possession of a client, may be used to invoke the handler. This is accomplished with the lapInvoke system call. No validity checking is performed either during the handler invocation or at the return to the calling code in the client actor. If the server withdraws its handler using svLapDelete, there is no protection for threads currently invoking the handler. Moreover, future invocations to the deleted lap are also not inhibited.
The lap descriptor is an opaque structure, with no application-visible fields. Functions are provided for optimized manipulations (clearing, testing validity, duplicating) of lap descriptors without introducing dependencies in an application on a particular descriptor format.
For efficiency reasons, the lap mechanism provides only a very simple argument passing scheme based on the assumption of direct access by a server to client memory. A single argument pointer is passed in the invocation, and the management of argument lists, types, and so on is left to individual server convention.
The Core Executive feature API is summarized in the following table.
Function Comment actorCreate Create an actor actorDelete Delete an actor actorSelf Get the current actor's capability lapDescDup Duplicate a lap descriptor lapDescIsZero Check a lap descriptor lapDescZero Clear a lap descriptor lapInvoke Invoke a lap lapResolve Find a lap descriptor by name threadActivate Activate a newly created thread threadContext Get or/and set a thread's context threadCreate Create a thread threadDelete Delete a thread threadDelay Delay the current thread threadLoadR Get software register threadName Set/Get thread symbolic name threadSelf Get the current thread's LI threadSemInit Initialize a thread semaphore threadSemWait Wait on a thread semaphore threadSemPost Signal a thread semaphore threadStat Get thread information threadStoreR Set software register svExcHandler Set an actor's exception handler svActorExcHandlerConnect Connect an actor's exception handler svActorExctHandlerDisconnect Disconnect an actor's exception handler svActorExctHandlerGetConnected Get an actor's exception handler svGetInvoker Get handler invoker svLapCreate Create a lap svLapDelete Delete a lap svMaskedLockGet Disable interrupts and get a spin lock svMaskedLockInit Initialize a spin lock svMaskedLockRel Release a spin lock and enable interrupts svSpinLockGet Disable preemption and get a spin lock svSpinLockInit Initialize a spin lock svSpinLockRel Release a spin lock and enable preemption svSpinLockTry Try to get a spin lock and disable preemption svSysCtx Get the system context structure address svSysPanic Force panic handling processing svSysReboot Request a reboot of the local size sySysTrapHandlerConnect Connect a trap handler sySysTrapHandlerDisconnect Disconnect a trap handler sySysTrapHandlerGetConnected Get a trap handler svTrapConnect Connect a trap handler svTrapDisConnect Disconnect a trap handler sysGetConf Get ChorusOS module configuration value sysRead Read characters from system console sysReboot Request a reboot of the local site sysWrite Write characters from system console sysPoll Poll characters from system console |
See attributes(5) for descriptions of the following attributes:
ATTRIBUTE TYPE | ATTRIBUTE VALUE |
---|---|
Interface Stability | Evolving |
NAME | FEATURE SUMMARY | Actors | Threads | Thread scheduling | Execution control | Thread and actor states | Thread context | Per-thread and per-actor kernel data | Interrupts | Locks | Interrupt Level Execution Mode | Exceptions | Local invocation | API | ATTRIBUTES