I am trying to write a small thread abstraction wrapper on top of the freeRTOS implementation and am testing it using the Windows port. I’m running into an issue which could be due to my use of Task Notifications but I’m unable to confirm.
This is my Create Thread Function
~~~
static void ThreadLauncher( void* arg )
{
KThread* pThread = ( KThread* )arg;
for( ;; ) {
TaskHandle
t hJoin;
ulTaskNotifyTake(pdTRUE, portMAXDELAY );
pThread->fn( pThread->arg );
KMutexLock( &pThread->joinMutex, WAIT_FOREVER );
hJoin = pThread->hJoinRequestTask;
KMutexUnlock( &pThread->joinMutex );
if( hJoin ) {
xTaskNotifyGive( hJoin );
}
vTaskDelete(NULL);
}
}
bool KThreadCreate( KThread* pThread, const KThreadCreateParams* pParams )
{
bool retval = false;
int err = 0;
if ( pThread &&
pParams &&
pParams->fn &&
pParams->pStack &&
pParams->stackSizeInBytes / sizeof( StackType
t ) > configMINIMALSTACK
SIZE ) {
StackTypet *pStack = ( StackType_t * ) pParams->pStack;
memset( pThread, 0, sizeof( KThread ) );
pThread->fn = pParams->fn;
pThread->arg = pParams->threadArg;
pThread->isComplete = false;
pThread->sanity = THREAD_SANITY_CHECK;
if ( pParams->pThreadName ) {
strncpy( pThread->threadName, pParams->pThreadName, sizeof( pThread->threadName ));
} else {
memset( pThread->threadName, 0, sizeof( pThread->threadName ));
}
if ( KMutexCreate( &pThread->joinMutex, NULL ) ) {
pThread->hTask = xTaskCreateStatic( ThreadLauncher,
pThread->threadName,
pParams->stackSizeInBytes / sizeof( StackType_t ),
pThread,
pParams->threadPriority,
pStack,
&pThread->task );
if ( pThread->hTask ) {
xTaskNotifyGive( pThread->hTask );
retval = true;
}
}
}
return retval;
}
~~~
The thread also creates a companion mutex which I’m using a part of my implementation to join. Since I’m storing the handle to the thread returned by xTaskCreateStatic (which is use in my wrapped ThreadLauncher function) I want to make sure that the created thread doesn’t really start running till pThread->hTask is properly set. Of
course this would mean a context switch when the created thread is of higher priority than the creating thread.
I try to accomplish the wait by
1. Having the created thread wait on Notification flag using xTaskNotifyTake( pdTRUE, portMAX_DELAY );
2. Give this notification in the creating thread using xTaskNotifyGive( pThread->hTask )
** I run into a problem when the creating thread is of higher priority. For some reason, the xTaskNotifyTake() completes even though the creating thread has not yet called xTaskNotifyGive(). The High priority thread runs to completion and when the lower priority (creating) thread resumes it gets blocked in the call to xTaskNotifyGive() specifically on the ReleaseMutex() call in the implementation to vPortExitCritical()**
I could use some help trying to figure out if what I did was wrong. I wrote a unit test which I’m running with embedded unit to test the problem ( the unit test runner itself runs in the freeRTOS task of the lowest priority, it is the first thread created before the task scheduler is launched) .
~~~
include <embUnit/embUnit.h>
include <ThreadInterface.h>
include <MutexInterface.h>
typedef struct
ThreadTest1Data
{
uint8t value;
KMutex mtx;
} Test1Data;
static Test1Data s_tst1 = { 0 };
static void TestThreadFunction( void *arg )
{
// usleep( 1000000 );
s_tst1.value = 1;
}
static void SetUp(void)
{
}
static void TearDown(void)
{
}
typedef struct
ThreadData
{
KThread thread;
uint8t stack[ 1024 ];
}ThreadData;
static ThreadData s
testThreadA;
static void ThreadApiTest(void)
{
KTHREADCREATE
PARAMS( stestThreadAParams,
“Test Thread”,
TestThreadFunction,
NULL,
s
testThreadA.stack,
sizeof( stestThreadA.stack ),
SEMANTIC
THREADPRIORITY
MID );
bool retval = KThreadCreate( &stestThreadA.thread, KTHREAD
PARAMS( stestThreadAParams ) );
TEST
ASSERT( retval );
retval = KThreadJoin( &stestThreadA.thread );
TEST
ASSERT( retval );
TESTASSERT( s
tst1.value == 1 );
retval = KThreadDelete( &stestThreadA.thread );
TEST_ASSERT( retval );
}
static void PremptTestThreadHi( void *arg )
{
KMutexLock( &s_tst1.mtx, WAIT_FOREVER);
s_tst1.value = 20;
KMutexUnlock( &s_tst1.mtx );
}
static void PremptTestThreadMid( void *arg )
{
uint32_t val = 0;
bool keepRunning = true;
while( keepRunning ) {
KMutexLock( &s_tst1.mtx, WAIT_FOREVER);
val = s_tst1.value;
KMutexUnlock( &s_tst1.mtx );
keepRunning = ( val == 0 ) ? true : false;
}
}
static ThreadData s
testThreadMid;
static ThreadData stestThreadHi;
static void TestBasicPremption( void )
{
memset( &s
tst1, 0, sizeof( stst1 ));
if( KMutexCreate( &s
tst1.mtx, “ThreadPremptTestMtx” )) {
stst1.value = 0;
KTHREAD
CREATEPARAMS( threadParamsMid,
“ThreadPremptTestMid”,
PremptTestThreadMid,
NULL,
s
testThreadMid.stack,
sizeof( stestThreadMid.stack ),
SEMANTIC
THREADPRIORITY
LOWEST);
KTHREADCREATE
PARAMS( threadParamsHi,
“ThreadPremptTestHi”,
PremptTestThreadHi,
NULL,
stestThreadMid.stack,
sizeof( s
testThreadMid.stack ),
SEMANTICTHREAD
PRIORITYHIGH );
if( KThreadCreate( &s
testThreadMid.thread, KTHREADPARAMS( threadParamsMid ) )) {
if( KThreadCreate( &s
testThreadHi.thread, KTHREADPARAMS( threadParamsHi ) )) {
TEST
ASSERT( KThreadJoin( &stestThreadMid.thread ) );
TEST
ASSERT( KThreadJoin( &stestThreadHi.thread ) );
TEST
ASSERT( KThreadDelete( &stestThreadMid.thread ) );
TEST
ASSERT( KThreadDelete( &stestThreadHi.thread ) );
}
}
KMutexDelete( &s_tst1.mtx );
}
}
TestRef KThreadTest
ApiTests()
{
EMBUNIT
TESTFIXTURES(fixtures) {
newTestFixture( “TreadApiTest”, ThreadApiTest ),
new_TestFixture( “BasicPremption”, TestBasicPremption )
};
EMB
UNITTESTCALLER( KThreadBasic, “KThreadBasic”, SetUp, TearDown, fixtures );
return (TestRef)&KThreadBasic;
}
~~~
I don’t think it matters but here is the rest of the implementation of the Thread Wrapper functions
~~~
bool KThreadJoin( KThread* pThread )
{
bool retval = false;
if( xTaskGetCurrentTaskHandle() != pThread->hTask ) {
if ( eTaskGetState( pThread->hTask ) != eDeleted ) {
//Wait to be notified when deleted
KMutexLock( &pThread->joinMutex, WAIT
FOREVER );
if( !pThread->hJoinRequestTask ){
pThread->hJoinRequestTask = xTaskGetCurrentTaskHandle();
retval = true;
}
KMutexUnlock( &pThread->joinMutex );
if( retval ) ulTaskNotifyTake(pdTRUE, portMAXDELAY );
} else {
retval = true;
}
}
return retval;
}
bool KThreadDelete( KThread* pThread )
{
bool retval = false;
if( pThread ) {
KThreadJoin( pThread );
pThread->hTask = 0;
pThread->sanity = 0;
KMutexDelete( &pThread->joinMutex );
pThread->hJoinRequestTask = 0;
retval = true;
}
return retval;
}
int32
t KThreadGetPriority( KThread* pThread )
{
return (int32t)uxTaskPriorityGet( pThread->hTask );
}
const char* KThreadGetName( KThread* pThread )
{
return pThread->threadName;
}
void vAssertCalled( unsigned long ulLine, const char * const pcFileName )
{
volatile uint32_t ulSetToNonZeroInDebuggerToContinue = 0;
/* Called if an assertion passed to configASSERT() fails. See
http://www.freertos.org/a00110.html#configASSERT for more information. */
/* Parameters are not used. */
( void )ulLine;
( void )pcFileName;
printf( “ASSERT! Line %d, file %srn”, ulLine, pcFileName );
taskENTER_CRITICAL();
{
/* You can step out of this function to debug the assertion by using
the debugger to set ulSetToNonZeroInDebuggerToContinue to a non-zero
value. */
while ( ulSetToNonZeroInDebuggerToContinue == 0 ) {
__asm { NOP };
__asm { NOP };
}
}
taskEXIT_CRITICAL();
}
/* configUSE
STATICALLOCATION is set to 1, so the application must provide an
implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
used by the Idle task. */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
/* If the buffers to be provided to the Idle task are declared inside this
function then they must be declared static – otherwise they will be allocated on
the stack and so not exists after this function exits. */
static StaticTask_t xIdleTaskTCB;
static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];
/* Pass out a pointer to the StaticTask_t structure in which the Idle task’s
state will be stored. */
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
/* Pass out the array that will be used as the Idle task’s stack. */
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
/* Pass out the size of the array pointed to by
ppxIdleTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
/———————————————————–*/
/* configUSE
STATICALLOCATION and configUSE
TIMERS are both set to 1, so the
application must provide an implementation of vApplicationGetTimerTaskMemory()
to provide the memory that is used by the Timer service task. */
void vApplicationGetTimerTaskMemory( StaticTaskt **ppxTimerTaskTCBBuffer, StackType
t **ppxTimerTaskStackBuffer, uint32t
pulTimerTaskStackSize )
{
/ If the buffers to be provided to the Timer task are declared inside this
function then they must be declared static – otherwise they will be allocated on
the stack and so not exists after this function exits. */
static StaticTask_t xTimerTaskTCB;
static StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];
/* Pass out a pointer to the StaticTask_t structure in which the Timer
task’s state will be stored. */
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
/* Pass out the array that will be used as the Timer task’s stack. */
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
Note that, as the array is necessarily of type StackType_t,
configMINIMAL_STACK_SIZE is specified in words, not bytes. */
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
void KThreadInit()
{
/* Start the scheduler so the demo tasks start to execute. */
vTaskStartScheduler();
/* vTaskStartScheduler() would only return if RAM required by the Idle and
Timer tasks could not be allocated. As this demo uses statically allocated
RAM only, there are no allocations that could fail, and
vTaskStartScheduler() cannot return – so there is no need to put the normal
infinite loop after the call to vTaskStartScheduler(). */
}
~~~
This is my unit test launcher
~~~
include<embUnit.h>
include <ThreadInterface.h>
extern TestRef PoolTest
ApiTests();
extern TestRef KThreadTestApiTests();
extern TestRef PriorityWakeTest();
extern TestRef PriorityDonateChainTest();
define TESTRUNNERSTACK_SIZE ( 1 << 14 )
static uint8
t stestRunnerThreadStack[ TEST
RUNNERSTACK
SIZE ];
static KThread stestRunnerThread;
static void TestRunner( void* arg )
{
TestRunner
start();
{
TestRunnerrunTest( PoolTest
ApiTests() );
TestRunnerrunTest( KThreadTest
ApiTests() );
TestRunnerrunTest( PriorityWakeTest() );
//TestRunner
runTest( PriorityDonateChainTest() );
}
TestRunnerend();
for ( ; ; );
}
int main (int argc, const char* argv[])
{
KThreadCreateParams param = {
.pThreadName = “TestRunnerThread”,
.threadPriority = SEMANTIC
THREADPRIORITY
LOWEST,
.threadArg = NULL,
.pStack = stestRunnerThreadStack,
.stackSizeInBytes = TEST
RUNNERSTACK
SIZE,
.fn = TestRunner
};
KThreadCreate( &stestRunnerThread, ¶m );
KThreadInit();
}
~~~
The complete code with build scripts can be found
here in case it is useful.
Again thanks a lot for the help