API calls from ISR while the scheduler is suspended

The API reference for vTaskSuspendAll() states that:

Other FreeRTOS API functions must not be called while the scheduler is suspended.

API functions that have the potential to cause a context switch (for example, vTaskDelayUntil(), xQueueSend(), etc.) must not be called while the scheduler is suspended.

As far as I understand, vTaskSuspendAll() leaves interrupts enabled. So I was wondering what happens if an ISR calls an API function while the the scheduler is suspended. For example, an ISR may call xQueueSendFromISR(), which may cause a context switch (after the scheduler is resumed of course). I assume that the task calling vTaskSuspendAll() has no control over this ISR and the ISR can be triggered by some hardware even when the scheduler is suspended.

Exactly. That’s the point using vTaskSuspend/ResumeAll() for task level critical sections instead of taskENTER/EXIT_CRITICAL() if desired.
See also the API doc of vTaskSuspendAll for further details.

So it’s safe for ISR functions to call *FromISR() functions while the scheduler is suspended, right? I was assuming the same, but the API reference doesn’t mention about this exceptional situation and simply says “No API calls are allowed while the scheduler is suspended”, so I was confused. Maybe the API reference needs to be updated to mention this exception.

void vTaskSuspendAll( void );

Suspends the scheduler. Suspending the scheduler prevents a context switch from occurring but leaves interrupts enabled. If an interrupt requests a context switch while the scheduler is suspended, then the request is held pending and is performed only when the scheduler is resumed (un-suspended).

from the API doc explains it (to me).
Using this feature for critical sections is more costly than interrupt-masking based critical sections. On the other hand it minimizes the response time jitter of interrupts/ISRs.

Yes it’s safe to call *FromISR functions from an ISR while the scheduler is suspended. The point about the FromISR calls is that they don’t try to do a task switch. The task switch (if any) would normally happen when the ISR completes, however if the scheduler is suspended then it will be deferred until vTaskResumeAll is called.

1 Like

vTaskSuspendAll()[1]/vTaskResumeAll()[2]can be used e.g. to protect a shared resource which is shared between tasks - still allowing interrupts to get through.

You call vTaskSuspendAll()[1]/vTaskResumeAll()[2] from a task and not from the ISR - right? Now, while in the vTaskSuspendAll()[1]/vTaskResumeAll()[2] section of that task an ISR comes along. You are not allowed to call blocking functions from an ISR, hence there are FromISR variations you need to use from an ISR and the ISR issues xQueueSendFromISR()[3].

Where is the message queue which receives the message from the ISR?

Say it’s in the task with a higher priority than the one which was interrupted by the ISR and this task currently sleeps waiting for a message in the message queue. As the doc says[3] " If xQueueSendFromISR() sets this value (pxHigherPriorityTaskWoken) to pdTRUE then a context switch should be requested before the interrupt is exited."

So I would assume that we don’t return from the ISR to the point where we came from (when taskYIELD_FROM_ISR () is invoked in the ISR), but after the ISR we proceed to the higher priority task which was awoken. Check the example in[3] how this could be done.

If taskYIELD_FROM_ISR () is not invoked we’ll return where we came from and the scheduler needs to come along and cause a context switch to proceed to the higher priority task which waits on the message in the message queue.

[1] The FreeRTOS vTaskSuspendAll() RTOS API function which is part of the RTOS scheduler control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers.
[2] The FreeRTOS xTaskResumeAll() RTOS API function which is part of the RTOS scheduler control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers.
[3] FreeRTOS - Open Source Software for Embedded Systems - FreeRTOS xQueueSendFromISR() interrupt safe API function description

1 Like

Context switches requested from an ISR while the scheduler is suspended are held pending until the scheduler is resumed.

Hi Richard,

Hmm interesting.

So what happens in the case when we are in a vTaskSuspendAll()[1]/vTaskResumeAll()[2] block, an ISR comes along xQueueSendFromISR() wakes up a high prio taskpxHigherPriorityTaskWoken and we invoke taskYIELD_FROM_ISR ()?

We just return from the ISR to the task we came from, finish the vTaskSuspendAll()[1]/vTaskResumeAll()[2] block and only after the scheduler kicks in we have the context switch to the high prio task, which was awoken by the ISR.

This is an important difference and should be mentioned somewhere in the docs since it changes the expected real-time behavior. Maybe it’s already somewhere, and I missed it :wink:

As mentioned before it’s documented in the API docs:

Suspends the scheduler. Suspending the scheduler prevents a context switch from occurring but leaves interrupts enabled. If an interrupt requests a context switch while the scheduler is suspended, then the request is held pending and is performed only when the scheduler is resumed (un-suspended).

xTaskResumeAll might reschedule tasks immediately if a higher prio task was made ready by an ISR.

1 Like

So it’s time-wise not as bad as not calling taskYIELD_FROM_ISR() but just a bit worse than calling it without suspending the scheduler, assuming the vTaskSuspendAll()[1]/vTaskResumeAll()[2] block is short, as it should be.

Well, one thing to consider here is side effects. As implied by the name, VTasxk<Suspend|Resume>All prevents context switches to ALL ready tasks which by definition includes tasks that are not affected by the sync issue in question.

In this here thread:

we had a similar issue, regarding the invasiveness of critical sections. For now, I’d like to liken the suspend/resume mechanism to the task-level counterpart of critical sections: It does have a global side effect.

Thus, only use global suspension/resume if there is a very good reason for it, analogous to the critical section. In real time architectures, our goal is to give every thread of execution (tasks or ISRs) the highest possible responsiveness and throughput allowed by the restrictions of the hardware and the OS. Employing any mechanism that has global side effects in return for only providing “local synchronisation” is always bad in first approximation unless absolutely required.

In my understanding, both the critical section and the global suspend/resume APIs should be flagged as something like “privileged APIs by convention,” meaning that they were originally introduced to be used by the OS itself and exposed only for application that do require a side effect on the entire system (and could thus be informally labelled “OS extensions”).

2 Likes

Yes you are absolutely right. It’s about “coarse locking” vs. “fine grained locking”. It’s also about how long the lock is held. I mean if the lock is held for a very short amount of time “coarse locking” might even be beneficial/faster because you avoid context switches. And then there are “lock-less algorithms”. And of course (Richard pointed this out to me a long time ago) “gatekeeper tasks”, which boil down to “if by design only one task accesses a certain resource no locking is needed”.