Content switch after ISR

oliver-fhg wrote on Thursday, August 16, 2018:

Dear community,

I am using non-preemptive scheduling but I am not quite sure if I understand what happens in the case of an interrupt. When an interrupt occurs, the active task becomes suspended and the ISR is executed. Immediately after the interrupt was handled, the suspended task will become active again and the execution continues. Is this correct or can it happen, that another task becomes active immediately after interrupt handling (before the execution of the suspended task is continued)?

I am using a Cortex-M4 port.

Kind regards

richarddamon wrote on Thursday, August 16, 2018:

It depends on how you write your ISR. Most of the examples have the ISR test the ‘wasWoken’ flag, and the conditioally to a scheduler call. This form assume that you want a pre-emptive system. If you want a non-preemptive system then your ISRs should not trigger the scheduler, so you stay cooperative.

oliver-fhg wrote on Friday, August 17, 2018:

So in other words you are saying: A non-preemtpive system remains non-preemptive if there is no ISR (and no ISR callback) which calls a function from cmsis_os.h, right?

richarddamon wrote on Friday, August 17, 2018:

One quick point, cmsis_os.h is NOT a piece of the official FreeRTOS distribution, but is something added by some other distribution, so would not be part of the definition of how FreeRTOS itself works.

FreeRTOS itself has a define in FreeRTOSConfig.h, USE_PREMPTION which controls how the system tick interrupt acts, weither it will cause task premption or not based on that interrupt. It it the reposibility on the programmer to implement that same decision in user provided ISRs too.

It if fine for the ISRs to use features of FreeRTOS, using semaphores, queues, and such and remain non-premptive. All the FromISR functions will use the ‘wasWoken’ flag to indicate that the ISR should initiate the scheduler (IF premption is desired). It is the ISRs job to perform that if desired.

oliver-fhg wrote on Wednesday, August 22, 2018:

So if I understood you correctly, the user can request a context switch from within an ISR by using functions which are appropriate and have the suffix “FromISR”, e.g. I can call

xTaskGenericNotifyFromISR(..., &xHigherPriorityTaskWoken);

to set the woken flag (if appropriate) and then execute

if (preemptionIsDesiredByUser)

to initiate the scheduler if preemption is desired.

  1. Is the statement above correct?
  2. When exactly is the context switch performed, if the ‘wasWoken’ flag is set?
  3. If I call " portYIELD_FROM_ISR" from within an ISR, non-preemption is destroyed, right?
  4. I am referring to Figure 48 of the freeRTOS hands on tutorial guide:
    If I want to implement strict non-preemptive behaviour, and I want to use Deferred Interrupt Processing I am still wondering about the following things:
    a) Will Task1 enter the blocked state automatically if an interrupt occurs?
    b) When I unblock Task2 from within the ISR, what means do I have to assure it will be executed immediately after the ISR? Can this work if Task1 and Task2 have equal priority?
    c) After Task2 has been completed, how can I assure that the interrupted task (Task1) will continue its execution before any other task is being executed?

richarddamon wrote on Wednesday, August 22, 2018:

  1. sort of. most FROMISR calls will not make a preemption happen, but only set the wasWoken flag. It is the portYIELD_FROM_ISR that causes the preemption.
  2. It depends on the port. On some (which don’t allow interrupt nesting), the call to portYIELD_FROM_ISR() does the context switch and execution immediately continus with the new task. The rest of the code in the ISR doesn’t run until the task that was interupted is brought back to be the running task (so it is vital that it be the last statement of the ISR). On others the call sets a request for a bottom priority interrupt, so the actual switch happens when the ISR exits and that interrupt is processed.
  3. Yes. portYIELD_FROM_ISR() acts just as if the interrupt task had done a yied at the (arbitrary) point it was running at.
  4. Figure 48 is presuming preemption is enabled. Much of the documentation presumes that because most of the benefits of a real time system are lost in a non-preemptive system, and considering it complicates a lot of the discussion.
    4a) You are misusing (or misunderstanding) the states of a task. Tasks have several states, including Running, Ready, and Blocked. Running is the one currently running task, Ready are all other tasks that are capable of becoming the Running Task, and Blocked are tasks that are not currently ready, but needing something to happen (maybe just time) before they will be ready. When the interrupt occurs, the state of the task doesn’t change (you just are not running in the ‘task’ context for the moment, but any operation that affects the ‘current task’ will affect the interrupt task). When the Yield that happens from executing the portYIELD_FROM_ISR(), the currently running task (the one that was interrupted) moves from Running to Ready, and then the highest priority Ready task is selected and moved to the Running state. So Task1 didn’t go to ‘Blocked’ but probably to ‘Ready’ state (instead of Running).
    b) What task runs is TOTALLY determened by ‘Highest Priority Ready Task’ at the point the Yield occurs, with the tie breaker for same priority being the order in the priority queue, with the yielding task moving to the back of the queue, so it will be the task that hasn’t had a chance for the longest time. You can not ‘chose’ which task to run except by this rule. Note that if you have another Ready task at the same priority of the Running task, (and no higher priority Ready task) a Yeild will switch to the other task as it will be before it in the queue, but if there are more than 1 of them, which one runs is based on the above rule.
    c) You can’t except by making sure Task1 is the then Highest Priority Ready Task.

As I said before, you really can’t mix Preemption and Non-Preemption in the task system.

You could possibly put your cooperative tasks all on priority 1, and when they start move themselves to priority 2, but everytime before executing an operation that might block, drop back to priority 1 and then back to 2 after the operation (this will then become a possible pre-emption point). Your defered execution task could then be at priority 3, and it will always be the highest priority task when it is unblocked. Any mistake in doing this will break the cooperative assumption. It may be easier to just fix your code to take into account data sharing and use the appropriate measures to allow for preemption.