{ "card": { "number": "05", "slug": "queue", "title": "Kolejki: blokowanie, FIFO i kopiowanie wartości", "topic": "xQueueSend, xQueueReceive, wait lists i ownership danych", "status": "Gotowa", "version": "v00.01" }, "front": { "goal": "Uczeń wyjaśnia, kiedy kolejka blokuje sendera lub receivera, oraz dowodzi, że FreeRTOS kopiuje stałą liczbę bajtów do własnego storage.", "scope": "Kolejka długości 1 przenosi M1, M2 i M3. Receiver blokuje się na pustej kolejce, sender na pełnej. Po wysłaniu M2 źródło zmienia się na 0xDEAD, lecz receiver odbiera niezmienione 0x2222. Verifier sprawdza FIFO, publiczny stan eBlocked, osobne stosy i digest dziesięciu zdarzeń." }, "viewpoints": [ {"id":"A1","label":"CONTEXT","status":"unavailable","subtitle":"Granice systemu","reason":"Granica aplikacja–kernel–Hazard3 pozostaje taka jak w FC03."}, {"id":"A2","label":"STRUCTURE","status":"enabled","subtitle":"Message, Queue<1> i trzy taski"}, {"id":"A3","label":"DISPATCH","status":"unavailable","subtitle":"Callback ABI","reason":"Kolejka nie wprowadza nowego callbacku C."}, {"id":"A4","label":"APPLICATION","status":"unavailable","subtitle":"Konkretny eksperyment","reason":"Topologia eksperymentu jest kompletna w A2 i A5."}, {"id":"A5","label":"FLOW","status":"enabled","subtitle":"E01–E10 i dwa wait lists"}, {"id":"A6","label":"STATE","status":"enabled","subtitle":"EMPTY, FULL i handoff"}, {"id":"A7","label":"RUNTIME","status":"enabled","subtitle":"Queue object, TCB, stos i storage"}, {"id":"A8","label":"PATTERNS","status":"enabled","subtitle":"Value message kontra pointer ownership"} ], "artifact": { "source": "src/tasks/task01_queue.c", "elf": "build/task01_queue/prog.elf", "image": "build/task01_queue/prog.bin" }, "strategies": [ { "id":"code.queue-contract","kind":"code", "prerequisites":["źródło FC05"], "layout":["QueueMessage","QueueExperiment","FreeRTOSConfig"], "commands":["rg -n 'QueueMessage|FC05_QUEUE_LENGTH|xQueueCreate' include src"], "expected_observations":["item size jest stały","storage kolejki nie jest buforem źródłowym"], "assertions":["length=1","payload ma checksum"], "evidence_fields":["code_ref","typ","rozmiar"] }, { "id":"run.queue-state","kind":"run", "prerequisites":["Hazard3 online","czysta RAM"], "layout":["source","queue object","sender/receiver TCB","event log"], "commands":["print g_fc05","print g_fc05_events","print g_fc05_event_count"], "expected_observations":["receiver czeka na empty","sender czeka na full"], "assertions":["publiczny eTaskState=eBlocked","uxMessagesWaiting=1"], "evidence_fields":["event","task state","queue occupancy","PC","SP","timestamp"] }, { "id":"run.byte-copy","kind":"run", "prerequisites":["M2 znajduje się w queue storage"], "layout":["mutated source","queue storage","received M2"], "commands":["print g_fc05.mutated_source","print g_fc05.received[1]"], "expected_observations":["source=0xDEAD","received M2=0x2222"], "assertions":["checksum M2 poprawny","adresy storage są różne"], "evidence_fields":["source payload","received payload","checksum"] }, { "id":"run.pass","kind":"run", "prerequisites":["sender i receiver zakończone"], "layout":["FIFO","bounded evidence","digest"], "commands":["print g_fc05_pass","print g_fc05.evidence_digest"], "expected_observations":["1/2/3","PASS=1","10 committed records"], "assertions":["digest jest stabilny w trzech procesach","verifier ma najniższy priorytet"], "evidence_fields":["PASS","digest","source blob","ELF hash","timestamp"] } ], "checkpoints": { "task01.config":{"event_id":"E01","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 1"},"verify":{"expressions":[{"expr":"g_fc05_event_count","equals":1},{"expr":"g_fc05.queue == 0","equals":1}]}}, "task01.receiver-empty":{"event_id":"E02","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 2"},"verify":{"expressions":[{"expr":"uxQueueMessagesWaiting(g_fc05.queue)","equals":0},{"expr":"g_fc05.receiver_sp != 0","equals":1}]}}, "task01.send-m1":{"event_id":"E03","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 3"},"verify":{"expressions":[{"expr":"g_fc05_event_count","equals":3},{"expr":"g_fc05.sender_sp != 0","equals":1}]}}, "task01.receive-m1":{"event_id":"E04","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 4"},"verify":{"expressions":[{"expr":"g_fc05.received[0].sequence","equals":1},{"expr":"g_fc05.received[0].payload","equals":4369}]}}, "task01.mutated-source":{"event_id":"E05","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 5"},"verify":{"expressions":[{"expr":"g_fc05.mutated_source.payload","equals":57005},{"expr":"uxQueueMessagesWaiting(g_fc05.queue)","equals":1}]}}, "task01.sender-blocked":{"event_id":"E06","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 6"},"verify":{"expressions":[{"expr":"g_fc05.sender_state_when_full","equals":2},{"expr":"g_fc05.waiting_when_full","equals":1}]}}, "task01.receive-m2":{"event_id":"E07","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 7"},"verify":{"expressions":[{"expr":"g_fc05.received[1].sequence","equals":2},{"expr":"g_fc05.received[1].payload","equals":8738}]}}, "task01.receive-m3":{"event_id":"E08","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 8"},"verify":{"expressions":[{"expr":"g_fc05.received[2].sequence","equals":3},{"expr":"g_fc05.received[2].payload","equals":13107}]}}, "task01.sender-finished":{"event_id":"E09","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 9"},"verify":{"expressions":[{"expr":"g_fc05.received[2].sequence","equals":3},{"expr":"g_fc05_event_count","equals":9}]}}, "task01.pass":{"event_id":"E10","stop":{"symbol":"fc05_checkpoint_committed","offset":0,"condition":"$a0 == 10"},"verify":{"expressions":[{"expr":"g_fc05_pass","equals":1},{"expr":"g_fc05.receiver_done","equals":1},{"expr":"g_fc05.sender_done","equals":1}]}} }, "sections": [ { "id":"A2","label":"STRUCTURE","title":"Structure","order":20,"orientation":"landscape", "description":"QueueMessage, Queue<1>, trzy taski i bounded evidence.", "content_tex":"Kolejka zna rozmiar elementu i przechowuje własne bajty. Konteksty aplikacji przechowują wyłącznie uchwyty i dowody.", "assets":[{"stem":"a2-structure","title":"A2 · struktura kolejki","caption":"A2 STRUCTURE — message, queue i trzy taski.","label":"fig:a2-structure","alt":"Struktura eksperymentu kolejki.","phases":[{"id":"structure","label":"MESSAGE / QUEUE / TASKS","steps":[ {"id":"message","number":1,"label":"fixed-size value message","mode":"CODE","strategy_ref":"code.queue-contract","svg_label":"01","code_ref":"include/task01_queue_model.h:7","description":"Trzy pola tworzą payload kopiowany bajtowo.","evidence":"sizeof i checksum."}, {"id":"queue","number":2,"label":"queue owns one item slot","mode":"CODE","strategy_ref":"code.queue-contract","svg_label":"02","code_ref":"src/tasks/task01_queue.c:267","description":"Length=1 ujawnia oba rodzaje blokowania.","evidence":"xQueueCreate(1,sizeof message)."}, {"id":"receiver","number":3,"label":"higher receiver waits on empty","mode":"RUN","event_id":"E02","strategy_ref":"run.queue-state","svg_label":"03","code_ref":"src/tasks/task01_queue.c:91","snapshot_ref":"task01.receiver-empty","description":"Receiver nie wykonuje polling.","evidence":"queue empty, receiver blocks."}, {"id":"sender","number":4,"label":"sender waits on full","mode":"RUN","event_id":"E06","strategy_ref":"run.queue-state","svg_label":"04","code_ref":"src/tasks/task01_queue.c:173","snapshot_ref":"task01.sender-blocked","description":"M3 czeka na zwolnienie slotu.","evidence":"sender eBlocked."}, {"id":"verifier","number":5,"label":"low verifier checks invariants","mode":"RUN","event_id":"E10","strategy_ref":"run.pass","svg_label":"05","code_ref":"src/tasks/task01_queue.c:220","snapshot_ref":"task01.pass","description":"PASS powstaje po obu taskach roboczych.","evidence":"FIFO, copy, states, digest."}, {"id":"evidence","number":6,"label":"records commit marker last","mode":"CODE","strategy_ref":"code.queue-contract","svg_label":"06","code_ref":"src/tasks/task01_queue.c:55","description":"Niepełny rekord nie wygląda jak dowód.","evidence":"committed zapisany po payload."} ]}]}] }, { "id":"A5","label":"FLOW","title":"Flow","order":40,"orientation":"landscape", "description":"Jeden deterministyczny przebieg E01–E10.", "content_tex":"Sekwencja eksponuje moment blokowania, preemption po handoff i moment kopiowania wartości.", "assets":[{"stem":"a5-flow","diagram_kind":"sequence","title":"A5 · empty/full/FIFO","caption":"A5 FLOW — trzy komunikaty przez kolejkę długości 1.","label":"fig:a5-flow","alt":"Sekwencja receiver, queue, sender i verifier.","phases":[{"id":"replay","label":"EMPTY / FULL / COPY / PASS","steps":[ {"id":"config","number":1,"label":"configuration committed","mode":"RUN","event_id":"E01","strategy_ref":"run.queue-state","svg_label":"01","code_ref":"src/tasks/task01_queue.c:265","snapshot_ref":"task01.config","description":"RAM i evidence zaczynają od zera.","evidence":"count=1 przed create."}, {"id":"empty","number":2,"label":"receiver reaches empty receive","mode":"RUN","event_id":"E02","strategy_ref":"run.queue-state","svg_label":"02","code_ref":"src/tasks/task01_queue.c:89","snapshot_ref":"task01.receiver-empty","description":"Kolejne API przeniesie task do Blocked.","evidence":"occupancy=0."}, {"id":"send1","number":3,"label":"sender publishes M1","mode":"RUN","event_id":"E03","strategy_ref":"run.queue-state","svg_label":"03","code_ref":"src/tasks/task01_queue.c:153","snapshot_ref":"task01.send-m1","description":"Send budzi wyższego receivera.","evidence":"sender SP i M1."}, {"id":"recv1","number":4,"label":"receiver obtains M1","mode":"RUN","event_id":"E04","strategy_ref":"run.queue-state","svg_label":"04","code_ref":"src/tasks/task01_queue.c:97","snapshot_ref":"task01.receive-m1","description":"Handoff następuje przed dalszym kodem sendera.","evidence":"M1=0x1111."}, {"id":"mutate","number":5,"label":"M2 stored, source mutated","mode":"RUN","event_id":"E05","strategy_ref":"run.byte-copy","svg_label":"05","code_ref":"src/tasks/task01_queue.c:164","snapshot_ref":"task01.mutated-source","description":"Queue storage zachowuje poprzednie bajty.","evidence":"source=0xDEAD, occupancy=1."}, {"id":"full","number":6,"label":"sender blocked with M3","mode":"RUN","event_id":"E06","strategy_ref":"run.queue-state","svg_label":"06","code_ref":"src/tasks/task01_queue.c:103","snapshot_ref":"task01.sender-blocked","description":"Receiver obserwuje publiczny stan.","evidence":"eBlocked i one item."}, {"id":"recv2","number":7,"label":"receiver gets copied M2","mode":"RUN","event_id":"E07","strategy_ref":"run.byte-copy","svg_label":"07","code_ref":"src/tasks/task01_queue.c:118","snapshot_ref":"task01.receive-m2","description":"Mutacja źródła nie zmienia queue storage.","evidence":"M2=0x2222."}, {"id":"recv3","number":8,"label":"receiver gets FIFO M3","mode":"RUN","event_id":"E08","strategy_ref":"run.queue-state","svg_label":"08","code_ref":"src/tasks/task01_queue.c:129","snapshot_ref":"task01.receive-m3","description":"Zwolniony sender kończy send.","evidence":"M3=0x3333."}, {"id":"sender-done","number":9,"label":"sender completes after unblock","mode":"RUN","event_id":"E09","strategy_ref":"run.queue-state","svg_label":"09","code_ref":"src/tasks/task01_queue.c:177","snapshot_ref":"task01.sender-finished","description":"Publikacja następuje przed self-delete.","evidence":"nine committed events."}, {"id":"pass","number":10,"label":"verifier commits PASS","mode":"RUN","event_id":"E10","strategy_ref":"run.pass","svg_label":"10","code_ref":"src/tasks/task01_queue.c:228","snapshot_ref":"task01.pass","description":"Najniższy priorytet sprawdza całość.","evidence":"PASS=1 i digest."} ]}]}] }, { "id":"A6","label":"STATE","title":"State","order":50,"orientation":"landscape", "description":"Occupancy kolejki jako przyczyna Blocked i Ready.", "content_tex":"EMPTY może blokować receive, FULL może blokować send. Operacja przeciwna zwalnia wait list i może natychmiast uruchomić task o wyższym priorytecie.", "assets":[{"stem":"a6-state","title":"A6 · occupancy i task state","caption":"A6 STATE — EMPTY, FULL, handoff i FIFO.","label":"fig:a6-state","alt":"Maszyna stanów kolejki długości 1.","phases":[{"id":"state","label":"OCCUPANCY / WAIT LISTS","steps":[ {"id":"empty","number":1,"label":"EMPTY blocks receiver","mode":"RUN","event_id":"E02","strategy_ref":"run.queue-state","svg_label":"01","code_ref":"src/tasks/task01_queue.c:91","snapshot_ref":"task01.receiver-empty","description":"Brak itemu jest przyczyną blokady.","evidence":"occupancy=0."}, {"id":"full","number":2,"label":"FULL blocks sender","mode":"RUN","event_id":"E06","strategy_ref":"run.queue-state","svg_label":"02","code_ref":"src/tasks/task01_queue.c:173","snapshot_ref":"task01.sender-blocked","description":"Brak slotu jest przyczyną blokady.","evidence":"occupancy=1, eBlocked."}, {"id":"handoff","number":3,"label":"receive frees slot and sender","mode":"RUN","event_id":"E07","strategy_ref":"run.queue-state","svg_label":"03","code_ref":"src/tasks/task01_queue.c:112","snapshot_ref":"task01.receive-m2","description":"Wait list sendera staje się Ready.","evidence":"M2 opuszcza storage."}, {"id":"done","number":6,"label":"FIFO trace is complete","mode":"RUN","event_id":"E10","strategy_ref":"run.pass","svg_label":"06","code_ref":"src/tasks/task01_queue.c:220","snapshot_ref":"task01.pass","description":"Verifier widzi 1,2,3.","evidence":"PASS=1."} ]}]}] }, { "id":"A7","label":"RUNTIME","title":"Runtime","order":60,"orientation":"landscape", "description":"Pełna kolejka, dwa TCB i dwa różne obszary pamięci.", "content_tex":"Checkpoint E06 łączy publiczny stan taska, queue occupancy, stack sendera i niezależny storage M2.", "assets":[{"stem":"a7-runtime","title":"A7 · full queue snapshot","caption":"A7 RUNTIME — sender Blocked na pełnej kolejce.","label":"fig:a7-runtime","alt":"Runtime snapshot E06.","phases":[{"id":"runtime","label":"TCB / QUEUE / STACK / STORAGE","steps":[ {"id":"experiment","number":1,"label":"experiment stores public evidence","mode":"RUN","event_id":"E06","strategy_ref":"run.queue-state","svg_label":"01","code_ref":"src/tasks/task01_queue.c:103","snapshot_ref":"task01.sender-blocked","description":"Aplikacja nie czyta prywatnego TCB.","evidence":"eBlocked i waiting=1."}, {"id":"queue","number":2,"label":"queue storage owns M2","mode":"RUN","event_id":"E05","strategy_ref":"run.byte-copy","svg_label":"02","code_ref":"src/tasks/task01_queue.c:160","snapshot_ref":"task01.mutated-source","description":"Queue zawiera kopię sprzed mutacji.","evidence":"queued M2=0x2222."}, {"id":"sender","number":3,"label":"sender waits with pending M3","mode":"RUN","event_id":"E06","strategy_ref":"run.queue-state","svg_label":"03","code_ref":"src/tasks/task01_queue.c:173","snapshot_ref":"task01.sender-blocked","description":"PC pozostaje wewnątrz send path.","evidence":"TCB state i sender SP."}, {"id":"receiver","number":4,"label":"receiver runs on another stack","mode":"RUN","event_id":"E06","strategy_ref":"run.queue-state","svg_label":"04","code_ref":"src/tasks/task01_queue.c:103","snapshot_ref":"task01.sender-blocked","description":"Receiver wykonuje obserwację.","evidence":"receiver SP != sender SP."}, {"id":"source","number":5,"label":"source lifetime remains sender-owned","mode":"RUN","event_id":"E05","strategy_ref":"run.byte-copy","svg_label":"05","code_ref":"src/tasks/task01_queue.c:164","snapshot_ref":"task01.mutated-source","description":"Zmiana źródła jest legalna po send.","evidence":"payload=0xDEAD."} ]}]}] }, { "id":"A8","label":"PATTERNS","title":"Patterns","order":70,"orientation":"portrait", "description":"Jawny wybór value message albo pointer ownership.", "content_tex":"FreeRTOS zawsze kopiuje bytes itemu. Gdy item jest wskaźnikiem, kopiowany jest tylko adres, dlatego lifetime i mutability wymagają osobnego kontraktu.", "assets":[{"stem":"a8-patterns","title":"A8 · message ownership","caption":"A8 PATTERNS — value kontra pointer message.","label":"fig:a8-patterns","alt":"Porównanie ownership danych kolejki.","phases":[{"id":"ownership","label":"VALUE / POINTER / CONTRACT","steps":[ {"id":"value","number":1,"label":"value message owns copied bytes","mode":"CODE","strategy_ref":"code.queue-contract","svg_label":"01","code_ref":"include/task01_queue_model.h:7","description":"Mały payload jest samowystarczalny.","evidence":"checksum w itemie."}, {"id":"pointer","number":2,"label":"pointer message copies address only","mode":"CODE","strategy_ref":"code.queue-contract","svg_label":"02","code_ref":"include/task01_queue_model.h:7","description":"Alternatywa wymaga zewnętrznego lifetime.","evidence":"Nie jest używana w FC05."}, {"id":"contract","number":3,"label":"ownership must be explicit","mode":"CODE","strategy_ref":"code.queue-contract","svg_label":"03","code_ref":"doc/card-plan.md:1","description":"API musi określić mutability i release.","evidence":"Decyzja projektowa."}, {"id":"decision","number":4,"label":"FC05 chooses value message","mode":"RUN","event_id":"E07","strategy_ref":"run.byte-copy","svg_label":"04","code_ref":"src/tasks/task01_queue.c:118","snapshot_ref":"task01.receive-m2","description":"Eksperyment dowodzi niezależności kopii.","evidence":"0x2222 mimo 0xDEAD."} ]}]}] } ], "learning": { "model_label":"Model kolejki", "model":"Uczeń wiąże EMPTY/FULL z wait lists oraz rozróżnia storage kolejki od pamięci producenta.", "replay_label":"Replay FIFO i copy", "replay":"Uczeń odtwarza E01–E10 i pokazuje publiczny stan, occupancy, payload, PC/SP i digest.", "criterion":"Program kończy się PASS; sender jest eBlocked na FULL; odbiór ma kolejność 1/2/3; M2 pozostaje 0x2222 po mutacji źródła; ślad i hashe są stabilne.", "requirement":"Zaprojektowanie i zbadanie deterministycznej komunikacji przez kolejkę FreeRTOS." }, "task": { "short_label":"queue i FIFO", "title":"Kolejka długości 1: empty, full, copy i FIFO", "prompt_tex":"Przejdź po A2, A5, A6, A7 i A8. Odtwórz E01–E10, wskaż dwa różne wait lists i udowodnij byte-copy M2.", "conclusion_tex":"Queue occupancy jest przyczyną blokowania, priorytet steruje handoff, a queue storage zachowuje własną kopię itemu. Pointer payload wymagałby osobnego kontraktu ownership.", "flow":[ {"kind":"block","id":"predict","title":"A — model i przewidywanie","content_tex":"Najpierw rozrysuj EMPTY/FULL i kolejność M1–M3.","steps":[ {"id":"wait-lists","title":"Wskaż dwa wait lists.","content_tex":"Określ, które API blokuje task na EMPTY i FULL."}, {"id":"copy","title":"Przewidź wynik mutacji.","content_tex":"Zapisz wartość M2 przed uruchomieniem replay."} ]}, {"kind":"block","id":"replay","title":"B — replay Hazard3","content_tex":"Każdy RUN rozpoczyna czystą RAM i kończy stabilnym checkpointem.","steps":[ {"id":"empty-full","title":"Zbadaj E02 i E06.","content_tex":"Porównaj occupancy oraz stan receivera/sendera."}, {"id":"byte-copy","title":"Zbadaj E05 i E07.","content_tex":"Porównaj source, queue storage i received M2."}, {"id":"finish","title":"Zbadaj E08–E10.","content_tex":"Sprawdź FIFO, bounded log i digest."} ]}, {"kind":"exercise","id":"pointer-contract","title":"Ćwiczenie — pointer payload","prompt_tex":"Zmień item na wskaźnik do statycznej puli trzech buforów. Zdefiniuj ownership, mutability i moment zwrotu slotu, a następnie dodaj checksum generacji zapobiegający ABA.","evidence_tex":"Diagram A8 po zmianie, snapshot puli oraz kanoniczny digest trzech komunikatów.","criterion":"Uczeń nie dereferencjonuje obiektu po zwrocie slotu i potrafi wskazać właściciela w każdym checkpointcie.","based_on":["predict","replay"]} ] } }