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( ;; ) {
TaskHandlet 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( StackTypet ) > configMINIMALSTACKSIZE ) {
StackTypet *pStack = ( StackTypet * ) pParams->pStack;
memset( pThread, 0, sizeof( KThread ) );
pThread->fn = pParams->fn;
pThread->arg = pParams->threadArg;
pThread->isComplete = false;
pThread->sanity = THREADSANITYCHECK;
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( StackTypet ),
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
include
include
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 stestThreadA;
static void ThreadApiTest(void)
{
KTHREADCREATEPARAMS( stestThreadAParams,
"Test Thread",
TestThreadFunction,
NULL,
stestThreadA.stack,
sizeof( stestThreadA.stack ),
SEMANTICTHREADPRIORITYMID );
bool retval = KThreadCreate( &stestThreadA.thread, KTHREADPARAMS( stestThreadAParams ) );
TESTASSERT( retval );
retval = KThreadJoin( &stestThreadA.thread );
TESTASSERT( retval );
TESTASSERT( stst1.value == 1 );
retval = KThreadDelete( &stestThreadA.thread );
TEST_ASSERT( retval );
}
static void PremptTestThreadHi( void *arg )
{
KMutexLock( &stst1.mtx, WAITFOREVER);
stst1.value = 20;
KMutexUnlock( &stst1.mtx );
}
static void PremptTestThreadMid( void *arg )
{
uint32t val = 0;
bool keepRunning = true;
while( keepRunning ) {
KMutexLock( &stst1.mtx, WAITFOREVER);
val = stst1.value;
KMutexUnlock( &s_tst1.mtx );
keepRunning = ( val == 0 ) ? true : false;
}
}
static ThreadData stestThreadMid;
static ThreadData stestThreadHi;
static void TestBasicPremption( void )
{
memset( &stst1, 0, sizeof( stst1 ));
if( KMutexCreate( &stst1.mtx, "ThreadPremptTestMtx" )) {
stst1.value = 0;
KTHREADCREATEPARAMS( threadParamsMid,
"ThreadPremptTestMid",
PremptTestThreadMid,
NULL,
stestThreadMid.stack,
sizeof( stestThreadMid.stack ),
SEMANTICTHREADPRIORITYLOWEST);
KTHREADCREATEPARAMS( threadParamsHi,
"ThreadPremptTestHi",
PremptTestThreadHi,
NULL,
stestThreadMid.stack,
sizeof( stestThreadMid.stack ),
SEMANTICTHREADPRIORITYHIGH );
if( KThreadCreate( &stestThreadMid.thread, KTHREADPARAMS( threadParamsMid ) )) {
if( KThreadCreate( &stestThreadHi.thread, KTHREADPARAMS( threadParamsHi ) )) {
TESTASSERT( KThreadJoin( &stestThreadMid.thread ) );
TESTASSERT( KThreadJoin( &stestThreadHi.thread ) );
TESTASSERT( KThreadDelete( &stestThreadMid.thread ) );
TESTASSERT( KThreadDelete( &stestThreadHi.thread ) );
}
}
KMutexDelete( &s_tst1.mtx );
}
}
TestRef KThreadTestApiTests()
{
EMBUNITTESTFIXTURES(fixtures) {
newTestFixture( "TreadApiTest", ThreadApiTest ),
new_TestFixture( "BasicPremption", TestBasicPremption )
};
EMBUNITTESTCALLER( 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, WAITFOREVER );
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;
}
int32t 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 );
taskENTERCRITICAL();
{
/* 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 };
}
}
taskEXITCRITICAL();
}
/* configUSESTATICALLOCATION 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( StaticTaskt **ppxIdleTaskTCBBuffer, StackTypet **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 StaticTaskt xIdleTaskTCB;
static StackTypet uxIdleTaskStack[ configMINIMALSTACKSIZE ];
/* 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 StackTypet,
configMINIMALSTACKSIZE is specified in words, not bytes. */
*pulIdleTaskStackSize = configMINIMALSTACK_SIZE;
}
/-----------------------------------------------------------*/
/* configUSESTATICALLOCATION and configUSETIMERS 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, StackTypet **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 StaticTaskt xTimerTaskTCB;
static StackTypet uxTimerTaskStack[ configTIMERTASKSTACK_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 StackTypet,
configMINIMALSTACKSIZE is specified in words, not bytes. */
*pulTimerTaskStackSize = configTIMERTASKSTACKDEPTH;
}
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
include
extern TestRef PoolTestApiTests();
extern TestRef KThreadTestApiTests();
extern TestRef PriorityWakeTest();
extern TestRef PriorityDonateChainTest();
define TESTRUNNERSTACK_SIZE ( 1 << 14 )
static uint8t stestRunnerThreadStack[ TESTRUNNERSTACKSIZE ];
static KThread stestRunnerThread;
static void TestRunner( void* arg )
{
TestRunnerstart();
{
TestRunnerrunTest( PoolTestApiTests() );
TestRunnerrunTest( KThreadTestApiTests() );
TestRunnerrunTest( PriorityWakeTest() );
//TestRunnerrunTest( PriorityDonateChainTest() );
}
TestRunnerend();
for ( ; ; );
}
int main (int argc, const char* argv[])
{
KThreadCreateParams param = {
.pThreadName = "TestRunnerThread",
.threadPriority = SEMANTICTHREADPRIORITYLOWEST,
.threadArg = NULL,
.pStack = stestRunnerThreadStack,
.stackSizeInBytes = TESTRUNNERSTACKSIZE,
.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
Hi Kartik,
I'd like to confirm that the Windows simulation of the scheduler is very realistic. It was tested with the task-notify functionality, all queue-types, mutexes and semaphores, and also with the event-groups.
The usage and behaviour of xTaskNotifyGive()
/ ulTaskNotifyTake()
is exactly the same as when your code is running on an embedded system.
Richard is (as usually) completely right: thou shall not call any (Windows) system API from a FreeRTOS task. Think of stdio and the Windows thread- and process-related functions. It may happen then that two tasks will start running simultaneously.
Suppose a task calls printf("Hello world\n")
. That function will call a Windows API and the API-call will put the thread asleep. The scheduler will start another task and while that runs, the API may return. At that moment, two tasks will be active.
At least in your published code, I don't see Window API calls.
I extended your test function as:
~~~
static void TestThreadFunction( void *pvArgument )
{
BaseTypet xIndex;
SemaphoreHandlet xSemaphore;
s_tst1.value = 1;
xSemaphore = xSemaphoreCreateBinary();
vLoggingPrintf( "TestThreadFunction started\n" );
for( xIndex = 1; xIndex < 4; xIndex++ )
{
xSemaphoreTake (xSemaphore, 1000);
vLoggingPrintf( "TestThreadFunction: %d\n", xIndex );
}
vSemaphoreDelete( xSemaphore );
}
~~~
just to see some sign of live.
vLoggingPrintf()
sends logging to stdout: this is being done from within a real Windows thread.
The communication between a FreeRTOS task and a Windows thread is very limited: threads know nothing about semaphores, while tasks are not supposed to use Windows mutexes.
I ran a bit of your code and what I saw happen is the following:
1) The creating task has a higher priority:
~~~
Time Message
147 [Main-task] xTaskCreateStatic()
147 [Main-task] xTaskNotifyGive start
147 [Main-task] xTaskNotifyGive end
147 [Main-task] KThreadJoin start
147 [Test Thread] ulTaskNotifyTake start
147 [Test Thread] ulTaskNotifyTake end
147 [Test Thread] TestThreadFunction started
1147 [Test Thread] TestThreadFunction: 1
2147 [Test Thread] TestThreadFunction: 2
3147 [Test Thread] TestThreadFunction: 3
3147 [Main-task] KThreadJoin end
3147 [Main-task] KThreadDelete start
3147 [Main-task] KThreadDelete end
3147 [Test Thread] Deleting task
~~~
2) The creating task has a lower priority:
~~~
Time Message
153 [Main-task] xTaskCreateStatic()
153 [Test Thread] ulTaskNotifyTake start
153 [Main-task] xTaskNotifyGive start
153 [Test Thread] ulTaskNotifyTake end
153 [Test Thread] TestThreadFunction started
153 [Main-task] xTaskNotifyGive end
153 [Main-task] KThreadJoin start
1153 [Test Thread] TestThreadFunction: 1
2153 [Test Thread] TestThreadFunction: 2
3153 [Test Thread] TestThreadFunction: 3
3153 [Test Thread] Deleting task
3154 [Main-task] KThreadJoin end
3154 [Main-task] KThreadDelete start
3154 [Main-task] KThreadDelete end
~~~
Please realise that if you xTaskCreate()
a low-priority task, that task will not run until the creator goes in a blocking state. This only happens at KThreadJoin()
.
If you xTaskCreate()
a high-priority task, that new task will run immediately, and xTaskCreate()
will only return when the new task goes in a bocking state, which happens at ulTaskNotifyTake()
.
As far as I can see there is nothing wrong with the Windows implementation of the task-notify functions. Regards.