C++ BaseTask for setting up xTaskCreateStatic and calling a client implemented functions via a single static function

nasher128 wrote on Sunday, June 09, 2019:

Hi all.

I’m currently making my own simple C++ wrapper around FreeRTOS and I’m trying to design it in such a way where I only need the client to implement a virtual function (I’ve called this runnable()) which is called by xTaskCreateStatic. Because
xTaskCreateStatic requires a static function to be passed in, if I create this function in my base task and pass in the “this” pointer to the function for casting to the derived class, everything works as expected.

However, I have a problem where if I create two objects of the same (e.g. two SensorTasks objects which implement the virtual function I’ve described), only the second object’s virtual function is being called. I think this is down to I’m passing the same static function address within my BaseClass to both xTaskCreateStatic() calls, making the second call overwrite the first.

richarddamon wrote on Sunday, June 09, 2019:

I have a similar wrapper at https://github.com/richard-damon/FreeRTOScpp

What I did is use the void* pointer parameter for the create task function to point to the base object that implements the task. The static function then casts the void* parameter into the right object pointer type, and uses it to call the virtual function. Yes, this means that your task can’t use that parameter for itself, but it does have the object it was part of instead.

stackmaster wrote on Saturday, June 22, 2019:

That is precisely the same model that I use, on multiple OS’s, and it works well.

That said, I am a newbie to FreeeRTOS, and I am worried about how this C++ model interacts with xTaskCreate. Does xTaskCreate have a “create suspended” flag?

Consider the following scenario:

  1. We create a C++ wrapper for a thread class object that uses xTaskCreate or xTaskCreateStatic.
  2. When the thread starts to run, as stated by Richard Damon, the static function does a reinterpret_cast<> to convert the void pointer argument to the function to be a pointer to the base class of the C++ thread wrapper.
  3. The static function then invokes pCThreadObject->runnable, as implied by nasher.
  4. pCThreadObject->runnable starts immediately doing whatever it wants to do, right-away.
  5. Some programmer derives a new class from CThreadObject and overrides CThreadObject::runnable.

Now we see a problem:

Upon construction of CThreadObject, the static running thread will attempt to invoke CDerivedObject::runnable instead of CThreadObject::runnable . CDerivedObject::runnable will think that it is OK to operate against the portion of the aggregate object that pertains only to CDerivedObject . Yikes! Race-condition! The program will likely crash.

The most elegant solution to this problem is to allow xTaskCreate or xTaskCreateStatic to specify that the new thread is to be created suspended. The new threaad should not be allowed to run at all until vTaskResume is invoked. There are other work-arounds that can be done by the programmer, but this is the simplest solution.


Does FreeRTOS allow a thread to be created in a suspended state?

richarddamon wrote on Saturday, June 22, 2019:

Tasks can not be created in a suspended state, but you can create the task at a lower priority than yourself, suspend it, then move it to a higher priority if desired. My wrapper, if used when the scheduler is running, will make sure that the created task is of lower priority than the creating task, so that the constructor can finish before the task is called (this does require certain options to be enabled, if they aren’t then it assumes the caller has done what is needed to make sure things happen correctly),

If the task was told to be created at a higher priority than the task creating it, it will be created at a lower priority, and once it executes, it will move its priority to what is desired.

stackmaster wrote on Monday, June 24, 2019:

Just curious: Are you taking care of the new race-condition that could arise where:

  1. Spawning thread extracts its priority P7.
  2. Spawning thread prepares to spawn spawned thread at lower priority than P6 < P7…
  3. Spawning thread is preempted.
  4. Some other thread changes priority of spawning thread to, say, P4 < P6.
  5. Spawning thread resumes and spawns spawned thread at priority P6.
  6. Spawned thread runs…etc…crash.

Yes, this is very inlikely, but…I was curious about what you thought about it.