A thread may be created dynamically by means of 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. You can create the new thread in the current actor by passing K_MYACTOR as the actor capability. This is the usual case. Should this be 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 is used to define the initial state of the thread, such as the initial program counter of the thread (the thread entry point), as well as the initial value of the stack pointer to be used by the created thread. You can also define whether the thread will run as a user thread or as a supervisor thread.
A thread needs a stack to run, in order to have room to store its local variables. When the thread is a user thread, the user must explicitly provide a stack to the thread. However, 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.
As the operating system does not prevent the user stack from overflowing, checks must be made every time a thread is created.
System stacks are not allowed to overflow as memory will become corrupted, resulting in unpredictable operating system behavior.
Example 6-1 is a simple 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 is implicitly created by the system. The goal of the example is to:
create a thread, which prints a message, including its thread identifier
simultaneously, the main thread prints another message with both thread identifiers
the main thread then terminates the actor
This example will work without modification whether it is run as a user or as a supervisor actor. In the first case, a user thread must be created, while in the second case a supervisor thread must be created. Using the actorPrivilege() service call might be helpful for this purpose.
This example requires some kind of synchronization between the main thread and the created one. Execution of a thread can be suspended for a given delay:
#include <chorus.h> int threadDelay(KnTimeVal* waitLimit);
This call suspends the execution of the invoking thread for a period specified by the KnTimeVal structure (see "Current Time " for more detail). There are two predefined values:
K_NOTIMEOUT specifies an infinite delay.
K_NOBLOCK, which specifies no delay. This is an explicit request for the processor to yield and reschedule another thread of the same priority.
These values may be used instead of the pointer to the KnTimeVal data structure. There is also a predefined macro which sets such a 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.
(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, 0, &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 */ 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. */ res = threadDelay(&wait); printf("Parent thread identifier = %d, Child thread identifier = %d\n", myThreadLi, newThreadLi); return 0; }
The schedParam parameter is set to 0. As a result, the created thread will inherit the scheduling attributes of the creator thread.
Note the usage of the actorPrivilege() service which enables the program to determine whether it must allocate a user stack area for the created thread or not, as well as to indicate the type of thread to be created.
If the actor is a supervisor actor, the following line: startInfo.dsSystemStackSize = K_DEFAULT_STACK_SIZE only gives an indication to the system of 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 kernel before being used for the thread. This is done either to enforce the platform-required alignment, on 8 or 16 byte boundaries for example, or to reserve a space which 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 is used to create the thread in the active state, so that the thread is ready to execute as soon as it is created.
Be aware that, although this program explicitly 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 explicitly created by the program.
The above example uses a service named threadDelay(), which allows 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 is called. Without this suspension period in the parent thread, the actor could terminate before the created thread has run.
As explained earlier, the termination of an actor implies that all its resources are freed. Threads are not an exception to that rule. Thus, the exit() call at the end of the main routine will lead to the destruction of both threads. Use of the period within the parent thread is not a guarantee. 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() has only been used in this example for the sake of simplicity, and is not recommended in practice for synchronizing threads. A more reliable synchronization scheme should be used to be sure that the actor does not terminate before the second thread has completed all jobs. These synchronization mechanisms are explained in "Synchronizing Threads".
The child thread uses the K_NOTIMEOUT special value to suspend itself for ever. This is a simple way to avoid undesirable behavior of the child thread until the actor terminates. Assume this call to threadDelay() does not exist. The child thread, after having executed the printf() statement, would reach the end of the sampleThread() routine, which being 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.
The 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 operating system programmer, must ensure that threads are properly cleaned up after they finish running. Mechanisms for coping with these types of situations are described in Chapter 7, Memory Management.