Inhalt
Topic:.os_thread.
They are two possibilities for organization to do the work on a target system:
a) Using a multithread operation system such as QNX, embedded Linux etc.
b) Only using the hardware interrupts and the main-backloop.
The approach b) is proper for simple targets. Both can be compatible:
The difference running in a thread - running in interrupt for a user algorithm is: for b) there should never be a wait for anything. The interrupt must go on. Elsewhere both is similar. A complex functionality can run in hardware interrupt. For example a cyclically activated control algorithm (with PID-controller, actual- and reference-value, controller output) is proper to run in the same interrupt which is caused from the measurement system (analog-digital-converter). The main-backloop can execute communication tasks which can be interrupted, should trigger a watchdog etc.
It is possible to write sources with the same context for locking mechanism able to use in the interrupt and in a preemptive mulitthreading, see TODO.
In generally, the creation of threads or the interrupt organization should be part of the special sources like shown in the
image above. Therefore it can be used the operation system API immediately, like shown for QNX for example. But it is also
possible to use ony the OSAL layer for os_thread.h
. The capability of that is lesser but may be sufficient for the requirements. For a simple CPU without OS the interrupt organization
is a special part of the CPU capabilities, intializing some CPU register etc. Therefore it is part of the special src in any case.
Topic:.os_thread.thread.
The different operation systems have some different definitions for API-functionality. But the simple intersection between all is used and may be sufficent for the OSAL layer.
The thread routine
Adequate to the definitions in emc/source/OSAL/os_thread.h
it should be follow the prototype:
/**The thread routine should have the followed prototype. * @param data Pointer to data. Mostly it is a class structure. * It is the same value given in createThread, param pUserData. * @return The exit value of the thread. The operation system can protocol or show ... */ typedef int OS_ThreadRoutine(void* data);
That defines the function pointer type. A thread routine on OSAL-level should be defined as
int myThreatRoutine(void* rawdata) { MyType* data = (MyType*) rawdata; while(shouldRun) { doSomewhatWith(data); } }
The void* data
-pointer is given in os_createTread(... , myData, ...)
. The common definition can only handle with a void
type because a universal base type such as ObjectJc
is not the common use in C. Threrefore a casting is necessary. Castings from void*
should be noteworthy especially for SIL (Safety Integrity Level) of programming. Therefore it should be compared with the source line(s) which invoke os_createTread(... , myData, ...)
. Additionally a significance check of the data content can be done here.
The thread routine should be run so long as the thread should be exists. That is a difference respectively the possibilities
of some operation system API definitions: Threads can sleep, re-activated etc. But that approach complicates the functionality.
Granted, the concept comes from java.lang.thread
. It is established and proven in the world of Java. If the thread should sleep and wait of a wake-up call, it is a problem
of wait-notify and not a problem of the thread. It should be programmed in the user's source.
If the thread routine ends, the thread is removed. That is the concept of Java. If the thread should be run unconditionally
till the end of the application, a while(true)
may be written there. Then only killing the process (with hardware reset on a simple CPU target) ends the thread. Usually,
a global shouldRun
can be used which is set to false
on end of the whole application.
A thread should never suspended from outside. The only way to stop thread execution is: It should inform from outside to do so, and it should do itself. Why? A thread may occupy system ressources. Only the execution in the thread itself knows which ressources. Only the thread itself can free this ressources. Simple example: The thread opens a file, only the thread (its data) knows about the opening, only the thread can close it.
Another situation is: processes in a multiprocess operation system. Usual the operation system notices which ressources are occupied by the process. The operation system frees this ressources on killing the process from outside. Why it is so? An operation system should offer a safety. A process may hang for any reason. The killing operation of a process should be safe. But the operation system cannot intervene in the details of the process. A thread is a detail. The process is responsible for its threads. It should be programmed in the process's software.
The thread handle
Usually the handle for a thread is only a simple integer value. But this value should not stored as integer, because it is not an integer (should not be used for any calculation). The handle is defined as a pointer to an unknown (forward-declared) struct:
/**Handle to a thread. The internal data structure is not known here. * A ,,OS_HandleThread,, may also be a simple integer, which is converted to this pointer type. */ typedef struct OS_HandleThread_t const* OS_HandleThread;
A integer value of the handle can be casted in the OSAL-implementation level. Because a pointer may use 8 Byte memory space in a 64-bit-system, the handle value can have up to 8 Byte, but it can have less too. Because the type is only forward-declared, may be never defined, only the handle value itself can be handled as input argument of the thread routines. That is sufficient.
Thread creation operation
/**Creates and starts a thread. * The thread will be end if the thread routine returns. * @param pHandle reference to a numerical identificator of the thread. * Note: all internal management data of the thread are stored os-internally. * @param routine Pointer to the thread routine, which is started. * @param pUserData This value will be transported to the first parameter of the user routine. * Typically it are the user instance data of the thread method. * @param sThreadName A textual identification for the operation system. * @param abstractPrio Priority between 1 (lowest) and 255 (highest). * Note: The opeation system may assign different abstract priorities * to the same real priority. It is not guaranteed, that such threads have different priorities. * @param stackSize The necessary size of stack. If 0 or -1,than the default stack size is used. * The default stack size is defined in the implementation and depends of the os-possibilities. * Note: Possible stack sizes depends on possibilities of the operation system. * It is a good style that the stack size should be in range of 2..16 kBytes, * the user shouldn't placed large data in stack. * Note, that interrupt handlers uses the stack also. * @return 0 if ok, negativ value on error. */ int os_createThread ( OS_HandleThread* pHandle , OS_ThreadRoutine routine , void* pUserData , char const* sThreadName , int abstactPrio , int stackSize );
This is the prototype definition in os_thread.h
The handle is a reference to the storage location, usual given with &myHandle
.
The routine is only the name of the proper defined thread routine. It is a function pointer in C language. The compiler checks
against the necessary function pointer type OS_ThreadRoutine
.
The type of user data should be matched to the casting type in the thread routine, see above. null
is admissible if the threadroutine does not use this argument because it knows its own static user data.
The thread's name is only important for internally reports and explorations of running threads in some operation system handling, for example show all active threads.
The priority choice is regarded to a sufficent range of possible priorities. A multithreading opeation system may support for example only 8 priority levels. Then the user level should be mapped to these levels, for example priority 0x80...0x9F till level 4. The fine distinction between priorities is the thing of the thread organization in the special src (see image above). There the level problematic should be known.
Last not least: The stack size. It is the problem child of any target and application. Often programmers don't know the used stacksize of there application. It is possible to measure it with
int stackSizeMax = getMaxStackDepth_ThreadContext_emC(_thCxt);
This routine is defined in emc/source/emC/ExcThCxtBase_emC.h
which is indirectly included in the applstdef_emC.h
. It can be called in any user routine, usual in the deepest one to estimate the size of the stack. But consider that interrupts
uses the stack too. The exakt calculation is not able to do. On the ARM processor technology there may be distinguish between
the APSR and the CPSR stack register (for privileged operations especially interrupts, and for normal processes), this defuses
it.
Topic:.os_thread..
OS_HandleThread os_getCurrentThreadHandle(void);
The result can be cast to int
to use it in special sources to access the thread on direct operation system level.
/**Changes the thread priority. */ int os_setThreadPriority(OS_HandleThread handle, uint abstractPrio);
Of course if a thread should run because a higher priority thread waits on a mutex, it gets the priority of the waiting thread. This mechanism is known as priority inversion. This needn't organizes by the user's software. The changing of the thread priority is selten really necessary.
/**Returns the priority of the given thread in the operation system's kind. * This is to compare priorities and for showing (in debug) */ int os_getOsThreadPriority(OS_HandleThread handle);
This is only for showing and checking in debug. The OSAL-using application should not deal with the operation system specific definitions. But it may be important to check whether 2 threads with different priorities (for example 128 and 129) have really the same priority. The number of different real priorities is usually less.
Topic:.os_thread..
The thread organization in the OSAL adaption needs an internally thread specific memory. The routines
/**Gets the user-thread-context of the current thread. The user-thread-context is a memory area, * assigned to any thread, which contains thread-local but routine-global data. * The structure of the user-thread-context can be defined in a user-adaption layer. * It should not be defined depending of the users algorithm, but in a common valid kind of users algorithm. * @return the pointer and the size of the users thread context. If the users threadcontext * was not set, the return structure contains {null, 0} */ PtrVal_MemUnit os_getCurrentUserThreadContext(); /**Sets the users thread context. This method can only be called one time for each thread. * @return error OS_UNEXPECTED_CALL if the users thread context is set already. */ int os_setCurrentUserThreadContext(OS_PtrValue mem);
deal with the ThreadContext which is used in ..