feat: publish FreeRTOS C FC05 card

This commit is contained in:
2026-07-19 16:36:02 +02:00
commit 3f151c823d
194 changed files with 59220 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
{
"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 aplikacjakernelHazard3 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":"E01E10 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 E01E10.",
"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 E01E10 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 E01E10, 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ść M1M3.","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 E08E10.","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"]}
]
}
}