rtel wrote on Tuesday, July 09, 2019:
Richard, I have some concerns about the solution you posted.
Thanks for your feedback.
- It only fixes the problem for the yield interrupt. If any other
simulated interrupt causes a context switch, the suspended thread
could continue to run for a while after the call to
vPortGenerateSimulatedInterrupt.
Skipping this one for now as will need more thought.
[edit1] This is basically the same case as a tick interrupt resulting in a context switch. When making the changes posted above I decided not to consider the tick as a critical case as a tick interrupt can occur at any time - and therefore if the context switch (the task being suspended) occured a little late it would not cause a fatal error because the task being interrupted is unaware of it anyway. This is a very different scenario to a task yielding because it is entering the blocked state - if a task fails to enter the blocked state because it is yielding then there will be logic errors in the program that are critical. For example, if a task is blocking on a queue but continues past the block point then the queue logic won’t work and anything might happen. The Win32 port is not ‘real time’ in any case, but an approximation that should behave logically the same, but not necessarily temporaraly the same.
- You’re using ulPendingInterrupts to check if you should wait on the
yield event object AFTER releasing the mutex. If the simulated
interrupt handler preempts the thread and clears the flags before
you reach the check, the task will never actually wait on the event.
You should either make a local copy before releasing the mutex, or
create a boolean indicating that the thread should wait on the event.
Can you please give me a line number (from the file I posted).
[edit 1] Fixed.
- Why do you wait on the event in a loop with a 0 timeout? You can
just use ResetEvent, right?
…because I didn’t know about ResetEvent(), will change.
- The fix assumes that the thread calling
vPortGenerateSimulatedInterrupt or vPortExitCritical is the
currently scheduled FreeRTOS task. If it’s a normal Windows thread
instead, it will access the yield event of the currently scheduled
FreeRTOS task,
If I understand you correctly here - that you are describing something
it is not valid to do. Normally scheduled windows threads cannot access
FreeRTOS scheduler operations, and Windows threads that are running
FreeRTOS tasks cannot make Windows system calls - the two are just not
compatible logically.
which could cause it to unblock that FreeRTOS task
early. This is why I used a thread-local storage slot in my
solution. It allows you to access the thread state of the currently
executing thread (not just the currently scheduled thread), which
ensures you’re using the correct yield event object. And, in the
case of a normal Windows thread, it will be NULL, so you know not to
wait on an event. It also provides an easy way to check if a thread
is running when it shouldn’t be (the thread state is different than
pxCurrentTCB).
Maybe I’m just not following this properly, but doing this kind of thing
could be the route cause of your issues, and be why I can’t replicate them.
- There is no need to duplicate the yield event waiting code in
vPortGenerateSimulatedInterrupt and vPortExitCritical. Just wrap the
code inside vPortGenerateSimulatedInterrupt with
vPortEnterCritical/vPortExitCritical instead of manually taking the
mutex.
- You don’t call CloseHandle on the yield event when deleting the thread.
Will fix.
In regards to calling Windows system calls from a FreeRTOS task. I know
your official position is to just don’t do that, but at the same time
you break this rule in your own demos.
In the standard demo we get away with this because we write to stdout
very infrequently and from only one task. You will note that the call
to kbhit() is normally commented out, because that causes issues too.
The TCP/IP examples on the other hand write to the console rapidly, so
we don’t use printf() at all as it will soon crash if we do. Instead we
send the strings to print to a Windows thread that is not under the
control of the FreeRTOS kernel and print them out from there. In that
case the strings are sent in a circular RAM buffer to avoid sharing any
FreeRTOS primitives with the Windows thread. I think the thread running
kernel code does signal the Windows thread somehow (forget how) but in
non blocking way.
From what I can tell, it’s not
safe to call any external function (even standard c library functions)
that could make a system call, without first entering a critical
section.
Entering a critical section may work, but I don’t know enough about the
inner workings of Windows to know.
Otherwise, the FreeRTOS scheduler could try to suspend the
thread during the syscall, which can cause SuspendThread to fail (or not
suspend immediately). If it actually does suspend the thread
successfully, it can cause a deadlock if another thread calls an system
function before the first has a chance to finish (if the first had
acquired a mutex before being suspended).
I suspect that SuspendThread can fail even when the program does not
call system functions from FreeRTOS threads, because the Windows port
itself does so. Namely, when entering/exiting a critical section, it
acquires and releases the interrupt mutex. If the scheduler tries to
suspend the thread at the wrong point inside WaitForSingleObject or
ReleaseMutex, it can cause the call to SuspendThread to either fail to
suspend the thread immediatly, or in some cases, fail to suspend the
thread at all. That’s why it is important to block the FreeRTOS task on
an event inside vPortExitCritical if any interrupt is pending, in case
the interrupt causes a context switch and SuspendThread fails.
Again need to consider this point more.
From my testing, with my fix in place and with all system calls wrapped
in a critical section, we can safely interact with standard Windows
threads, without relying on lockless data structures or using the
Windows thread priority as synchronization mechanism. We can safely call
taskENTER_CRITICAL/taskEXIT_CRITICAL from Windows threads to synchronize
access to memory that is shared with a FreeRTOS task. We can interract
with Windows threads from FreeRTOS tasks by calling Windows API
functions from a critical section. We can interract with FreeRTOS tasks
from Windows threads using the ISR FreeRTOS api functions.
I only tried the code posted by davidefer - which for me locked up right
away, I think because the trace recorder code was calling critical
sections from inside the interrupt. I need to study your code in more
detail as it sounds like it has some goodness - I did diff your file
with the released code over the weekend but there were too many changes
to look through.
Good discussions -