Pthreads-Embedded (PTE) Porting Guide

The PTE library consists of both a platform independent component, which contains the bulk of the pthreads functionality, and a platform specific component which connects the platform independent component to the underlying OS. Naturally, the platform specific layer is the only layer that needs to be modified when porting PTE.

The OS adaptation layer (OSAL) must provide the following functionality:

  1. Threads

  2. Semaphores

  3. Mutexes

  4. Atomic operations

  5. Thread local storage

The underlying OS does not necessarily have to provide all of this functionality – it is possible for the OSAL to emulate behavior. For instance, mutexes can be emulated using semaphores provided by the OS. The sections below present a high level of the required functionality as well as how it fits into a “typical” embedded OS. Specifics such as function parameters, etc are covered in the OSAL API reference.

Threads

Thread Initialization

OsThreadCreate, OsThreadStart

Thread initialization is separated into two steps: create and start. When OSThreadCreate is called, the OS should create a new thread, but it must be started in a suspended state. The thread should not start executing until OSThreadStart is called. This separation in functionality is due necessary to avoid race condFor instance, if the target OS supports TLS but only allows a single TLS value, this single value could contain a pointer to a structure that contains multiple TLS values. The PTE distribution includes a helper file to implement this functionality (/platform/helper/tls-helper.c). See the DSP/BIOS port for an example of using tls-helper.c.itions in the PTE library.

Since the actual prototype of an thread entry point varies between OS and thus will more than likely not match that used by OsThreadCreate, it will usually be necessary to create a stub function that matches your OS's entry point prototype. This stub function simply calls the entry point specified by OsThreadCreate (see DSP/BIOS and PSP-OS ports).

Typically, OsThreadCreate will also perform other initialization; for instance to initialize TLS structures, allocate other control resources. This of course varies depending on the target OS.

Some OS's require additional parameters to start a thread. For instance, DSP/BIOS requires the priority in order to start the thread. Rather than pass these parameters to both OsThreadCreate and OsThreadStart, the OSAL should store any necessary information as thread specific values during OsThreadCreate and then retrieve them as necessary in OsThreadStart.



Thread Destruction

OsThreadExit, OsThreadDelete, OsThreadExitAndDelete, OsThreadWaitForEnd

Thread destruction is broken into three API calls to support the different use cases of the pthreads API. OsThreadExit should cause the currently executing thread to stop execution; resources should not yet be freed. This is called when a thread exits but the thread is not detached – resource deallocation must wait until the user calls pthread_join (or pthread_detach) at which point OsThreadDelete will be called.

Alternatively, if a detached thread exits, thread resource deallocation and thread termination can occur simultaneously. In this case, OsThreadExitAndDelete will be called.

OsThreadWaitForEnd should block until the specified thread exists. For OS's that do not directly support this functionality, a semaphore can be used to emulate this behavior (see DSP/BIOS port). Note that this call should be cancellable – that is, it should return (even if the target thread has not exited) if OsThreadCancel is called.

Thread Priority

OsThreadSetPriority, OsThreadGetPriority, OsThreadGetMaxPriority, OsThreadGetMinPriority

The OSAL provides the upper and lower bounds of it's priority range when OsThreadGetMaxPriority and OsThreadGetMinPriority are called. The PTE library will ensure that all priorities passed to the OSAL (e.g. through OsThreadCreate) are within these bounds.

Thread Cancellation

OsThreadCancel, OsThreadCheckCancel

Currently, the PTE library only supports deferred cancellation (see PTE notes). While the PTE library handles most of the complexities of cancellation, there are three hooks required from the OSAL. When OsThreadCancel is called, it must cause OsSemaphorePendCancellable and OsThreadWaitForEnd to return (this function is used by the PTE library to implement pthread cancellation points). Since most embedded OS's do not support this kind of functionality, it can be implemented using semaphores (see DSP/BIOS and PSP-OS ports). OsThreadCheckCancel simply returns whether OsThreadCancel has been called for this thread.

Miscellaneous Thread Functionality

OsThreadGetHandle, OsThreadSleep, OsThreadGetMaxPriority, OsThreadGetMinPriority, OsThreadGetDefaultPriority

Semaphores



OsSemaphoreCreate, OsSemaphoreDelete, OsSemaphorePend, OsSemaphorePort

This basic semaphore functionality should map directly to almost all embedded OS's.

OsSemaphoreCancellablePend

In order to implement deferred cancellation, a “cancellable” pend (OsSemaphorePendCancellable) must also be supported. As discussed above, OsSemaphorePendCancellable must return when OsThreadCancel has been called on the thread that is currently pending, regardless of whether the semaphore has been posted to or not. The way that this is implemented in other ports (e.g. DSP/BIOS and PSP-OS) is to use an additional semaphore, and then poll on both semaphores, as shown in the pseudo-code below:

loop forever:

poll main semaphore

if semaphore was posted to, return OK

else

check timeout

if timeout has expired, return 'timed out'

else

poll cancellation semaphore

if cancellation semaphore has been posted to, return 'canceled'

else

sleep for small amount of time

For instance, if the target OS supports TLS but only allows a single TLS value, this single value could contain a pointer to a structure that contains multiple TLS values. The PTE distribution includes a helper file to implement this functionality (/platform/helper/tls-helper.c). See the DSP/BIOS port for an example of using tls-helper.c.

Mutexes

Mutexes are only included as an optimization as some OS's mutex operation is much faster than semaphore operations. If the target OS does not support mutexes, they can easily be implemented using semaphores.



Atomic operations

OsAtomicExchange, OsAtomicCompareExchange, OsAtomicExchangeIncrement, OsAtomicDecrement, OsAtomicIncrement

The PTE library requires five atomic operations to be supplied by the OSAL. Macros are used in case the target platform supports direct assembly instructions to perform some or all of these operations. However, under most OS's these macros will simply refer to functions that disable interrupts and then perform the required operations.



Thread local storage

OsTlsInit, OsTlsAlloc, OsTlsFree, OsTlsSetValue, OsTlsGetValue

The OSAL must be able to allocate and free TLS keys, and retrieve thread specific data. If the target OS does not support this level of TLS functionality, but does have limited TLS support, it is possible to emulate the behavior required by the PTE library.

For instance, if the target OS supports TLS but only allows a single TLS value, this single value could contain a pointer to a structure that contains multiple TLS values. The PTE distribution includes a helper file to implement this functionality (/platform/helper/tls-helper.c). See the DSP/BIOS port for an example of using tls-helper.c.

If the OS contains no TLS support, it might still be possible to emulate TLS functionality. See the PSP-OS port for an example of how this can be accomplished. Be warned – it is not a pretty solution, but it works.

It is important to note that TLS functionality is required by the PTE library – it is used for more than just the pthread TLS functions, but is used extensively throughout the library.

One potentially tricky issue with TLS is how to handle the case of when pthread TLS functions are called from non-pthread threads (i.e. pure native threads that were not created through pthread_create). Technically, according to the pthread spec, this should work. However, it is problematic in that OsTlsInit would not have been called for that thread, since it is called in response to pthread_create(). Different ports handle this differently – see the notes for a particular ports.

Miscellaneous Functionality

ftime

Since pthreads uses absolute time for timeouts, the PTE library requires the OS to supply the current time. Note that this does not have to be the actual world time, but can be an internal timebase (for example, since the unit started up). However, the time source should be the same one that the caller to pthread would use.

Types and Constants

The OSAL layer must declare a number of types and constants.

The following types must be defined to map to the appropriate OS constructs:

OsThreadHandle

OsSemaphoreHandle

OsMutexHandle


The following constants must be defined:

OS_DEFAULT_PRIO – default priority for a created thread.

OS_MIN_PRIO – minimum thread priority.

OS_MAX_PRIO – maximum thread priority.

OS_MAX_SIMUL_THREADS – maximum number of threads that may be active simultaneously.



Each port must also include a file, pte_types.h, that defines all of the following types. This may be done by explicitly typedef'ing the structure (for OS's that do not natively support the type) or simply by including the appropriate header file:

pid_t

struct timespec

mode_t

struct timeb

File structure

The OSAL layer must include a file named pte_osal.h. This file must include pte_generic_osal.h as well as the platform specific header file (e.g. dspbios_osal.h).