Files
lab-rv32i-freertos-heap4/json/card_source.json
T
2026-07-20 12:04:14 +02:00

1173 lines
42 KiB
JSON

{
"$schema": "../../../tools/card-layouts/schemas/card-source.schema.json",
"schema": "esc-card-source.v1",
"card": {
"id": "mpabi-freertos-c-01-heap4-mechanics",
"series": "freertos-c",
"series_title": "FreeRTOS C",
"number": "01",
"count": "15",
"slug": "heap4-mechanics",
"title": "heap_4: From Linear Cursor to Reusable Blocks",
"topic": "headers, address-ordered free list, first-fit, split, coalescing and heap statistics",
"project": "Freestanding C nad FreeRTOS",
"subject": "Informatyka",
"level": "Rok 2 · L01 · RV32I/Hazard3",
"revision_date": "2026-07-20T00:00:00+02:00",
"status": "Gotowa",
"version": "v00.01",
"uuid": "87e48095-7793-54e3-9230-4e61d31bd459",
"author": "M. Pabiszczak",
"year": "2026"
},
"generated": {
"tex": "doc/generated/main.tex",
"html": "web/index.html",
"html_css": "web/style.css",
"html_tree_inspector": false,
"react_app": true
},
"render_dictionary": false,
"viewpoints": [
{
"id": "A1",
"label": "CONTEXT",
"subtitle": "from cursor to reusable heap",
"status": "enabled",
"target_label": "fig:a1-context"
},
{
"id": "A2",
"label": "STRUCTURE",
"subtitle": "headers and address order",
"status": "enabled",
"target_label": "fig:a2-structure"
},
{
"id": "A3",
"label": "DISPATCH",
"subtitle": "not used by the allocator",
"status": "unavailable",
"reason": "heap_4 is called directly through pvPortMalloc and vPortFree; there is no callback or dispatch mechanism to explain."
},
{
"id": "A4",
"label": "APPLICATION",
"subtitle": "experiment is already explicit",
"status": "unavailable",
"reason": "The concrete A/B/C allocation experiment is completely represented by A5 and its two runtime checkpoints."
},
{
"id": "A5",
"label": "FLOW",
"subtitle": "allocate, fragment, coalesce",
"status": "enabled",
"target_label": "fig:a5-flow"
},
{
"id": "A6",
"label": "STATE",
"subtitle": "block list and statistics",
"status": "enabled",
"target_label": "fig:a6-state"
},
{
"id": "A7",
"label": "RUNTIME",
"subtitle": "Hazard3 evidence",
"status": "enabled",
"target_label": "fig:a7-runtime"
},
{
"id": "A8",
"label": "PATTERNS",
"subtitle": "mechanism, not a separate page",
"status": "unavailable",
"reason": "First-fit and the intrusive address-ordered free list are already proved in A2 and A5; a separate pattern page would duplicate them."
}
],
"debug_strategies": {
"code.model": {
"id": "code.model",
"kind": "code",
"action": "open",
"expected_observations": [
"requested size includes header and alignment",
"free list is ordered by block address"
],
"assertions": [
"payload begins after the header",
"split preserves the total arena size"
],
"evidence_fields": [
"code_ref",
"header bytes",
"wanted bytes",
"block address"
],
"prerequisites": [
"K&R 5.4 linear allocator complete"
],
"layout": [
"header",
"payload",
"remainder",
"address-ordered list"
],
"commands": [
"rg -n 'ModelBlock|align_up|insert_and_coalesce' src/tasks"
]
},
"code.heap4": {
"id": "code.heap4",
"kind": "code",
"action": "open",
"expected_observations": [
"heap_4 keeps metadata inside the arena",
"free blocks form one address-ordered intrusive list"
],
"assertions": [
"allocated bit is stored in the size field",
"xStart and pxEnd are sentinels"
],
"evidence_fields": [
"symbol",
"list link",
"block size",
"arena address"
],
"prerequisites": [
"FreeRTOS-Kernel V11.3.0 available"
],
"layout": [
"ucHeap",
"BlockLink_t",
"xStart",
"pxEnd"
],
"commands": [
"rg -n 'BlockLink_t|prvInsertBlockIntoFreeList|pvPortMalloc|vPortFree' vendor/FreeRTOS-Kernel/portable/MemMang/heap_4.c"
]
},
"run.fragmented": {
"id": "run.fragmented",
"kind": "run",
"action": "replay",
"expected_observations": [
"A and C are free while B remains allocated",
"the free list contains two blocks"
],
"assertions": [
"xNumberOfFreeBlocks equals 2",
"current free bytes exceed the largest free block"
],
"evidence_fields": [
"free blocks",
"largest block",
"free bytes",
"heap bytes"
],
"prerequisites": [
"clean Hazard3 RAM",
"task03 image loaded"
],
"layout": [
"source",
"free-list stats",
"ucHeap memory",
"registers"
],
"commands": [
"break heap4_fragmented_checkpoint",
"print g_fragmented_stats",
"x/64bx ucHeap"
]
},
"run.coalesced": {
"id": "run.coalesced",
"kind": "run",
"action": "replay",
"expected_observations": [
"freeing B joins both neighbours",
"the arena returns to one reusable block"
],
"assertions": [
"final free equals initial free",
"minimum-ever does not rise after free",
"oversized allocation returns NULL and calls the hook once"
],
"evidence_fields": [
"final free",
"largest block",
"minimum-ever",
"OOM hook",
"PASS"
],
"prerequisites": [
"fragmented checkpoint reached"
],
"layout": [
"final stats",
"minimum-ever",
"OOM hook",
"PASS"
],
"commands": [
"break task03_debug_checkpoint",
"print g_final_stats",
"print g_task03_pass"
]
},
"code.identity": {
"id": "code.identity",
"kind": "code",
"action": "open",
"expected_observations": [
"upstream heap_4 symbols are linked",
"checkpoint and ucHeap symbols remain observable"
],
"assertions": [
"pvPortMalloc and vPortFree are present",
"prvInsertBlockIntoFreeList is present"
],
"evidence_fields": [
"source blob",
"ELF SHA256",
"image SHA256",
"symbol"
],
"prerequisites": [
"built ELF"
],
"layout": [
"symbols",
"sections",
"artifact hashes"
],
"commands": [
"tests/check_task03_elf.sh build/task03_freertos_heap4/prog.elf"
]
}
},
"template": "templates/karta-klasyczna.json",
"title_block": {
"category": "KARTA PRACY · INFORMATYKA",
"prepared_by": "M. Pabiszczak",
"prepared_on": "2026-07-20T00:00:00+02:00",
"title": "heap_4: From Linear Cursor to Reusable Blocks",
"url": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/aea09ab7-9bfd-5368-8d1b-c8724871c1a7",
"repository_url": "https://zsl-gitea.mpabi.pl/edu-freertos-c/lab-rv32i-freertos-heap4",
"url_host_uuid": "dce7fb9d-7b2f-5d49-96a2-3a30d3070b84",
"url_domain": "mpabi.pl",
"doc_uuid": "aea09ab7-9bfd-5368-8d1b-c8724871c1a7",
"revision": "v00.01",
"issued_on": "2026-07-20T00:00:00+02:00",
"series": "FREERTOS-C-01",
"document_type": "karta pracy",
"tool": "card-layouts",
"show_qr": true,
"show_repository_qr": true,
"height_cm": 2.6,
"repeat_on_every_page": true,
"replace_front_matter": true
},
"front_page_scope": {
"title": "Cel karty",
"content_tex": "Uczeń przechodzi od liniowego kursora z K\\&R 5.4 do wielokrotnego użycia tej samej areny: rozpoznaje nagłówek bloku, first-fit, split oraz dwustronne scalanie wolnych bloków w heap\\_4.",
"scope_title": "Zakres karty",
"scope_content_tex": "Dwa małe modele C przygotowują przewidywanie. Główny replay uruchamia prawdziwe FreeRTOS-Kernel V11.3.0 heap\\_4 na RV32I/Hazard3 i dowodzi fragmentacji, pełnego scalenia, minimum-ever oraz kontrolowanego OOM.",
"scope_table": {
"headers": [
"Lekcja",
"Task",
"Najważniejsza idea",
"Priorytet",
"Status",
"Version"
],
"rows": [
{
"chapter": "FC01",
"task": "Task01",
"idea_tex": "headers, address-ordered free list, first-fit, split, coalescing and heap statistics",
"priority": "główny",
"status": "ready",
"version": "v00.01",
"key": true
}
]
}
},
"side_margin_tree_layout": {
"columns": [
{
"id": "zawodowe",
"label": "TECH",
"side": "left",
"tree": "WE -> EK -> KW",
"description": "Dowody runtime."
},
{
"id": "ogolne",
"label": "OG",
"side": "right",
"tree": "WE -> EN -> KW",
"description": "Model FreeRTOS C."
}
]
},
"learning_effects": {
"FC01.EN01": {
"bloom_level": "Analiza",
"label": "Model bloków heap_4",
"text": "Uczeń wyjaśnia nagłówek, wyrównanie, first-fit, split, adresową listę wolnych bloków i dwustronne coalescing.",
"assessment_criteria": [
"FC01.KW01"
]
},
"FC01.EK01": {
"bloom_level": "Zastosowanie",
"label": "Fragmentacja i odzyskanie areny",
"text": "Uczeń odtwarza dwa checkpointy Hazard3 i odróżnia current free, largest free block oraz minimum-ever.",
"assessment_criteria": [
"FC01.KW01"
]
}
},
"assessment_criteria": {
"FC01.KW01": {
"text": "Model 13 -> 32 bajty; poprawna predykcja dwóch wolnych bloków po free(A,C) i jednego po free(B); final free = initial free; minimum-ever nie rośnie; OOM zwraca NULL; PASS=1.",
"learning_effects": [
"FC01.EN01",
"FC01.EK01"
]
}
},
"educational_requirements": {
"FC01.WE01": {
"text": "Analiza i weryfikacja mechaniki dynamicznej alokacji heap_4 w FreeRTOS C na RV32I/Hazard3.",
"label": "headers, address-ordered free list, first-fit, split, coalescing and heap statistics",
"learning_effects": [
"FC01.EN01",
"FC01.EK01"
],
"learning_tree": {
"schema": "we-learning-tree.v1",
"policy": "Każda aktywna kotwica wskazuje kod lub checkpoint.",
"ogolne": [
{
"effect_ref": "FC01.EN01",
"display": "EN LOCAL RTOS.01",
"source": "LOCAL",
"official": "RTOS",
"local": "01",
"kind": "EN",
"tree_id": "FC01.WE01.OG.LOCAL.RTOS.01",
"text": "Uczeń wyjaśnia nagłówek, wyrównanie, first-fit, split, adresową listę wolnych bloków i dwustronne coalescing.",
"kw": [
{
"criterion_ref": "FC01.KW01",
"display": "KW LOCAL RTOS.01",
"source": "LOCAL",
"kind": "KW",
"official": "RTOS",
"local": "01",
"text": "Łączy kod, diagram i zweryfikowany dowód."
}
]
}
],
"zawodowe": [
{
"effect_ref": "FC01.EK01",
"display": "EK LOCAL RTOS.01",
"source": "LOCAL",
"official": "RTOS",
"local": "01",
"kind": "EK",
"tree_id": "FC01.WE01.TECH.LOCAL.RTOS.01",
"text": "Uczeń odtwarza dwa checkpointy Hazard3 i odróżnia current free, largest free block oraz minimum-ever.",
"kw": [
{
"criterion_ref": "FC01.KW01",
"display": "KW LOCAL RTOS.01",
"source": "LOCAL",
"kind": "KW",
"official": "RTOS",
"local": "01",
"text": "Łączy kod, diagram i zweryfikowany dowód."
}
]
}
]
}
}
},
"debug_checkpoints": {
"schema": "stem-debug-checkpoints.v1",
"semantics": "deterministic-replay",
"artifact": {
"source": "src/tasks/task03_freertos_heap4.c",
"source_git_blob": "a73eb936ad3896bc212fe7fcc1e9918e4430746e",
"elf": "build/task03_freertos_heap4/prog.elf",
"hazard3_elf_sha256": "6a7b20ad3447d0877e7ecff39087653e32f4931e2364bdc2ea533bf5d70f7854",
"hazard3_image": "build/task03_freertos_heap4/prog.bin",
"hazard3_image_sha256": "c9d9b45546305671fa6172156fbfc58b90b145fe952e3b7cfe8cce0da0f5e7dd"
},
"targets": {
"hazard3-sim": {
"adapter": "hazard3-reset-load-replay.v1",
"baseline": "restart-backend",
"clean_ram": true
}
},
"items": {
"task01.fragmented": {
"event_id": "E01",
"stop": {
"symbol": "heap4_fragmented_checkpoint",
"offset": 0
},
"verify": {
"expressions": [
{
"expr": "g_fragmented_stats.xNumberOfFreeBlocks",
"equals": 2
},
{
"expr": "g_initial_free > g_after_allocations",
"equals": 1
},
{
"expr": "g_allocations_aligned",
"equals": 1
}
]
}
},
"task01.coalesced": {
"event_id": "E02",
"stop": {
"symbol": "task03_debug_checkpoint",
"offset": 0
},
"verify": {
"expressions": [
{
"expr": "g_final_stats.xNumberOfFreeBlocks",
"equals": 1
},
{
"expr": "g_final_free == g_initial_free",
"equals": 1
},
{
"expr": "g_oom_is_null",
"equals": 1
},
{
"expr": "g_task03_pass",
"equals": 1
}
]
}
}
}
},
"sections": [
{
"title": "A1 — Od liniowego kursora do odzyskiwalnej areny",
"order": 10,
"content_kind": "prose",
"asset_page_mode": "one-per-page",
"page_orientation": "portrait",
"content_tex": "Kursor liniowy potrafi tylko rosnąć. heap_4 dodaje metadane i listę wolnych bloków, dzięki czemu zwolniona pamięć wraca do ponownego użycia.",
"assets": [
{
"path": "assets/a1-context.png",
"html_path": "assets/a1-context.svg",
"source_path": "assets/a1-context.puml",
"caption": "A1 CONTEXT — od żądania aplikacji do rzeczywistych bajtów ucHeap.",
"label": "fig:a1-context",
"alt": "A1 · granice odpowiedzialności heap_4",
"kind": "diagram",
"width": 1.0,
"page_grid": {
"columns": 1,
"rows": 1,
"page_width": 660,
"page_height": 760,
"overview": true,
"step_tiles": {
"application": 1,
"api": 1,
"allocator": 1,
"arena": 1,
"target": 1
}
},
"interactive": {
"kind": "uml-class",
"storage_key": "fc01-a1-context",
"title": "A1 · granice odpowiedzialności heap_4",
"task": {
"id": "task01",
"label": "Task01 · mechanika heap_4"
},
"block": {
"id": "a1",
"label": "A1 CONTEXT",
"description": "Aplikacja korzysta z publicznego API, heap_4 zarządza jedną areną, a Hazard3 pokazuje jej rzeczywisty stan."
},
"phases": [
{
"id": "boundary",
"label": "APPLICATION / API / ALLOCATOR / TARGET",
"steps": [
{
"id": "application",
"number": 1,
"label": "aplikacja żąda i zwalnia bajty",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "01",
"code_ref": "src/tasks/task03_freertos_heap4.c:78",
"description": "Aplikacja zna wyłącznie publiczne API i wskaźniki payloadu.",
"evidence": "trzy żądania 24, 40 i 16 bajtów"
},
{
"id": "api",
"number": 2,
"label": "FreeRTOS wystawia malloc, free i statystyki",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "02",
"code_ref": "src/tasks/task03_freertos_heap4.c:90",
"description": "API oddziela eksperyment od prywatnej listy bloków.",
"evidence": "pvPortMalloc, vPortFree, vPortGetHeapStats"
},
{
"id": "allocator",
"number": 3,
"label": "heap_4 zarządza blokami i kolejnością adresów",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "03",
"code_ref": "src/tasks/task03_freertos_heap4.c:70",
"description": "Prywatny mechanizm wybiera, dzieli i scala wolne bloki.",
"evidence": "reset i inicjalizacja listy"
},
{
"id": "arena",
"number": 4,
"label": "ucHeap jest jedną ograniczoną areną",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "04",
"code_ref": "src/tasks/task03_freertos_heap4.c:11",
"description": "Metadane i payload znajdują się w tej samej statycznej tablicy.",
"evidence": "4096 bajtów, wyrównanie portu"
},
{
"id": "target",
"number": 5,
"label": "Hazard3 ujawnia adresy i bajty",
"mode": "CODE",
"strategy_ref": "code.identity",
"svg_label": "05",
"code_ref": "tests/check_task03_elf.sh:8",
"description": "ELF łączy kod, symbole i pamięć obserwowaną przez GDB.",
"evidence": "ucHeap i checkpointy w symbolach ELF"
}
]
}
]
}
}
]
},
{
"title": "A2 — Nagłówek, payload i lista wolnych bloków",
"order": 20,
"content_kind": "prose",
"asset_page_mode": "one-per-page",
"page_orientation": "portrait",
"content_tex": "Rozmiar żądania rośnie o nagłówek i wyrównanie. Duży wolny blok może zostać rozdzielony na blok przydzielony i użyteczną resztę.",
"assets": [
{
"path": "assets/a2-structure.png",
"html_path": "assets/a2-structure.svg",
"source_path": "assets/a2-structure.puml",
"caption": "A2 STRUCTURE — metadane wewnątrz areny i adresowo uporządkowana lista.",
"label": "fig:a2-structure",
"alt": "A2 · struktura bloku i listy",
"kind": "diagram",
"width": 1.0,
"page_grid": {
"columns": 1,
"rows": 1,
"page_width": 660,
"page_height": 760,
"overview": true,
"step_tiles": {
"header": 1,
"alignment": 1,
"split": 1,
"address-order": 1,
"right-join": 1,
"left-join": 1
}
},
"interactive": {
"kind": "uml-class",
"storage_key": "fc01-a2-structure",
"title": "A2 · struktura bloku i listy",
"task": {
"id": "task01",
"label": "Task01 · mechanika heap_4"
},
"block": {
"id": "a2",
"label": "A2 STRUCTURE",
"description": "Każdy blok ma nagłówek, a tylko wolne bloki wykorzystują wskaźnik next do budowy listy uporządkowanej adresami."
},
"phases": [
{
"id": "blocks",
"label": "HEADER / PAYLOAD / FREE LIST",
"steps": [
{
"id": "header",
"number": 1,
"label": "nagłówek przechowuje next i size",
"mode": "CODE",
"strategy_ref": "code.model",
"svg_label": "01",
"code_ref": "src/tasks/heap4_model.h:11",
"description": "ModelBlock pokazuje minimalny kontrakt metadanych heap_4.",
"evidence": "dwa słowa metadanych"
},
{
"id": "alignment",
"number": 2,
"label": "żądanie obejmuje nagłówek i wyrównanie",
"mode": "CODE",
"strategy_ref": "code.model",
"svg_label": "02",
"code_ref": "src/tasks/task01_first_fit_split.c:3",
"description": "13 bajtów payloadu zajmuje 32 bajty całego bloku.",
"evidence": "header=16, wanted=32"
},
{
"id": "split",
"number": 3,
"label": "split pozostawia osobny wolny blok",
"mode": "CODE",
"strategy_ref": "code.model",
"svg_label": "03",
"code_ref": "src/tasks/task01_first_fit_split.c:43",
"description": "Reszta zachowuje własny nagłówek i trafia na listę wolnych.",
"evidence": "32 + 224 = 256"
},
{
"id": "address-order",
"number": 4,
"label": "lista jest uporządkowana rosnącymi adresami",
"mode": "CODE",
"strategy_ref": "code.model",
"svg_label": "04",
"code_ref": "src/tasks/task02_address_order_coalesce.c:29",
"description": "Pozycja w liście ujawnia bezpośredniego poprzednika i następnika.",
"evidence": "previous < block < current"
},
{
"id": "right-join",
"number": 5,
"label": "blok scala się z prawym sąsiadem",
"mode": "CODE",
"strategy_ref": "code.model",
"svg_label": "05",
"code_ref": "src/tasks/task02_address_order_coalesce.c:40",
"description": "Koniec zwalnianego bloku równy adresowi current oznacza ciągłość.",
"evidence": "block + size == current"
},
{
"id": "left-join",
"number": 6,
"label": "wynik scala się z lewym sąsiadem",
"mode": "CODE",
"strategy_ref": "code.model",
"svg_label": "06",
"code_ref": "src/tasks/task02_address_order_coalesce.c:48",
"description": "Drugi test domyka dwustronne scalanie w jeden blok.",
"evidence": "previous + size == block"
}
]
}
]
}
}
]
},
{
"title": "A5 — First-fit, fragmentacja i pełne scalenie",
"order": 50,
"content_kind": "prose",
"asset_page_mode": "one-per-page",
"page_orientation": "landscape",
"content_tex": "Uczeń najpierw przewiduje liczbę wolnych bloków. RUN odtwarza dwa stabilne stany: po zwolnieniu A i C oraz po zwolnieniu B.",
"assets": [
{
"path": "assets/a5-flow.png",
"html_path": "assets/a5-flow.svg",
"source_path": "assets/a5-flow.puml",
"caption": "A5 FLOW — inicjalizacja, A/B/C, fragmentacja, dwustronne scalenie i OOM.",
"label": "fig:a5-flow",
"alt": "A5 · pełny przebieg heap_4",
"kind": "diagram",
"width": 1.0,
"page_grid": {
"columns": 1,
"rows": 1,
"page_width": 940,
"page_height": 560,
"overview": true,
"step_tiles": {
"reset": 1,
"alloc-a": 1,
"alloc-bc": 1,
"free-ac": 1,
"fragmented": 1,
"free-b": 1,
"coalesced": 1,
"oom": 1
}
},
"interactive": {
"kind": "uml-sequence",
"storage_key": "fc01-a5-flow",
"title": "A5 · pełny przebieg heap_4",
"task": {
"id": "task01",
"label": "Task01 · mechanika heap_4"
},
"block": {
"id": "a5",
"label": "A5 FLOW",
"description": "Jeden przebieg A/B/C przechodzi od pustej areny przez trzy przydziały i dwa wolne obszary do ponownie scalonego bloku."
},
"phases": [
{
"id": "replay",
"label": "ALLOCATE / FRAGMENT / COALESCE",
"steps": [
{
"id": "reset",
"number": 1,
"label": "reset i inicjalizacja areny",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "01",
"code_ref": "src/tasks/task03_freertos_heap4.c:70",
"description": "Pierwsze małe żądanie inicjalizuje sentinele i listę.",
"evidence": "initial free = 4080"
},
{
"id": "alloc-a",
"number": 2,
"label": "first-fit przydziela A",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "02",
"code_ref": "src/tasks/task03_freertos_heap4.c:78",
"description": "Pierwszy wystarczający blok zostaje rozdzielony.",
"evidence": "A payload = 24"
},
{
"id": "alloc-bc",
"number": 3,
"label": "kolejne split tworzą B i C",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "03",
"code_ref": "src/tasks/task03_freertos_heap4.c:79",
"description": "B i C zajmują kolejne fragmenty tego samego wolnego bloku.",
"evidence": "after allocations = 3952"
},
{
"id": "free-ac",
"number": 4,
"label": "zwolnij A i C, pozostaw B",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "04",
"code_ref": "src/tasks/task03_freertos_heap4.c:88",
"description": "C scala się z prawą resztą, ale B oddziela ją od A.",
"evidence": "dwa wolne bloki"
},
{
"id": "fragmented",
"number": 5,
"label": "checkpoint fragmentacji",
"mode": "RUN",
"event_id": "E01",
"strategy_ref": "run.fragmented",
"svg_label": "05",
"code_ref": "src/tasks/task03_freertos_heap4.c:91",
"snapshot_ref": "task01.fragmented",
"description": "GDB zatrzymuje program przy dwóch wolnych blokach.",
"evidence": "xNumberOfFreeBlocks = 2"
},
{
"id": "free-b",
"number": 6,
"label": "zwolnij B między dwoma sąsiadami",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "06",
"code_ref": "src/tasks/task03_freertos_heap4.c:93",
"description": "Wstawienie adresowe umożliwia dwa testy ciągłości.",
"evidence": "A + B + C + reszta są ciągłe"
},
{
"id": "coalesced",
"number": 7,
"label": "arena wraca do jednego bloku",
"mode": "RUN",
"event_id": "E02",
"strategy_ref": "run.coalesced",
"svg_label": "07",
"code_ref": "src/tasks/task03_freertos_heap4.c:96",
"snapshot_ref": "task01.coalesced",
"description": "Finalne statystyki dowodzą pełnego odzyskania areny.",
"evidence": "final free = initial free; blocks = 1"
},
{
"id": "oom",
"number": 8,
"label": "zbyt duże żądanie kończy się NULL",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "08",
"code_ref": "src/tasks/task03_freertos_heap4.c:98",
"description": "Kontrolowany OOM nie narusza listy i wywołuje hook dokładnie raz.",
"evidence": "NULL, hooks=1, asserts=0"
}
]
}
]
}
}
]
},
{
"title": "A6 — Stan bloku a stan statystyk",
"order": 60,
"content_kind": "prose",
"asset_page_mode": "one-per-page",
"page_orientation": "portrait",
"content_tex": "Fragmentacja to nie tylko suma wolnych bajtów. O możliwości przydziału decyduje także największy pojedynczy blok.",
"assets": [
{
"path": "assets/a6-state.png",
"html_path": "assets/a6-state.svg",
"source_path": "assets/a6-state.puml",
"caption": "A6 STATE — wolny, przydzielony, rozdzielony, scalony oraz minimum-ever.",
"label": "fig:a6-state",
"alt": "A6 · stany bloków i liczników",
"kind": "diagram",
"width": 1.0,
"page_grid": {
"columns": 1,
"rows": 1,
"page_width": 660,
"page_height": 760,
"overview": true,
"step_tiles": {
"free": 1,
"allocated": 1,
"fragmented-state": 1,
"coalesced-state": 1,
"minimum-ever": 1
}
},
"interactive": {
"kind": "uml-class",
"storage_key": "fc01-a6-state",
"title": "A6 · stany bloków i liczników",
"task": {
"id": "task01",
"label": "Task01 · mechanika heap_4"
},
"block": {
"id": "a6",
"label": "A6 STATE",
"description": "Bieżąca liczba wolnych bajtów może rosnąć po free, ale minimum-ever pozostaje historycznym minimum."
},
"phases": [
{
"id": "state",
"label": "BLOCK STATE / HEAP STATS",
"steps": [
{
"id": "free",
"number": 1,
"label": "wolny blok należy do listy",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "01",
"code_ref": "src/tasks/task03_freertos_heap4.c:70",
"description": "Stan początkowy zawiera jeden największy wolny blok.",
"evidence": "initial free = largest free"
},
{
"id": "allocated",
"number": 2,
"label": "przydzielony blok znika z listy wolnych",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "02",
"code_ref": "src/tasks/task03_freertos_heap4.c:78",
"description": "Payload jest wyrównany, a bieżąca wolna pamięć maleje.",
"evidence": "all payload pointers aligned"
},
{
"id": "fragmented-state",
"number": 3,
"label": "dwa wolne bloki nie są jednym dużym blokiem",
"mode": "RUN",
"event_id": "E01",
"strategy_ref": "run.fragmented",
"svg_label": "03",
"code_ref": "src/tasks/task03_freertos_heap4.c:90",
"snapshot_ref": "task01.fragmented",
"description": "Suma wolnego miejsca nie gwarantuje jednego dużego przydziału.",
"evidence": "free blocks = 2"
},
{
"id": "coalesced-state",
"number": 4,
"label": "scalenie przywraca jeden największy blok",
"mode": "RUN",
"event_id": "E02",
"strategy_ref": "run.coalesced",
"svg_label": "04",
"code_ref": "src/tasks/task03_freertos_heap4.c:94",
"snapshot_ref": "task01.coalesced",
"description": "Current free i largest wracają do wartości początkowej.",
"evidence": "free blocks = 1"
},
{
"id": "minimum-ever",
"number": 5,
"label": "minimum-ever zachowuje najniższy poziom",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "05",
"code_ref": "src/tasks/task03_freertos_heap4.c:95",
"description": "Historyczne minimum nie zwiększa się po odzyskaniu pamięci.",
"evidence": "minimum-ever <= after allocations"
}
]
}
]
}
}
]
},
{
"title": "A7 — Dowód w Hazard3 i ELF",
"order": 70,
"content_kind": "prose",
"asset_page_mode": "one-per-page",
"page_orientation": "landscape",
"content_tex": "Snapshot jest dowodem tylko wtedy, gdy zawiera tożsamość artefaktu, pozycję programu i wartości, które rozstrzygają hipotezę.",
"assets": [
{
"path": "assets/a7-runtime.png",
"html_path": "assets/a7-runtime.svg",
"source_path": "assets/a7-runtime.puml",
"caption": "A7 RUNTIME — kod, ELF, checkpoint, pamięć i statystyki jednego przebiegu.",
"label": "fig:a7-runtime",
"alt": "A7 · dowód runtime heap_4",
"kind": "diagram",
"width": 1.0,
"page_grid": {
"columns": 1,
"rows": 1,
"page_width": 940,
"page_height": 560,
"overview": true,
"step_tiles": {
"source": 1,
"elf": 1,
"fragmented-runtime": 1,
"memory": 1,
"final-runtime": 1
}
},
"interactive": {
"kind": "uml-class",
"storage_key": "fc01-a7-runtime",
"title": "A7 · dowód runtime heap_4",
"task": {
"id": "task01",
"label": "Task01 · mechanika heap_4"
},
"block": {
"id": "a7",
"label": "A7 RUNTIME",
"description": "Ten sam artefakt łączy źródło, symbole ELF, dwa checkpointy, pamięć ucHeap i końcowe asercje."
},
"phases": [
{
"id": "evidence",
"label": "ARTIFACT / CHECKPOINT / MEMORY / ASSERTION",
"steps": [
{
"id": "source",
"number": 1,
"label": "źródło wskazuje eksperyment A/B/C",
"mode": "CODE",
"strategy_ref": "code.identity",
"svg_label": "01",
"code_ref": "src/tasks/task03_freertos_heap4.c:62",
"description": "Blob źródła jest częścią tożsamości karty.",
"evidence": "source git blob"
},
{
"id": "elf",
"number": 2,
"label": "ELF zawiera prawdziwe symbole heap_4",
"mode": "CODE",
"strategy_ref": "code.identity",
"svg_label": "02",
"code_ref": "tests/check_task03_elf.sh:8",
"description": "Test ABI odrzuca atrapę bez pvPortMalloc, vPortFree i ucHeap.",
"evidence": "upstream symbols present"
},
{
"id": "fragmented-runtime",
"number": 3,
"label": "checkpoint pokazuje dwa wolne bloki",
"mode": "RUN",
"event_id": "E01",
"strategy_ref": "run.fragmented",
"svg_label": "03",
"code_ref": "src/tasks/task03_freertos_heap4.c:91",
"snapshot_ref": "task01.fragmented",
"description": "Neovim, GDB i pamięć zatrzymują się na tym samym stanie.",
"evidence": "blocks=2 and aligned=1"
},
{
"id": "memory",
"number": 4,
"label": "ucHeap ujawnia nagłówki i payload",
"mode": "CODE",
"strategy_ref": "code.heap4",
"svg_label": "04",
"code_ref": "src/tasks/task03_freertos_heap4.c:11",
"description": "Widok bajtów łączy diagram z realnym układem areny.",
"evidence": "aligned arena and in-band metadata"
},
{
"id": "final-runtime",
"number": 5,
"label": "checkpoint końcowy dowodzi odzyskania",
"mode": "RUN",
"event_id": "E02",
"strategy_ref": "run.coalesced",
"svg_label": "05",
"code_ref": "src/tasks/task03_freertos_heap4.c:111",
"snapshot_ref": "task01.coalesced",
"description": "Jedna ramka łączy stats, OOM, hook i PASS.",
"evidence": "final=initial, blocks=1, pass=1"
}
]
}
]
}
}
]
},
{
"title": "Task01 — mechanika heap_4",
"order": 90,
"content_kind": "tasks",
"task_refs": [
"task01"
]
}
],
"tasks": {
"task01": {
"title": "Predict and prove reusable heap_4 blocks",
"uuid": "aea09ab7-9bfd-5368-8d1b-c8724871c1a7",
"prompt_tex": "Przewidź układ bloków po alloc A/B/C, następnie po free(A,C) i free(B). Odtwórz oba checkpointy i udowodnij, że arena została scalona bez utraty minimum-ever.",
"criterion": "Model 13 -> 32 bajty; poprawna predykcja dwóch wolnych bloków po free(A,C) i jednego po free(B); final free = initial free; minimum-ever nie rośnie; OOM zwraca NULL; PASS=1.",
"conclusion_tex": "heap\\_4 odzyskuje pamięć dzięki metadanym w arenie, adresowo uporządkowanej liście i scalaniu sąsiadów. Suma wolnych bajtów nie zastępuje informacji o największym bloku, a minimum-ever jest historią, nie bieżącym stanem.",
"flow": [
{
"kind": "block",
"id": "predict",
"title": "A — przewidywanie",
"content_tex": "Przejdź przez model nagłówka, split i porządek adresowy przed uruchomieniem prawdziwego heap\\_4.",
"steps": [
{
"id": "size",
"title": "Policz rozmiar bloku.",
"content_tex": "Dodaj nagłówek do 13 bajtów i wyrównaj wynik do ośmiu."
},
{
"id": "list",
"title": "Narysuj listę po free(A,C).",
"content_tex": "Zaznacz, dlaczego przydzielony B rozdziela dwa wolne obszary."
}
]
},
{
"kind": "block",
"id": "replay",
"title": "B — replay Hazard3",
"content_tex": "Powiąż diagram z dwoma deterministycznymi checkpointami.",
"steps": [
{
"id": "fragmented",
"title": "Sprawdź E01.",
"content_tex": "Porównaj liczbę wolnych bloków, największy blok i bieżące wolne bajty."
},
{
"id": "coalesced",
"title": "Sprawdź E02.",
"content_tex": "Udowodnij pełne scalenie, zachowanie minimum-ever i kontrolowany OOM."
}
]
},
{
"kind": "exercise",
"id": "fragmentation",
"title": "Ćwiczenie — ta sama suma, inna użyteczność",
"prompt_tex": "Zaproponuj dwa układy wolnych bloków o tej samej sumie bajtów, ale tylko jeden zdolny obsłużyć wskazane duże żądanie. Uzasadnij odpowiedź przez largest-free-block.",
"evidence_tex": "Dwa diagramy listy, suma wolnych bajtów, rozmiar największego bloku i wynik pvPortMalloc.",
"criterion": "Uczeń nie utożsamia sumy wolnej pamięci z możliwością pojedynczego dużego przydziału.",
"based_on": [
"predict",
"replay"
]
}
],
"educational_requirement_refs": [
"FC01.WE01"
],
"learning_effect_refs": [
"FC01.EN01",
"FC01.EK01"
],
"assessment_criterion_ref": "FC01.KW01"
}
},
"tasks_order": [
"task01"
]
}