A couple questions related to critical sections and ulCriticalNesting, w.r.t. FreeRTOS 8.1.x specifically:
task.h's documentation for taskENTER_CRITICAL states:
Macro to mark the start of a critical code region. Preemptive context
switches cannot occur when in a critical region.
What is the exact definition of "Preemptive context switches"? In this context, I believe that it solely refers to context-switches induced by a timer interrupt. In other words, application-initiated context switches are not included in the definition, and hence if an application calls FreeRTOS APIs that might yield, block or change a thread's priority, context-switching might still occur.
In particular, invocation of APIs such as taskYIELD, vTaskPrioritySet, vTaskDelayUntil, xTaskCreate, (and more) can all cause context switches even inside a critical section. Can you confirm this?
Is it true that all ports must context-switch the ulCriticalNesting value, either directly as part of portSAVECONTEXT/portRESTORECONTEXT, or by setting portCRITICALNESTINGINTCB=1, and then likely using vTaskEnter/ExitCritical to implement portENTER/EXITCRITICAL?
I believe this is true, since the "IRQ disabled" state is intended to be a per-task setting, i.e. the following represents how critical sections and interupt masking is intended to wokk:
Task 1 enters a critical section. Interrupts are now disabled.
Task 1 yields or blocks.
Task 2 is selected by the scheduler.
Task 2's context is restored. This (probably) causes interrupts to be re-enabled (depending on task 2's previous state).
Task 2 yields, blocks, or is pre-empted.
Task 1 is selected by the scheduler.
Task 1's context is restored. This causes interrupts to be disabled again, since this is part of task 1's context.
For this to work, I believe that ulCriticalNesting must always be context-switched along with other application state. Otherwise, if Task 2 above were to enter and exit a critical section ulCriticalNesting wouldn't reach 0, and hence interrupts wouldn't be re-enabled when its critical section was left.
Background: I ported FreeRTOS to my CPU, and copied code from a port that didn't context switch ulCriticalNesting. I created an application that ran various of the FreeRTOS demo tasks. I found that pretty soon, the only task running was a CPU-bound thread, and no pre-emption was taking place. Once I fixed the port to context-switch ulCriticalNesting, everything worked as I expected. I believe the issue is exactly as I outlined in the previous two paragraphs. However, a colleague disagrees, believing the issue is that the real fix is to make portYIELDWITHINAPI use a SW interrupt rather than SVC call.
Assuming that ulCriticalNesting must be context-switched, I don't understand how some ports are doing so.
For example, Source/portable/CCS/ARMCortex-R4/. Its implementation of portDISABLE/ENABLEINTERRUPTS uses ulCriticalNesting, yet nothing in portASM.asm seems to actually context-switch it.
Thanks for any help!
[Which architecture did you port to? Have you uploaded your port to the FreeRTOS Interactive site?]
There is no one answer - it is completely dependent on the port. Different CPU architectures have different characteristics, features and restrictions, so the port works with what the architecture happens to provide.
Generally, older or simpler architectures will context switch the critical nesting count because they do context switch from within a yield in some synchronous way (trap, function call, whatever) - where ever that yield is made. Newer or more complex architectures will often use an asynchronous pended interrupt (or software pend) which means they will not context switch from within a critical section, but will context switch immediately that a critical section is exited if the context switch is pending at that time. When a context switch cannot occur in a critical section there is no point saving the nesting depth as part of the task's context.
The core (non portable layer) FreeRTOS code is written to function correctly either way.
Back to your first question - a preemptive context switch is any context switch that is not requested directly by a task - in other words, any context switch that the task cannot predict is going to happen (hence the task is preemptive). Any interrupt can cause that, not just a timer interrupt.
I'm running on both an ARM7TDMI and an ARM Cortex R5 with different ports. The ports aren't uploaded to the FreeRTOS interactive site at present; that would require some negotiations with lawyers which hasn't happened yet; we're not distributing the ports outside the organization in either source or binary form at present.
In the port I made, portYIELD maps to a SWI/SVC instruction; a synchronous exception. FWIW, IIRC I based this port on Source/portable/GCC/ARM7_AT91FR40008 simply because that was the first GCC-based port in the directory listing! It sounds like in this case, ulCriticalNesting must be context-switched, since yielding is immediate.
I assume you'll tell me that in the ARM Cortex R4 port I mentioned (Source/portable/CCS/ARM_Cortex-R4/), yielding is implemented by a SW interrupt (that is masked inside a critical section) rather than an exception, hence the switch is deferred until after the critical section is executed?
You would actually be better off copying the FreeRTOS/Source/portable/GCC/ARM_CA9 code, maybe barring the interrupt controller code in that port (which may or may not be ok for your chip depending on whether it is using a GIC or not).
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.