ChorusOS 5.0 Application Developer's Guide

Working with Threads

A thread is the flow of control within an actor. Each thread is associated with an actor and defines a unique execution state. An actor may contain multiple threads. The threads share the resources of that actor, such as memory regions or message spaces, and are scheduled independently.

Each thread has a local identifier, threadli. A thread may obtain its local identifier using the following ChorusOS operating system service:

#include <chorus.h>
int threadSelf();

An example of how this call can be used is provided in Example 7-1.

For an overview of the ChorusOS thread model, see the "Microkernel" chapter in the ChorusOS 5.0 Features and Architecture Overview.

Creating a Thread

A thread may be created dynamically, using the following ChorusOS operating system service:

#include <chorus.h>
int threadCreate(KnCap*         actorCap, 
                 KnThreadLid*   thLi, 
                 KnThreadStatus status,
                 void*          schedParam, 
                 void*          startInfo);

The actorCap parameter identifies the actor in which the new thread will be created. The standard way to create a new thread in the current actor is to pass K_MYACTOR as the actor capability. If this is successful, the local identifier of the newly created thread is returned at the location defined by the thLi parameter.

The schedParam parameter is used to define the scheduling properties of the thread to be created. If this parameter is set to 0, the created thread inherits the scheduling attributes of the creator thread.

The startInfo parameter defines the initial state of the thread, such as the initial program counter of the thread (the thread entry point). It also defines the initial value of the stack pointer to be used by the created thread and whether the thread will run as a user thread or as a supervisor thread.

A thread needs a stack to run so as to have room in which to store its local variables. When you create a user thread, you must explicitly specify a user stack for the thread. Stacks for supervisor threads are implicitly allocated by the system. In fact, a system stack is allocated for all threads, even those running in user mode. When a thread switches from user to supervisor mode, the system stack is used, rather than the user stack.


Note -

Because the operating system does not prevent the user stack from overflowing, checks must be made each time a thread is created.


System stacks cannot overflow as memory becomes corrupted, resulting in unpredictable operating system behavior.

Example 7-1 is a basic program illustrating the creation of a thread by the main thread of an actor. The actor is loaded by the arun command. Its main thread will be implicitly created by the system. The following example includes these steps:

The following example works without modification, whether run as a user or supervisor actor. The user thread is created first and the supervisor thread is created second. The actorPrivilege(2K) call is used to retrieve and to set the actor privileges of the user and supervisor threads.

The example requires synchronization between the main thread and the created thread. Execution of a thread can be suspended for a given delay:

#include <chorus.h>
int threadDelay(KnTimeVal* waitLimit);

The preceding call suspends the execution of the invoking thread for a period specified by the KnTimeVal structure. There are two predefined values:

These two values may be used instead of the pointer to the KnTimeVal data structure. There is also a predefined macro which sets this type of data structure from a delay expressed in milliseconds: K_MILLI_TO_TIMEVAL(KnTimeVal* waitLimit, int delay). For more information, see the threadCreate(2K), threadDelay(2K) and threadSelf(2K) man pages.


Example 7-1 Creating a Thread

(file: progov/thCreate.c)
#include <stdio.h>
#include <stdlib.h>
#include <chorus.h>
#define USER_STACK_SIZE (1024 * sizeof(long))
    int
childCreate(KnPc entry)
{
  KnActorPrivilege      actorP;
  KnDefaultStartInfo_f  startInfo;
  char*                 userStack;
  int                   childLid = -1;
  int                   res;
      /* Set defaults startInfo fields */
  startInfo.dsType            = K_DEFAULT_START_INFO;
  startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE;
      /* Get actor's privilege */
  res = actorPrivilege(K_MYACTOR, &actorP, NULL);
  if (res != K_OK) {            
    printf("Cannot get the privilege of the actor, error %d\n", res); 
    exit(1);             
  }              
      /* Set thread privilege */
  if (actorP == K_SUPACTOR) {
    startInfo.dsPrivilege = K_SUPTHREAD;
  } else {
    startInfo.dsPrivilege = K_USERTHREAD;
  }
      /* Allocate a stack for user threads */
  if (actorP != K_SUPACTOR) {
    userStack = malloc(USER_STACK_SIZE);
    if (userStack == NULL) {           
      printf("Cannot allocate user stack\n");         
      exit(1);             
    }              
    startInfo.dsUserStackPointer = userStack + USER_STACK_SIZE;
  } 
      /* Set entry point for the new thread */
  startInfo.dsEntry = entry;
      /* Create the thread in the active state */
  res = threadCreate(K_MYACTOR, &childLid, K_ACTIVE, NULL, &startInfo);
  if (res != K_OK) {            
    printf("Cannot create the thread, error %d\n", res);       
    exit(1);             
  }              
                return childLid;
}
    void
sampleThread()
{
  int myThreadLi;
  myThreadLi = threadSelf();
  printf("I am the new thread. My thread identifier is: %d\n", myThreadLi);
      /* Block itself for ever */
  (void) threadDelay(K_NOTIMEOUT);
}
int main(int argc, char** argv, char**envp)
{
  int        myThreadLi;
  int        newThreadLi;
  int        res;            
  KnTimeVal  wait;
  newThreadLi = childCreate((KnPc)sampleThread);
  myThreadLi = threadSelf();
      /* Initialize KnTimeVal structure */
  K_MILLI_TO_TIMEVAL(&wait, 10);
      /*
       * Suspend myself for 10 milliseconds to give the newly
       * created thread the opportunity to run before
       * the actor terminates.
       */
  (void) res = threadDelay(&wait);
  printf("Parent thread identifier = %d, Child thread identifier = %d\n",
      myThreadLi, newThreadLi);
  return 0;
}

In the previous example the schedParam parameter is set to a NULL pointer. As a result, the created thread inherits the scheduling attributes of the creator thread. The actorPrivilege(2K) service enables the program to determine whether it must allocate a user stack area for the created thread. It also indicates the type of thread to be created.

If the actor is a supervisor actor, the following line: startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE

indicates to the system the expected usage of the system stack. The maximum system stack length is defined by a global tunable value.

On some platforms, the stack pointer value passed in dsUserStackPointer is automatically decremented by the microkernel before being used for the thread. The decrementation occurs either to enforce the platform-required alignment, on 8 or 16 byte boundaries for example, or to reserve a space. This space will be accessed by a typical C language routine because of the platform-specific calling conventions, such as saving the return address to the caller.

The status parameter specifies that the thread is created in the active state, so that it is ready to execute as soon as it is created. Note that, although this program creates only one thread, there are in fact two threads running in this actor: the main thread, created implicitly by the system when the actor is loaded, and the thread created explicitly by the program.

The example uses the threadDelay(2K) service, which enables a thread to suspend its execution for a certain period. The parent thread suspends itself for ten milliseconds, so that the child thread is able to run before exit(3STDC) is called. Without this suspension period in the parent thread, the actor could terminate before the created thread had run. The termination of an actor implies that all its resources are freed. Threads are not an exception to this rule. Therefore, the exit(3STDC) call at the end of the main routine results in the destruction of both threads.

Using the suspension period within the parent thread does not guarantee that the child thread will execute correctly. Depending on the load of the system, ten milliseconds might not be sufficient to ensure that the child thread has completed its task. The threadDelay(2K) command is used in this example merely for the sake of simplicity, and is not recommended in practice for synchronizing threads. A more reliable synchronization method should be used to ensure that the actor does not terminate before the second thread has completed all jobs. These synchronization mechanisms are further explained in "Mutual Exclusion Locks" and "Semaphores".

The child thread uses the K_NOTIMEOUT special value to suspend itself forever. This is a simple way to avoid undesirable behavior of the child thread until the actor terminates. Assume that the call to threadDelay(2K) does not return. The child thread, having executed the printf(3STDC) statement, would reach the end of the sampleThread() routine which, since it is written in C, terminates with a return instruction. However, the child thread has nowhere to return. As a result it would return to an unspecified location, probably resulting in a memory fault.

An appropriate method to terminate a created thread is to make it delete itself using the threadDelete() service (see "Deleting a Thread").

The ChorusOS operating system does not preset the stack of a thread to ensure that the thread is deleted upon return from its starting routine. You, the ChorusOS application developer, must ensure that threads are properly cleaned up after they have finished running. Mechanisms for coping with these types of situations are described in Chapter 8, Native Memory Management.

Deleting a Thread

A thread may be deleted dynamically by itself, or by another thread using the following service:

#include <chorus.h>
int threadDelete(KnCap* actorCap, KnThreadLid thLi);

This call enables one thread to delete another inside the same actor. The actorCap parameter is set to K_MYACTOR and the thread identifier of the thread to be deleted has been identified. The call also enables one thread to delete another inside a different actor (provided they are both running on the same machine), as long as it provides both the actor capability and the target thread identifier. The predefined thread identifier K_MYSELF enables a thread to name itself without knowing its actual thread identifier.

Example 7-2 is a slightly different version of the previous example. The subroutine childCreate() is unchanged, however in this example, the created thread kills itself instead of going idle forever.


Note -

This strategy does not solve the synchronization problem that occurred in the previous example -- the main thread still does not know exacty when to terminate the actor.


For more information, refer to the threadDelete(2K) man page.


Example 7-2 Deleting a Thread

(file: progov/thDelete.c)
#include <stdio.h>
#include <stdlib.h>
#include <chorus.h>
#define USER_STACK_SIZE (1024 * sizeof(long))
    int
childCreate(KnPc entry)
{
  KnActorPrivilege      actorP;
  KnDefaultStartInfo_f  startInfo;
  char*                 userStack;
  int                   childLid = -1;
  int                   res;
  startInfo.dsType            = K_DEFAULT_START_INFO;
  startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE;
  res = actorPrivilege(K_MYACTOR, &actorP, NULL);
  if (res != K_OK) {
    printf("Cannot get the privilege of the actor, error %d\n", res);
    exit(1);
  }
  if (actorP == K_SUPACTOR) {
    startInfo.dsPrivilege = K_SUPTHREAD;
  } else {
    startInfo.dsPrivilege = K_USERTHREAD;
  }
  if (actorP != K_SUPACTOR) {
    userStack = malloc(USER_STACK_SIZE);
    if (userStack == NULL) {
      printf("Cannot allocate user stack\n");
      exit(1);
    }
    startInfo.dsUserStackPointer = userStack + USER_STACK_SIZE;
  } 
  startInfo.dsEntry = entry;
  res = threadCreate(K_MYACTOR, &childLid, K_ACTIVE, NULL, &startInfo);
  if (res != K_OK) {
    printf("Cannot create the thread, error %d\n", res);
    exit(1);
  }
  return childLid;
}
    void
sampleThread()
{
  int myThreadLi;
  myThreadLi = threadSelf();
  printf("I am the new thread. My thread identifier is: %d\n", myThreadLi);
      /* Suicide */
  (void) threadDelete(K_MYACTOR, K_MYSELF);
      /* Should never reach this point! */
}
int main(int argc, char** argv, char**envp)
{
  int        myThreadLi;
  int        newThreadLi;
  int        res;            
  KnTimeVal  wait;
  newThreadLi = childCreate((KnPc)sampleThread);
  myThreadLi = threadSelf();
      /* Initialize KnTimeVal structure */
  K_MILLI_TO_TIMEVAL(&wait, 10);
      /*
       * Suspend myself for 10 milliseconds to give the newly
       * created thread the opportunity to run before
       * the actor terminates.
       */
  (void) threadDelay(&wait);
  printf("Parent thread identifier = %d, Child thread identifier = %d\n",
      myThreadLi, newThreadLi);
  return 0;
}

The exit(3STDC) function is used instead of the threadDelete(2K) function in the main thread. Using threadDelete(2K) would leave the actor in a passive situation, with no thread running within it. This implies that resources used by an actor are not freed when the last thread is deleted.

In the case of a user thread, deleting a thread does not imply that the stack of the thread will also be freed. If the user stack was allocated through a call to malloc(3STDC), it must be freed through a call to free(3STDC). This cannot be done by the thread itself and must be done by a separate thread. In the previous example, the actor is going to terminate so there is no real need to free the stack because all resources used by the actor will be returned to the system. In the case of a supervisor thread, the ChorusOS operating system frees the system stack it allocated at threadCreate(2K)time.