/*
* FreeRTOS STARVATION Demo — STM32 Nucleo
* ---------------------------------------------------------------------------
* Shows STARVATION: a task that is READY the entire time but NEVER gets the CPU,
* because higher-or-equal-priority work is always available to run instead.
*
* STARVATION - a READY task is indefinitely denied the CPU. It is NOT blocked
* and NOT deadlocked -- nothing it needs is missing; the scheduler
* simply always has something more important to run.
* vs DEADLOCK - deadlocked tasks are BLOCKED, each waiting on a resource another
* holds. A starved task waits on NOTHING but CPU time.
*
* The victim (LOW priority) loses every scheduling decision because the RED "hog"
* (higher priority, CPU-bound, never blocks) is always READY. GREEN and BLUE run
* fine; only YELLOW starves. After 10 s we apply PRIORITY AGING (raise the
* victim's priority) and it immediately runs -- the standard cure for starvation.
*
* SAFETY: the hog is CPU-bound but is still PREEMPTED by the higher-priority GREEN
* task and by the tick ISR, so the RTOS keeps running and serial keeps flowing.
* This is starvation of lower-priority tasks, not a system hang.
*/
/*
* FreeRTOS STARVATION Demo — STM32 Nucleo
* ---------------------------------------------------------------------------
* Shows STARVATION: a task that is READY the entire time but NEVER gets the CPU,
* because higher-or-equal-priority work is always available to run instead.
*
* STARVATION - a READY task is indefinitely denied the CPU. It is NOT blocked
* and NOT deadlocked -- nothing it needs is missing; the scheduler
* simply always has something more important to run.
* vs DEADLOCK - deadlocked tasks are BLOCKED, each waiting on a resource another
* holds. A starved task waits on NOTHING but CPU time.
*
* The victim (LOW priority) loses every scheduling decision because the RED "hog"
* (higher priority, CPU-bound, never blocks) is always READY. GREEN and BLUE run
* fine; only YELLOW starves. After 10 s we apply PRIORITY AGING (raise the
* victim's priority) and it immediately runs -- the standard cure for starvation.
*
* SAFETY: the hog is CPU-bound but is still PREEMPTED by the higher-priority GREEN
* task and by the tick ISR, so the RTOS keeps running and serial keeps flowing.
* This is starvation of lower-priority tasks, not a system hang.
*/
#include <STM32FreeRTOS.h>
// ---- LED pins: reuse your working pins ----
const int LED_HOG = PB1; // RED - the CPU hog (priority 2, never blocks)
const int LED_URGENT = PB13; // GREEN - high-priority periodic (priority 3) - runs fine
const int LED_WORKER = PB14; // BLUE - shares priority 2 with the hog - runs (time-slice)
const int LED_VICTIM = PB2; // YELLOW - the STARVED task (priority 1) - never runs (until cured)
// ---- Priorities (higher = more urgent) ----
#define PRIO_VICTIM (tskIDLE_PRIORITY + 1) // 1 - lowest: this is who starves
#define PRIO_HOG (tskIDLE_PRIORITY + 2) // 2 - CPU-bound, keeps level 2 always READY
#define PRIO_WORKER (tskIDLE_PRIORITY + 2) // 2 - equal to hog: they time-slice
#define PRIO_URGENT (tskIDLE_PRIORITY + 3) // 3 - highest: preempts, keeps system responsive
#define STACK_WORDS 256
#define STARVE_SECONDS 10 // how long to let it starve before curing
TaskHandle_t victimHandle = NULL;
// shared observation state
volatile uint32_t victimRunCount = 0; // how many times the victim has run
volatile uint32_t victimLastRunMs = 0; // when it last ran (0 = never)
static inline uint32_t nowMs() {
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
}
// Bounded CPU-bound spin (does NOT block) for d ms at the caller's priority.
static void busyWorkMs(uint32_t d) {
TickType_t s = xTaskGetTickCount();
while ((xTaskGetTickCount() - s) < pdMS_TO_TICKS(d)) { /* spin */ }
}
// ---- RED: the hog. Priority 2, CPU-bound, NEVER blocks. Because it is always
// READY at priority 2, no priority-1 task can ever be the highest READY task.
void taskHog(void *pv) {
uint32_t lastPrint = 0;
for (;;) {
digitalWrite(LED_HOG, HIGH);
busyWorkMs(20); // hold the CPU (preemptible by GREEN + tick)
if (nowMs() - lastPrint >= 1000) { // status once a second
lastPrint = nowMs();
Serial.print(nowMs()); Serial.println(" ms - RED HOG running (holding the CPU)");
}
// NOTE: no vTaskDelay here on purpose -> this is what causes the starvation.
}
}
// ---- BLUE: equal priority to the hog. Round-robin time-slicing means it still
// gets turns at priority 2, so it runs fine -- proving the victim's problem
// is priority, not the hog specifically.
void taskWorker(void *pv) {
for (;;) {
digitalWrite(LED_WORKER, HIGH);
busyWorkMs(10);
digitalWrite(LED_WORKER, LOW);
Serial.print(nowMs()); Serial.println(" ms - BLUE worker got a turn (equal priority)");
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// ---- GREEN: highest priority, periodic. Blocks most of the time, so it doesn't
// cause starvation -- but it preempts the hog when it wakes, keeping the
// system responsive. It also MONITORS the victim and reports the starvation.
void taskUrgent(void *pv) {
bool cured = false;
for (;;) {
vTaskDelay(pdMS_TO_TICKS(1000)); // BLOCKED most of the time
digitalWrite(LED_URGENT, HIGH);
busyWorkMs(3);
digitalWrite(LED_URGENT, LOW);
uint32_t starvedMs = (victimRunCount == 0) ? nowMs() : (nowMs() - victimLastRunMs);
Serial.print(nowMs());
Serial.print(" ms - GREEN urgent OK | victim runs=");
Serial.print(victimRunCount);
Serial.print(" starved for ");
Serial.print(starvedMs);
Serial.println(" ms");
// ---- THE CURE: priority aging ----
if (!cured && nowMs() >= STARVE_SECONDS * 1000UL) {
cured = true;
Serial.println(">>> CURE: aging the victim -- raising its priority above the hog");
vTaskPrioritySet(victimHandle, PRIO_HOG + 1); // now higher than the hog -> it will run
}
}
}
// ---- YELLOW: the victim. Priority 1. It is ALWAYS READY (it never blocks), yet
// it never runs while the hog holds priority 2 -- pure starvation. Once its
// priority is aged up, this loop finally executes.
void taskVictim(void *pv) {
for (;;) {
victimRunCount++;
victimLastRunMs = nowMs();
digitalWrite(LED_VICTIM, HIGH);
Serial.print(nowMs()); Serial.println(" ms - *** YELLOW VICTIM RAN! ***");
busyWorkMs(5);
digitalWrite(LED_VICTIM, LOW);
vTaskDelay(pdMS_TO_TICKS(200)); // after the cure, run periodically
}
}
void setup() {
Serial.begin(115200); // use whatever serial setup works on your board
delay(1000);
Serial.println("--- FreeRTOS STARVATION demo ---");
Serial.print("Victim (YELLOW) will starve for ~");
Serial.print(STARVE_SECONDS);
Serial.println(" s, then priority aging cures it.");
pinMode(LED_HOG, OUTPUT); pinMode(LED_URGENT, OUTPUT);
pinMode(LED_WORKER, OUTPUT); pinMode(LED_VICTIM, OUTPUT);
xTaskCreate(taskUrgent, "URGENT", STACK_WORDS, NULL, PRIO_URGENT, NULL);
xTaskCreate(taskHog, "HOG", STACK_WORDS, NULL, PRIO_HOG, NULL);
xTaskCreate(taskWorker, "WORKER", STACK_WORDS, NULL, PRIO_WORKER, NULL);
xTaskCreate(taskVictim, "VICTIM", STACK_WORDS, NULL, PRIO_VICTIM, &victimHandle);
vTaskStartScheduler();
}
void loop() {}
Loading
st-nucleo-c031c6
st-nucleo-c031c6