{ "card": { "number": "01", "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", "status": "Gotowa", "version": "v00.01", "revision_date": "2026-07-20T00:00:00+02:00" }, "front": { "goal": "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": "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." }, "viewpoints": [ { "id": "A1", "label": "CONTEXT", "subtitle": "from cursor to reusable heap", "status": "enabled" }, { "id": "A2", "label": "STRUCTURE", "subtitle": "RV32 header, split and address order", "status": "enabled" }, { "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 the eight-state replay in A5." }, { "id": "A5", "label": "FLOW", "subtitle": "allocate, fragment, coalesce", "status": "enabled" }, { "id": "A6", "label": "STATE", "subtitle": "block list and statistics", "status": "enabled" }, { "id": "A7", "label": "RUNTIME", "subtitle": "Hazard3 evidence", "status": "enabled" }, { "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." } ], "artifact": { "source": "src/tasks/task03_freertos_heap4.c", "elf": "build/task03_freertos_heap4/prog.elf", "image": "build/task03_freertos_heap4/prog.bin" }, "strategies": [ { "id": "code.boundary", "kind": "code", "expected_observations": [ "application code calls only the public FreeRTOS allocation API", "the selected heap implementation is linked below that API" ], "assertions": [ "the application never edits allocator metadata", "Hazard3 observes the same ucHeap object named by the ELF" ], "evidence_fields": [ "code_ref", "public symbol", "arena symbol", "target profile" ], "prerequisites": [ "K&R 5.4 linear allocator complete" ], "layout": [ "application", "FreeRTOS API", "heap_4", "ucHeap", "Hazard3" ], "commands": [ "rg -n 'pvPortMalloc|vPortFree|vPortGetHeapStats|ucHeap' src/tasks/task03_freertos_heap4.c", "rg -n 'TASK03_KERNEL_SOURCES|HEAP4_SOURCE' Makefile" ] }, { "id": "code.heap4", "kind": "code", "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|prvHeapInit|prvInsertBlockIntoFreeList|pvPortMalloc|vPortFree' ${FREERTOS_KERNEL_PATH:-/opt/FreeRTOS-Kernel}/portable/MemMang/heap_4.c" ] }, { "id": "run.timeline", "kind": "run", "expected_observations": [ "every checkpoint is reached from a clean reset with the same ELF", "free bytes change by the aligned whole-block size, not by payload alone" ], "assertions": [ "A, B and C are aligned", "the free-byte sequence is strictly decreasing during allocation" ], "evidence_fields": [ "payload pointer", "consumed bytes", "available bytes", "free-list statistics" ], "prerequisites": [ "clean Hazard3 RAM", "task03 image loaded" ], "layout": [ "current checkpoint", "source call", "heap statistics", "ucHeap memory" ], "commands": [ "print g_initial_free", "print g_after_a_free", "print g_after_b_free", "print g_after_allocations", "print g_a_consumed", "print g_b_consumed", "print g_c_consumed" ] }, { "id": "run.fragmented", "kind": "run", "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", "print g_fragmented_free", "x/96bx ucHeap" ] }, { "id": "run.coalescing", "kind": "run", "expected_observations": [ "the replay stops after free(C), immediately before free(B)", "inside prvInsertBlockIntoFreeList the previous block is merged before the next block" ], "assertions": [ "pxIterator < pxBlockToInsert < pxIterator->pxNextFreeBlock", "the left adjacency test is evaluated before the right adjacency test" ], "evidence_fields": [ "pxIterator", "pxBlockToInsert", "left end address", "right start address" ], "prerequisites": [ "task01.fragmented replay ready" ], "layout": [ "application call vPortFree(g_b)", "prvInsertBlockIntoFreeList", "free-list links", "ucHeap addresses" ], "commands": [ "tbreak prvInsertBlockIntoFreeList", "continue", "print pxIterator", "print pxBlockToInsert", "next" ] }, { "id": "run.coalesced", "kind": "run", "expected_observations": [ "freeing B joins both neighbours", "the arena returns to one reusable block" ], "assertions": [ "final free equals initial free", "one free block is also the largest block", "minimum-ever does not rise after free" ], "evidence_fields": [ "final free", "largest block", "minimum-ever", "free blocks" ], "prerequisites": [ "fragmented checkpoint reached" ], "layout": [ "final stats", "minimum-ever", "OOM hook", "PASS" ], "commands": [ "break heap4_coalesced_checkpoint", "print g_final_stats", "print g_final_free", "print g_minimum_ever_free" ] }, { "id": "run.verified", "kind": "run", "expected_observations": [ "the oversized request returns NULL", "the malloc-failed hook runs once without corrupting the recovered arena" ], "assertions": [ "g_oom_is_null equals 1", "g_malloc_failed_hooks equals 1", "g_assert_failures equals 0", "g_task03_pass equals 1" ], "evidence_fields": [ "OOM result", "hook count", "assert count", "PASS" ], "prerequisites": [ "coalesced checkpoint reproducible" ], "layout": [ "final stats", "OOM result", "hook", "PASS" ], "commands": [ "break task03_debug_checkpoint", "print g_too_large", "print g_malloc_failed_hooks", "print g_assert_failures", "print g_task03_pass" ] }, { "id": "code.identity", "kind": "code", "expected_observations": [ "upstream heap_4 symbols are linked", "checkpoint and ucHeap symbols remain observable" ], "assertions": [ "pvPortMalloc and vPortFree are present", "prvInsertBlockIntoFreeList is present", "all eight replay checkpoint symbols are 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" ] } ], "checkpoints": { "task01.reset": { "event_id": "E01", "stop": { "symbol": "heap4_reset_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_initial_stats.xNumberOfFreeBlocks", "equals": 1 }, { "expr": "g_initial_stats.xSizeOfLargestFreeBlockInBytes == g_initial_free", "equals": 1 }, { "expr": "g_initial_free", "equals": 4080 } ] } }, "task01.alloc-a": { "event_id": "E02", "stop": { "symbol": "heap4_alloc_a_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_a != 0", "equals": 1 }, { "expr": "g_a_aligned", "equals": 1 }, { "expr": "g_a_consumed", "equals": 48 }, { "expr": "g_after_a_stats.xNumberOfFreeBlocks", "equals": 1 }, { "expr": "g_after_a_free", "equals": 4032 } ] } }, "task01.alloc-b": { "event_id": "E03", "stop": { "symbol": "heap4_alloc_b_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_b != 0", "equals": 1 }, { "expr": "g_b_aligned", "equals": 1 }, { "expr": "g_b_consumed", "equals": 64 }, { "expr": "g_after_b_free < g_after_a_free", "equals": 1 }, { "expr": "g_after_b_free", "equals": 3968 } ] } }, "task01.alloc-c": { "event_id": "E04", "stop": { "symbol": "heap4_alloc_c_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_c != 0", "equals": 1 }, { "expr": "g_c_aligned", "equals": 1 }, { "expr": "g_c_consumed", "equals": 32 }, { "expr": "g_allocations_aligned", "equals": 1 }, { "expr": "g_after_allocations", "equals": 3936 } ] } }, "task01.free-a": { "event_id": "E05", "stop": { "symbol": "heap4_free_a_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_after_free_a_stats.xNumberOfFreeBlocks", "equals": 2 }, { "expr": "g_after_free_a == g_after_allocations + g_a_consumed", "equals": 1 }, { "expr": "g_after_free_a_stats.xAvailableHeapSpaceInBytes > g_after_free_a_stats.xSizeOfLargestFreeBlockInBytes", "equals": 1 }, { "expr": "g_after_free_a", "equals": 3984 } ] } }, "task01.fragmented": { "event_id": "E06", "stop": { "symbol": "heap4_fragmented_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_fragmented_stats.xNumberOfFreeBlocks", "equals": 2 }, { "expr": "g_fragmented_free == g_after_free_a + g_c_consumed", "equals": 1 }, { "expr": "g_fragmented_stats.xAvailableHeapSpaceInBytes > g_fragmented_stats.xSizeOfLargestFreeBlockInBytes", "equals": 1 }, { "expr": "g_fragmented_free", "equals": 4016 } ] } }, "task01.coalesced": { "event_id": "E07", "stop": { "symbol": "heap4_coalesced_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_final_stats.xNumberOfFreeBlocks", "equals": 1 }, { "expr": "g_final_free == g_initial_free", "equals": 1 }, { "expr": "g_final_stats.xSizeOfLargestFreeBlockInBytes == g_initial_free", "equals": 1 }, { "expr": "g_minimum_ever_free <= g_after_allocations", "equals": 1 }, { "expr": "g_final_free", "equals": 4080 } ] } }, "task01.verified": { "event_id": "E08", "stop": { "symbol": "task03_debug_checkpoint", "offset": 0 }, "verify": { "expressions": [ { "expr": "g_oom_is_null", "equals": 1 }, { "expr": "g_malloc_failed_hooks", "equals": 1 }, { "expr": "g_assert_failures", "equals": 0 }, { "expr": "g_task03_pass", "equals": 1 }, { "expr": "g_rv32_layout_expected", "equals": 1 } ] } } }, "sections": [ { "id": "A1", "label": "CONTEXT", "title": "Od liniowego kursora do odzyskiwalnej areny", "order": 10, "orientation": "portrait", "description": "Aplikacja korzysta z publicznego API, heap_4 zarządza jedną areną, a Hazard3 pokazuje jej rzeczywisty stan.", "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": [ { "stem": "a1-context", "title": "A1 · granice odpowiedzialności heap_4", "caption": "A1 CONTEXT — od żądania aplikacji do rzeczywistych bajtów ucHeap.", "label": "fig:a1-context", "alt": "A1 · granice odpowiedzialności heap_4", "phases": [ { "id": "boundary", "label": "APPLICATION / API / ALLOCATOR / TARGET", "steps": [ { "id": "application", "number": 1, "label": "aplikacja żąda i zwalnia bajty", "mode": "CODE", "strategy_ref": "code.boundary", "svg_label": "01", "code_ref": "src/tasks/task03_freertos_heap4.c:127", "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.boundary", "svg_label": "02", "code_ref": "src/tasks/task03_freertos_heap4.c:127", "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": "UPSTREAM.md:1", "description": "Prywatny mechanizm wybiera, dzieli i scala wolne bloki; strategia otwiera prawdziwy heap_4.c.", "evidence": "prvHeapInit i prvInsertBlockIntoFreeList" }, { "id": "arena", "number": 4, "label": "ucHeap jest jedną ograniczoną areną", "mode": "CODE", "strategy_ref": "code.boundary", "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" } ] } ] } ] }, { "id": "A2", "label": "STRUCTURE", "title": "Nagłówek, payload i lista wolnych bloków", "order": 20, "orientation": "portrait", "description": "Każdy blok ma nagłówek, a tylko wolne bloki wykorzystują wskaźnik next do budowy listy uporządkowanej adresami.", "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": [ { "stem": "a2-structure", "title": "A2 · struktura bloku i listy", "caption": "A2 STRUCTURE — metadane wewnątrz areny i adresowo uporządkowana lista.", "label": "fig:a2-structure", "alt": "A2 · struktura bloku i listy", "phases": [ { "id": "blocks", "label": "HEADER / PAYLOAD / FREE LIST", "steps": [ { "id": "header", "number": 1, "label": "nagłówek RV32 przechowuje next i size", "mode": "CODE", "strategy_ref": "code.heap4", "svg_label": "01", "code_ref": "UPSTREAM.md:1", "description": "Prawdziwy BlockLink_t zawiera wskaźnik i size_t, po 4 bajty na RV32; port wyrównuje nagłówek do 16 bajtów.", "evidence": "sizeof(BlockLink_t)=8; portBYTE_ALIGNMENT=16; xHeapStructSize=16" }, { "id": "alignment", "number": 2, "label": "A(24) obejmuje nagłówek i wyrównanie", "mode": "RUN", "event_id": "E02", "strategy_ref": "run.timeline", "svg_label": "02", "code_ref": "src/tasks/task03_freertos_heap4.c:137", "snapshot_ref": "task01.alloc-a", "description": "Różnica licznika wolnych bajtów mierzy cały blok, nie sam payload.", "evidence": "g_a_consumed = 48; g_a_aligned = 1" }, { "id": "split", "number": 3, "label": "split pozostawia osobny wolny blok", "mode": "RUN", "event_id": "E02", "strategy_ref": "run.timeline", "svg_label": "03", "code_ref": "src/tasks/task03_freertos_heap4.c:137", "snapshot_ref": "task01.alloc-a", "description": "Po pierwszym first-fit pozostała część areny nadal jest jednym wolnym blokiem.", "evidence": "free = 4032; free blocks = 1" }, { "id": "address-order", "number": 4, "label": "lista jest uporządkowana rosnącymi adresami", "mode": "RUN", "event_id": "E06", "strategy_ref": "run.fragmented", "svg_label": "04", "code_ref": "src/tasks/task03_freertos_heap4.c:168", "snapshot_ref": "task01.fragmented", "description": "Pozycja w liście ujawnia bezpośredniego poprzednika i następnika.", "evidence": "g_a < g_b < g_c; dwa wolne obszary" }, { "id": "left-join", "number": 5, "label": "heap_4 scala najpierw poprzedni blok", "mode": "RUN", "event_id": "E06", "strategy_ref": "run.coalescing", "svg_label": "05", "code_ref": "src/tasks/task03_freertos_heap4.c:173", "snapshot_ref": "task01.fragmented", "description": "Replay E06, tymczasowy breakpoint i step w prywatnej funkcji pokazują pierwszy test ciągłości.", "evidence": "previous + previous.size == inserted" }, { "id": "right-join", "number": 6, "label": "następnie scala prawy blok", "mode": "RUN", "event_id": "E06", "strategy_ref": "run.coalescing", "svg_label": "06", "code_ref": "src/tasks/task03_freertos_heap4.c:173", "snapshot_ref": "task01.fragmented", "description": "Drugi test wykorzystuje już powiększony lewy blok i domyka scalenie areny.", "evidence": "merged + merged.size == current" } ] } ] } ] }, { "id": "A5", "label": "FLOW", "title": "First-fit, fragmentacja i pełne scalenie", "order": 50, "orientation": "landscape", "description": "Jeden przebieg A/B/C przechodzi od pustej areny przez trzy przydziały i dwa wolne obszary do ponownie scalonego bloku.", "content_tex": "Uczeń przewiduje układ bloków, a następnie odtwarza osiem deterministycznych stanów tego samego przebiegu Hazard3.", "assets": [ { "stem": "a5-flow", "title": "A5 · pełny przebieg heap_4", "caption": "A5 FLOW — inicjalizacja, A/B/C, fragmentacja, dwustronne scalenie i OOM.", "label": "fig:a5-flow", "alt": "A5 · pełny przebieg heap_4", "phases": [ { "id": "replay", "label": "ALLOCATE / FRAGMENT / COALESCE", "steps": [ { "id": "reset", "number": 1, "label": "reset i inicjalizacja areny", "mode": "RUN", "event_id": "E01", "strategy_ref": "run.timeline", "svg_label": "01", "code_ref": "src/tasks/task03_freertos_heap4.c:127", "snapshot_ref": "task01.reset", "description": "Próbne żądanie inicjalizuje sentinele; po jego zwolnieniu arena ma jeden blok.", "evidence": "free = largest = 4080; blocks = 1" }, { "id": "alloc-a", "number": 2, "label": "first-fit przydziela A", "mode": "RUN", "event_id": "E02", "strategy_ref": "run.timeline", "svg_label": "02", "code_ref": "src/tasks/task03_freertos_heap4.c:137", "snapshot_ref": "task01.alloc-a", "description": "Pierwszy wystarczający blok zostaje rozdzielony.", "evidence": "A=24; consumed=48; free=4032" }, { "id": "alloc-b", "number": 3, "label": "kolejny split tworzy B", "mode": "RUN", "event_id": "E03", "strategy_ref": "run.timeline", "svg_label": "03", "code_ref": "src/tasks/task03_freertos_heap4.c:145", "snapshot_ref": "task01.alloc-b", "description": "B zajmuje następny fragment tej samej reszty.", "evidence": "B=40; consumed=64; free=3968" }, { "id": "alloc-c", "number": 4, "label": "trzeci split tworzy C", "mode": "RUN", "event_id": "E04", "strategy_ref": "run.timeline", "svg_label": "04", "code_ref": "src/tasks/task03_freertos_heap4.c:153", "snapshot_ref": "task01.alloc-c", "description": "A, B i C są wyrównane, a za nimi pozostaje jedna wolna reszta.", "evidence": "C=16; consumed=32; free=3936" }, { "id": "free-a", "number": 5, "label": "zwolnij A, pozostaw B i C", "mode": "RUN", "event_id": "E05", "strategy_ref": "run.timeline", "svg_label": "05", "code_ref": "src/tasks/task03_freertos_heap4.c:163", "snapshot_ref": "task01.free-a", "description": "Wolny A i końcowa reszta są rozdzielone przez B i C.", "evidence": "blocks=2; free=3984" }, { "id": "fragmented", "number": 6, "label": "zwolnij C, pozostaw B", "mode": "RUN", "event_id": "E06", "strategy_ref": "run.fragmented", "svg_label": "06", "code_ref": "src/tasks/task03_freertos_heap4.c:168", "snapshot_ref": "task01.fragmented", "description": "C scala się z prawą resztą, ale B nadal rozdziela dwa wolne obszary.", "evidence": "blocks=2; free=4016; free>largest" }, { "id": "coalesced", "number": 7, "label": "zwolnij B i scal oba sąsiednie bloki", "mode": "RUN", "event_id": "E07", "strategy_ref": "run.coalesced", "svg_label": "07", "code_ref": "src/tasks/task03_freertos_heap4.c:173", "snapshot_ref": "task01.coalesced", "description": "Scalenie lewego, a potem prawego sąsiada przywraca jeden blok.", "evidence": "free=largest=initial=4080; blocks=1" }, { "id": "oom", "number": 8, "label": "zbyt duże żądanie kończy się NULL", "mode": "RUN", "event_id": "E08", "strategy_ref": "run.verified", "svg_label": "08", "code_ref": "src/tasks/task03_freertos_heap4.c:179", "snapshot_ref": "task01.verified", "description": "Kontrolowany OOM nie narusza listy i wywołuje hook dokładnie raz.", "evidence": "NULL; hooks=1; asserts=0; PASS=1" } ] } ], "diagram_kind": "sequence" } ] }, { "id": "A6", "label": "STATE", "title": "Stan bloku a stan statystyk", "order": 60, "orientation": "portrait", "description": "Bieżąca liczba wolnych bajtów może rosnąć po free, ale minimum-ever pozostaje historycznym minimum.", "content_tex": "Fragmentacja to nie tylko suma wolnych bajtów. O możliwości przydziału decyduje także największy pojedynczy blok.", "assets": [ { "stem": "a6-state", "title": "A6 · stany bloków i liczników", "caption": "A6 STATE — wolny, przydzielony, rozdzielony, scalony oraz minimum-ever.", "label": "fig:a6-state", "alt": "A6 · stany bloków i liczników", "phases": [ { "id": "state", "label": "BLOCK STATE / HEAP STATS", "steps": [ { "id": "free", "number": 1, "label": "wolny blok należy do listy", "mode": "RUN", "event_id": "E01", "strategy_ref": "run.timeline", "svg_label": "01", "code_ref": "src/tasks/task03_freertos_heap4.c:127", "snapshot_ref": "task01.reset", "description": "Stan początkowy zawiera jeden największy wolny blok.", "evidence": "initial free = largest free = 4080" }, { "id": "allocated", "number": 2, "label": "przydzielony blok znika z listy wolnych", "mode": "RUN", "event_id": "E04", "strategy_ref": "run.timeline", "svg_label": "02", "code_ref": "src/tasks/task03_freertos_heap4.c:153", "snapshot_ref": "task01.alloc-c", "description": "Payload jest wyrównany, a bieżąca wolna pamięć maleje.", "evidence": "all payload pointers aligned; free=3936" }, { "id": "fragmented-state", "number": 3, "label": "dwa wolne bloki nie są jednym dużym blokiem", "mode": "RUN", "event_id": "E06", "strategy_ref": "run.fragmented", "svg_label": "03", "code_ref": "src/tasks/task03_freertos_heap4.c:168", "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": "E07", "strategy_ref": "run.coalesced", "svg_label": "04", "code_ref": "src/tasks/task03_freertos_heap4.c:173", "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": "RUN", "event_id": "E08", "strategy_ref": "run.verified", "svg_label": "05", "code_ref": "src/tasks/task03_freertos_heap4.c:179", "snapshot_ref": "task01.verified", "description": "Historyczne minimum nie zwiększa się po odzyskaniu pamięci.", "evidence": "minimum-ever <= after allocations" } ] } ] } ] }, { "id": "A7", "label": "RUNTIME", "title": "Dowód w Hazard3 i ELF", "order": 70, "orientation": "landscape", "description": "Ten sam artefakt łączy źródło, symbole ELF, osiem checkpointów, pamięć ucHeap i końcowe asercje.", "content_tex": "Snapshot jest dowodem tylko wtedy, gdy zawiera tożsamość artefaktu, pozycję programu i wartości, które rozstrzygają hipotezę.", "assets": [ { "stem": "a7-runtime", "title": "A7 · dowód runtime heap_4", "caption": "A7 RUNTIME — kod, ELF, checkpoint, pamięć i statystyki jednego przebiegu.", "label": "fig:a7-runtime", "alt": "A7 · dowód runtime heap_4", "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:127", "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": "E06", "strategy_ref": "run.fragmented", "svg_label": "03", "code_ref": "src/tasks/task03_freertos_heap4.c:168", "snapshot_ref": "task01.fragmented", "description": "Neovim, GDB i pamięć zatrzymują się na tym samym stanie.", "evidence": "blocks=2 and free>largest" }, { "id": "memory", "number": 4, "label": "ucHeap ujawnia nagłówki i payload", "mode": "RUN", "event_id": "E06", "strategy_ref": "run.fragmented", "svg_label": "04", "code_ref": "src/tasks/task03_freertos_heap4.c:11", "snapshot_ref": "task01.fragmented", "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": "E08", "strategy_ref": "run.verified", "svg_label": "05", "code_ref": "src/tasks/task03_freertos_heap4.c:179", "snapshot_ref": "task01.verified", "description": "Jedna ramka łączy stats, OOM, hook i PASS.", "evidence": "final=initial, blocks=1, pass=1" } ] } ] } ] } ], "learning": { "model_label": "Model bloków heap_4", "model": "Uczeń wyjaśnia nagłówek, wyrównanie, first-fit, split, adresową listę wolnych bloków i dwustronne coalescing.", "replay_label": "Fragmentacja i odzyskanie areny", "replay": "Uczeń odtwarza osiem checkpointów Hazard3 i odróżnia current free, largest free block oraz minimum-ever.", "criterion": "Na RV32 BlockLink_t ma 8 bajtów, lecz 16-bajtowe wyrównanie portu daje xHeapStructSize=16; A/B/C zużywają 48/64/32 bajty; po free(A,C) są dwa wolne bloki, po free(B) jeden; final free = initial free; minimum-ever nie rośnie; OOM zwraca NULL; PASS=1.", "requirement": "Analiza i weryfikacja mechaniki dynamicznej alokacji heap_4 w FreeRTOS C na RV32I/Hazard3." }, "task": { "short_label": "mechanika heap_4", "title": "Predict and prove reusable heap_4 blocks", "prompt_tex": "Przewidź układ bloków po alloc A/B/C, następnie po free(A), free(C) i free(B). Odtwórz checkpointy E01--E08 i udowodnij, że arena została scalona bez utraty minimum-ever.", "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 rozmiary całych bloków.", "content_tex": "Rozróżnij surowy BlockLink\\_t (8 bajtów) od xHeapStructSize (16 bajtów), dodaj nagłówek do żądań 24, 40 i 16 bajtów, a następnie wyrównaj wynik do 16." }, { "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ąż każdy stan diagramu z deterministycznym checkpointem E01--E08.", "steps": [ { "id": "allocate", "title": "Sprawdź E01--E04.", "content_tex": "Potwierdź wartości 4080, 4032, 3968 i 3936 oraz zużycie 48/64/32 bajtów." }, { "id": "release", "title": "Sprawdź E05--E07.", "content_tex": "Porównaj liczbę wolnych bloków i udowodnij scalenie poprzedniego, a potem następnego sąsiada." }, { "id": "verify", "title": "Sprawdź E08.", "content_tex": "Udowodnij zachowanie minimum-ever, kontrolowany OOM, brak asercji i PASS=1." } ] }, { "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" ] } ] } }