In freeRTOS 5.1.12, there is a taskYIELD() inside a critical section(portENTER_CRITICAL) in function xTaskResumeAll. Is that ok? If disabling the interrupts before switching the context, who will preempt the new running task?
I really need to add this to the FAQ!
Yes this is perfectly OK everywhere the scheduler does it, although i don’t recommend doing this in the application code unless you fully understand the consequences.
Each task maintains its own flags value so a task can yield from a critical section to a task that is not in a critical section - the original idea being that when the original task started to run again it would start from within a critical section and therefore have guaranteed access to the data structures. This reason has actually since disappeared but it is still safe.
Maybe I’m missing the point here, but when you enter a critical section, uxCriticalNesting is incremented and the interrupts are disabled. The interrupts being disabled, no timer interrupt will give the kernel the tick. This means the current task(the one becoming active after yield) will run for an indefinite amount of time without being preempted.
What you are missing is that as part of the context switch caused by the yield will re-enable the interrupts (unless the new task was also in a critical section) as uxCriticalNesting is stored on a per TASK basis.
Well, first, I should say that my architecture won’t store the General Interrupt Flag in the Program Status Word, thus this flag will not be saved/restored at a context switch.
More, nowhere in the example ports did I see that uxCriticalNesting is stored on a per task basis. Could this feature be introduced later from the V5.1.2 or so?
So I am still puzzled by this yield inside critical section.
Ok, it is clear to me now, as I have inspected ‘tasks.c’ and found this:
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
unsigned portBASE_TYPE uxCriticalNesting;
So I’ll just have to redesign a little my context switch functions(timer interrupt and yield ‘function’) to use this feature.
In most cases the critical nesting count is stored on the stack of the task, but in newer ports it is stored as part of the TCB.
Also, not wanting to add confusion to the discussion, but some ports now don’t actually yield until they leave the critical section. This part is very port specific.
As I have already said in a missposted post: https://sourceforge.net/forum/message.php?msg_id=6885683
If you have the nesting count then you don’t need the status word as well, just look at the nesting count value to know if you are in a critical section or not.
Which architecture are you using?
Well, I am trying to port freeRTOS to NXP 51XA(8051 eXtended Architecture). Upon interrupt, the stack contains the PSW(program status word), PC(program counter), and eventually, the CS(Code segment), if using the non-page zero mode. Non-page zero mode means the controller will use more than 64KB of RAM and more that 64KB of code. I won’t go into much detail here, just for reference.
Anyway, if using the uxCriticalNesting inside the TCB, another problem arises: Who will set the member variable uxCriticalNesting to 0? Because, if it is not set to zero, the start count will not be consistent with the interrupts enable/disable state.
>>Who will set the member variable uxCriticalNesting to 0?
if you have set "portCRITICAL_NESTING_IN_TCB==1" then in tasks.c file, "prvInitialiseTCBVariables" function will take care of it.
Yeah, that’s right. Looked for something like this in the source code before asking the question, but found nothing. Looked again now, it’s there. I should concentrate my attention better.
Another problem found in xTaskCreate:
if( uxCurrentNumberOfTasks == ( unsigned portBASE_TYPE ) 1 )
/* As this is the first task it must also be the current task. */
pxCurrentTCB = pxNewTCB;
well, my implementation of portENTER_CRITICAL() is on a per-task basis(portCRITICAL_NESTING_IN_TCB == 1), so uxCriticalNesting is contained within the TCB. Well, pxCurrentTCB is not initialized at the point portENTER_CRITICAL() is called here. So my implementation of portENTER_CRITICAL() will alter some RAM zone which is not a TCB.
I saw in the source code that there are two forms of entering critical: portENTER_CRITICAL and taskENTER_CRITICAL. Unfortunately, both refer to the same function/code snippet. Why not split them to have one of them(taskENTER_CRITICAL) for tasks(TCB based) and the other(portENTER_CRITICAL) for the whole system(global uxCriticalNesting)?
Of course, my implementation of portENTER_CRITICAL could use both a global variable or the field from TCB, based on the scheduler state, but that would increase code size and timing.
The idea is (was) that user code does not call anything in the port layer directly, only the task and queue API. Therefore user code should call taskENTER_CRITICAL() not portENTER_CRITICAL(), although the two are mapped to the same code so in reality it makes no difference unless you switch code from one port to another.
When portCRITICAL_NESTING_IN_TCB is set to 1 then the enter/exit critical section macros should map to vTaskEnterCritical() and vTaskExitCritical() [defined in task.c]. These functions will only attempt to access pxCurrentTCB if the scheduler is running to remove the risk of them accessing it before it have been set to point to a valid TCB structure.