Exercises — FreeRTOS IPC — queues, semaphores, mutexes, event groups
5.5.11 · D4· Coding › Embedded Systems & Real-Time Software › FreeRTOS IPC — queues, semaphores, mutexes, event groups
Is page mein queues, semaphores, mutexes, aur event groups — sab kuch test hota hai. Prerequisite ideas — RTOS Scheduling — preemption & priorities, Priority Inversion & the Mars Pathfinder bug, Interrupt Service Routines & Deferred Processing, Producer–Consumer Pattern, aur Memory Management in FreeRTOS — heap_1..5 — use ki gayi hain lekin har solution mein re-explain ki gayi hain.
Neeche, ek chhoti picture (map) dikhati hai ki dive in karne se pehle har rung kaun sa object exercise karta hai.

L1 · Recognition — "sahi tool ka naam batao"
Yahan tum sirf decide karte ho ki kaun sa kernel object fit baithta hai. Abhi koi code nahi. Char objects yaad karo:
- Queue = copy-by-value mailbox (FIFO, first-in-first-out).
- Semaphore = ek counter jise tum Give (1 add karo) aur Take (1 subtract karo, 0 pe blocking) kar sakte ho.
- Mutex = ek binary semaphore owner ke saath + priority inheritance (ek lockable "key").
- Event group = bits ka ek set jinka tum AND / OR logic ke saath wait karte ho.
Exercise L1.1
Ek UART interrupt tab fire hota hai jab ek byte arrive karta hai. Interrupt handler tiny hona chahiye; ek normal task ko actual parsing karni chahiye. Kaun sa IPC object task ko wake karta hai, aur mutex kyun nahi?
Recall Solution
Object: binary semaphore (ISR se xSemaphoreGiveFromISR ke saath diya jaata hai).
Kyun: ISR ko sirf signal karna hai "kuch hua" — ek single yes/no wake-up. Binary semaphore exactly ek 0/1 flag hota hai.
Mutex kyun nahi: ek mutex ka ek owner hota hai — sirf woh task jo ise leti hai woh ise de sakti hai. Ek ISR ki koi task identity nahi hoti, isliye koi nahi hota jo own kare ya priority inherit kare. Ownership rules ek ISR ko mutex release karne se rokti hain, isliye priority inheritance undefined hogi. Signalling locking nahi hai.
Exercise L1.2
Task A uint16_t temperature readings produce karta hai; task B unhe log karta hai, possibly slower. Tum koi bhi reading kabhi nahi kho sakte jab tak buffer overflow na ho, aur donon tasks ek dusre ka data corrupt nahi kar sakten. Kaun sa object?
Recall Solution
Object: queue of uint16_t items.
Kyun: tumhe data transfer karna hai (reading khud), order maintain karna hai (FIFO), aur thodi der ke liye buffer karna hai jab B slow ho. Queue bytes ko kernel-owned storage mein copy karta hai, isliye A apna local variable return hote hi overwrite kar sakta hai bina in-flight message ko harm kiye. Yeh Producer–Consumer Pattern hai.
Exercise L1.3
Ek firmware update task sirf tab run honi chahiye jab dono "network connected" aur "flash unlocked" true ho jaayein, kisi bhi order mein. Kaun sa object?
Recall Solution
Object: event group do bits ke saath, AND rule se wait kiya (xWaitForAllBits = pdTRUE).
Kyun: tum independent conditions ke logical combination pe wait kar rahe ho. Ek semaphore ek yes/no ka jawaab deta hai; unhe do ke awkward juggling karni padti. Ek event group ek single task ko block karne deta hai jab tak chosen mask (dono bits) satisfy na ho jaye — chahe jis order mein aayein.
L2 · Application — "API sahi se use karo"
Exercise L2.1
Ek queue create karo jo 8 items hold kare, har ek struct sensor_sample 12 bytes ka. Queue messages ke liye kitne bytes of storage reserve karti hai (control block ignore karo), aur exact xQueueCreate call kya hai?
Recall Solution
Call: xQueueCreate(8, sizeof(struct sensor_sample)); → xQueueCreate(8, 12);
Messages ke liye storage: length × itemSize = 8 × 12 = 96 bytes.
Yeh size kyun: ek queue har item ko by value copy karti hai, isliye ise length × itemSize bytes own karni padti hain taaki ek baar mein length full copies hold kar sake. (Ek chhota control block aur blocked tasks ki do lists bhi hain, lekin message buffer khud 96 bytes ka hai.)
Exercise L2.2
Ek sender xQueueSend(q, &v, pdMS_TO_TICKS(50)) karta hai. Kernel tick 1000 Hz pe run karta hai (1 ms per tick). Agar queue poora time full rahe toh sender kitne ticks block karega? Yeh bhi explain karo ki argument v nahi balki &v kyun hai, aur derive karo kyun rounding formula 300 Hz tick rate pe +999 bias use karta hai.
Recall Solution
Real macro round up karta hai. FreeRTOS mein definition essentially yeh hai
integer arithmetic mein compute ki jaati hai — aur common implementation ek rounding bias add karta hai taaki partial ticks round up hon, jaise kuch ports pe ((ms * configTICK_RATE_HZ) + 999) / 1000. Yeh tab matter karta hai jab tick rate 1000 se evenly divide nahi hoti.
+999 kyun, step by step (integer-arithmetic "why"). C mein integer division a / b remainder throw away karta hai — yeh floor karta hai, yaani neeche round karta hai. Lekin ek timeout kabhi requested se chhoti nahi honi chahiye, isliye humein upar round karna hai. "Divide aur round up" ke liye universal integer trick hai:
Exactly kyun aur koi aur number kyun nahi? Likho remainder ke saath.
- Agar (evenly divide hota hai): add karne se milta hai, aur — unchanged, sahi.
- Agar (ek leftover partial tick hai): , isliye floor ho jaata hai — exactly ek se bump up, sahi. se kam add karna kuch cases bump karne mein fail hoga; zyada add karna pe over-bump kar sakta hai. Isliye woh unique smallest bias hai jo sirf tab round up karta hai jab remainder exist karta hai. Yahan (ms→s denominator) hai, isliye bias hai.
1000 Hz pe: exactly, koi rounding nahi chahiye. Isliye sender 50 ticks (= 50 ms) tak block karta hai.
300 Hz pe (1000 se divide nahi hota): lo: exact hai . Plain truncation ticks deta hai (bahut chhota!); round-up form ticks. Rounded-up answer guarantee karta hai ki tum kam se kam requested time wait karo, kabhi kam nahi.
&v kyun: API copy karne ke liye item ka pointer leta hai. FreeRTOS us address se shuru ho kar itemSize bytes read karta hai aur unhe queue mein copy karta hai. v (value) pass karna ek type error hoga — function tumhara item ka type nahi jaanta, sirf uska byte count aur ek source address.
Exercise L2.3
Ek ISR ek binary semaphore ke through ek high-priority task ko wake karna chahta hai. Classic pattern ko xSemaphoreGiveFromISR ke alawa do cheezein chahiye. Fill in karo.
Recall Solution
BaseType_t woke = pdFALSE; // (1) ek flag jo API set karta hai
xSemaphoreGiveFromISR(sem, &woke); // agar ek higher-prio task wake hua
portYIELD_FROM_ISR(woke); // (2) ISR exit pe us par switch karowoke flag kyun: semaphore dene se ek task higher priority wali jo bhi interrupt hui thi us se unblock ho sakti hai. xSemaphoreGiveFromISR woke = pdTRUE set karta hai tumhe batane ke liye.
portYIELD_FROM_ISR kyun: agar ek higher-priority task ready ho gayi, hum ISR return pe immediately us par context-switch karte hain, scheduler ke next tick ka wait karne ke bajaye — yeh wake-up latency minimise karta hai. Yeh Interrupt Service Routines & Deferred Processing action mein hai.
Exercise L2.4 — event-group API
Ek OTA (firmware update) task tabhi proceed kare jab dono WIFI_BIT (bit 0) aur CFG_BIT (bit 1) set hon, aur exit pe dono clear kare taaki next cycle fresh se start ho. Create call, dono set calls doosri tasks se, aur wait call likho — aur state karo ki "both bits" mask numerically kya value hai.
Recall Solution
#define WIFI_BIT (1 << 0) // = 0b01 = 1
#define CFG_BIT (1 << 1) // = 0b10 = 2
EventGroupHandle_t eg = xEventGroupCreate(); // bits ka ek word, sab 0
// WiFi task se, jab link up aaye:
xEventGroupSetBits(eg, WIFI_BIT); // bit 0 set karta hai
// config task se, jab config load ho:
xEventGroupSetBits(eg, CFG_BIT); // bit 1 set karta hai
// OTA task tab tak block karta hai jab tak DONO set na hon:
EventBits_t b = xEventGroupWaitBits(
eg,
WIFI_BIT | CFG_BIT, // wait karne ka mask = 0b11 = 3
pdTRUE, // xClearOnExit: unblock hone pe woh bits clear karo
pdTRUE, // xWaitForAllBits: pdTRUE = AND (dono), pdFALSE = OR (koi bhi)
portMAX_DELAY); // satisfy hone tak hamesha ke liye block karo"Both bits" mask numerically: WIFI_BIT | CFG_BIT = 1 | 2 = 3 (binary 0b11). Bitwise-OR combine karta hai do single-bit flags ko ek mask mein; us mask pe xWaitForAllBits = pdTRUE ke saath wait karna matlab hai "sirf tab wake karo jab mask ke saare set bits group mein set hon."
Clear-on-exit ke liye pdTRUE kyun: bina iske bits set rahengi, aur next loop iteration stale bits pe immediately fire hogi fresh events ka wait karne ke bajaye.
portMAX_DELAY kyun: OTA task ke paas kuch karne ko nahi jab tak dono conditions hold na karein — kernel use hamesha ke liye sone do (no busy-waiting) time out karne ke bajaye. (portMAX_DELAY = block forever, is page ke top pe define kiya gaya hai.)
L3 · Analysis — "counts aur timing ke baare mein socho"
Exercise L3.1
Ek counting semaphore max count 3, initial count 0 ke saath create hoti hai. Jab ek worker task busy hai (Take nahi call kar rahi), ek ISR Give 5 baar call karta hai. Baad mein semaphore ka count kya hoga, aur worker ka Take looping shuru karne ke baad bina block kiye kitni baar succeed karega?
Recall Solution
Count: har Give c += 1 try karta hai lekin max, 3 pe saturate hota hai. 0 se shuru: gives 1→1, 2→2, 3→3, 4→still 3, 5→still 3. Final count = 3.
Takes jo immediately succeed karengi: count 3 hai, isliye worker bina blocking ke 3 baar Take kar sakta hai (3→2→1→0). 4th aur 5th events lost ho gayi kyunki semaphore saturate ho gayi.
Lesson: ek counting semaphore "yaad rakhti hai kitne, apne max tak." Max ≥ worst-case burst choose karo jo tum kho nahi sakte.
Exercise L3.2 — Priority inversion timing
Teen tasks: L (low), M (medium), H (high). L pe ek mutex leta hai aur lock ke andar 30 ms kaam karta hai. ms pe, H wake hota hai aur same mutex lene ki koshish karta hai. ms pe, M wake hota hai aur 100 ms run karta. Single core, priority-preemptive scheduling assume karo. (a) Priority inheritance ke bina, H mutex kab (earliest) acquire kar sakta hai? (b) Priority inheritance ke saath, H usse kab acquire kar sakta hai?
Recall Solution
Puri self-contained timeline words mein — aur neeche ki figure exactly yeh do timelines side by side draw karti hai taaki tum dekh sako difference.
Figure kaise padhein: time left-to-right milliseconds mein chalta hai; har row ek task hai (L bottom, M middle, H top). Green bar = L lock hold karte hue run kar raha hai; yellow bar = M run kar raha hai; blue bar = L boosted priority pe run kar raha hai; red bar = H blocked, wait kar raha hai. Arrow woh instant mark karta hai jab H finally mutex acquire karta hai. Left panel without inheritance hai (H 130 ms tak wait karta hai); right panel with inheritance hai (H sirf 30 ms tak wait karta hai). Dekho kaise right pe H ki critical path se yellow M bar gayab ho jaata hai — wahi pura fix hai.
(a) Inheritance ke bina — time ke saath CPU owner trace karo:
- ms: sirf L awake hai. L run karta hai, lock hold karta hai. Usne abhi apne 30 ms locked work ka 5 ms kiya hai.
- ms: H wake hota hai aur mutex lene ki koshish karta hai. L abhi bhi ise hold kar raha hai, isliye H block ho jaata hai. L abhi bhi single ready task hai, isliye L run karta rehta hai.
- ms: L aur 5 ms run karta hai → 10 ms locked work done, 20 ms remaining.
- ms: M wake hota hai. M ki priority > L ki, aur M ko lock nahi chahiye, isliye scheduler L ko preempt karta hai aur M run karta hai.
- ms: M apna pura 100 ms run karta hai. L poora time starved rehta hai, abhi bhi lock hold karta hai, isliye H block rehta hai.
- ms: M finish karta hai. L resume karta hai aur apna remaining 20 ms locked work run karta hai.
- ms: L mutex release karta hai. H finally ms pe mutex acquire karta hai. Yeh blocking unbounded hai: aur medium tasks add karo aur H aur zyada wait karta hai.
(b) Inheritance ke saath — same trace, ek rule changed:
- ms: L lock hold karte hue run karta hai (5 ms done, 25 ms left).
- ms: H mutex pe block hota hai. Kernel ab L ki priority H ki priority tak boost karta hai (priority inheritance) kyunki L woh lock hold karta hai jo H chahta hai.
- ms: L, ab H ki priority pe, apna remaining 25 ms uninterrupted run karta hai.
- ms: M wake hota hai — lekin M ki medium priority L ki boosted priority se neeche hai, isliye M L ko preempt nahi kar sakta. M wait karta hai.
- ms: L locked work finish karta hai aur mutex release karta hai; uski priority low pe wapas aa jaati hai. H ms pe mutex acquire karta hai, sirf L ki critical-section length se bounded. Yeh Priority Inversion & the Mars Pathfinder bug ke peeche ka fix hai.

Exercise L3.3
Length 2 ki ek queue uint32_t hold karti hai. Teen producers har ek xQueueSend ek value (10, 20, 30) xTicksToWait = 0 (no blocking) ke saath karte hain, back-to-back, koi receiver run karne se pehle. Har send ke saath kya hota hai, aur receiver pehle kya padhega?
Recall Solution
- Send 10 → queue
[10],pdTRUEreturn karta hai. - Send 20 → queue
[10, 20](full),pdTRUEreturn karta hai. - Send 30 → queue full hai aur timeout 0 hai, isliye yeh block nahi karta;
pdFALSEreturn karta hai. 30 drop ho jaata hai. - Receiver FIFO read karta hai → pehle 10, phir 20.
Lesson:
xTicksToWait = 0ke saath, ek full queuexQueueSendko block karne ke bajaye immediately fail kara deti hai (return valuepdFALSE) — tumhe return value check karna chahiye.
L4 · Synthesis — "ek chhota system design karo"
Exercise L4.1
Is system ke liye IPC design karo: ek temperature ISR har sample fire hota hai aur ek float ek filter task ko hand karna chahiye; filter results ek shared I²C display pe likhta hai jo ek doosri clock task bhi likhti hai. Har woh object list karo jo tum create karoge aur ek sentence batao kyun.
Recall Solution
- Queue
q_samples(float, length ≈ 8): ISR → filter task data transfer. Copy-by-value har sample ki lifetime-safety ensure karta hai; length jitter absorb karta hai. ISR meinxQueueSendFromISRuse karo. - Mutex
m_display: filter task aur clock task dono shared I²C display likhte hain; ek mutex exclusive turns deta hai aur priority inheritance deta hai taaki ek low-priority writer ek high-priority ko indefinitely block na kare. - (Optional) binary semaphore queue ko replace kar sakta tha sirf tab agar ISR koi data pass nahi karta — lekin yahan yeh ek
floatpass karta hai, isliye queue zaruri hai. Mutex ISR→filter kyun nahi? ISRs mutexes own nahi kar sakten; hand-off ke liye queue use karo (jiske...FromISRvariants hain).
Exercise L4.2
Tumhe guarantee karni hai ki ek "deferred interrupt processing" pipeline kabhi events nahi khoega chahe ek long stretch mein worker busy ho toh bhi 4 interrupts fire hon. Kaun sa object, kya parameters, aur trade-off kya hai?
Recall Solution
- Counting semaphore,
xSemaphoreCreateCounting(maxCount, 0)ke saathmaxCount ≥ 4(worst-case burst choose karo, jaise margin ke liye 8). - ISR har event pe
xSemaphoreGiveFromISRkarta hai; worker loop karta haixSemaphoreTake(sem, portMAX_DELAY); handle_one();. - Counting kyun, binary nahi: ek binary semaphore 1 pe saturate hoti hai — ek busy stretch mein 4 events single wake-up mein collapse ho jaate hain, 3 kho jaate hain. Ek counting semaphore backlog yaad rakhti hai apne max tak.
- Trade-off: agar real burst
maxCountse zyada ho sakta hai, toh us se pare events abhi bhi kho jaate hain — tumhemaxCounttrue worst case ke hisaab se size karna hoga, thoda bade control block ki cost pe.
L5 · Mastery — "ek subtle choice defend karo"
Exercise L5.1
Ek colleague ek shared counter ko 3-instruction increment ke liye taskENTER_CRITICAL()/taskEXIT_CRITICAL() se protect karta hai, aur bahin jagah ek 5 ms I²C transaction guard karne ke liye mutex use karta hai. Dono choices ko defend karo ya refute karo.
Recall Solution
- 3-instruction increment ke liye critical section: sahi.
taskENTER_CRITICALkuch cycles ke liye scheduler/interrupts disable karta hai — sasta, aur itni chhoti cheez ke liye koi blocking object nahi chahiye. Dekho Critical Sections & taskENTER_CRITICAL. - 5 ms I²C transaction ke liye mutex: sahi. 5 ms ke liye interrupts disable karna real-time latency barbad kar deta (koi ISR run nahi kar sakta). Ek mutex waiting task ko block karta hai — CPU baaki kaam karta rehta hai — aur priority inheritance wait ko bound karta hai.
- Rule: bahut chhoti, deterministic sequences ko critical section se guard karo; lambi ya blocking operations ko mutex se. Galat swap (5 ms ke around critical section) poore system ke interrupts ko 5 ms ke liye freeze kar dega.
Exercise L5.2
Count karke prove karo ki burst-of- deferred-work scenario ke liye counting ki jagah binary semaphore use karna worst case mein exactly events kho deta hai, jab saare gives kisi bhi take se pehle lagate hain. Phir batao ki wali counting semaphore kya achieve karti hai.
Recall Solution
Binary semaphore ko length 1, itemSize 0 ki queue ki tarah model karo. Uska count hai, aur har Give saturating update karta hai.
- Start . Give #1: .
- Gives #2 … #N: har ek compute karta hai — count already saturated hai, isliye yeh saare no-ops hain. Yeh exactly lost events hain.
- Jab worker finally Take karta hai: ek successful wake-up, phir . Isliye events mein se exactly 1 kaam produce karta hai aur lost ho jaate hain — yahi claim tha.
Counting semaphore ke saath, : update burst ke dauran kabhi saturate nahi hoti, isliye gives ke baad . Worker phir baar Take karta hai, saare units of work 0 losses ke saath karta hai. Yeh L4.2 ke sizing rule ka concrete justification hai: maxCount worst-case burst ke hisaab se set karo.
Recall Self-test cloze
Ek semaphore itemSize ::: 0 (zero bytes) ki queue ki tarah implement hoti hai.
Sirf woh task jo mutex leti hai usse wapas de sakti hai — yeh property ::: ownership kehlaati hai.
Jab ek high-priority task mutex pe wait karti hai, kernel holder ki priority boost karta hai — yeh ::: priority inheritance hai.
pdMS_TO_TICKS(50) 100 Hz tick rate pe ::: 5 ticks ke barabar hai.
Ek counting semaphore ka count uske ::: maximum count pe saturate hota hai.
Event-group mask WIFI_BIT | CFG_BIT bits 0 aur 1 ke saath numerically ::: 3 ke barabar hai.