1504 lines
52 KiB
JSON
1504 lines
52 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": "Mateusz 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": "RV32 header, split 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 the eight-state replay in A5."
|
|
},
|
|
{
|
|
"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.boundary": {
|
|
"id": "code.boundary",
|
|
"kind": "code",
|
|
"action": "open",
|
|
"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"
|
|
]
|
|
},
|
|
"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|prvHeapInit|prvInsertBlockIntoFreeList|pvPortMalloc|vPortFree' ${FREERTOS_KERNEL_PATH:-/opt/FreeRTOS-Kernel}/portable/MemMang/heap_4.c"
|
|
]
|
|
},
|
|
"run.timeline": {
|
|
"id": "run.timeline",
|
|
"kind": "run",
|
|
"action": "replay",
|
|
"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"
|
|
]
|
|
},
|
|
"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",
|
|
"print g_fragmented_free",
|
|
"x/96bx ucHeap"
|
|
]
|
|
},
|
|
"run.coalescing": {
|
|
"id": "run.coalescing",
|
|
"kind": "run",
|
|
"action": "replay",
|
|
"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"
|
|
]
|
|
},
|
|
"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",
|
|
"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"
|
|
]
|
|
},
|
|
"run.verified": {
|
|
"id": "run.verified",
|
|
"kind": "run",
|
|
"action": "replay",
|
|
"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"
|
|
]
|
|
},
|
|
"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",
|
|
"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"
|
|
]
|
|
}
|
|
},
|
|
"template": "templates/karta-klasyczna.json",
|
|
"title_block": {
|
|
"category": "KARTA PRACY · INFORMATYKA",
|
|
"prepared_by": "Mateusz 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 osiem checkpointów Hazard3 i odróżnia current free, largest free block oraz minimum-ever.",
|
|
"assessment_criteria": [
|
|
"FC01.KW01"
|
|
]
|
|
}
|
|
},
|
|
"assessment_criteria": {
|
|
"FC01.KW01": {
|
|
"text": "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.",
|
|
"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 osiem checkpointów 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": "14ee8fedf1eec198c6b373e2805e07825c3fd0ed",
|
|
"elf": "build/task03_freertos_heap4/prog.elf",
|
|
"hazard3_elf_sha256": "91af9f1169945abe4b16037ded2b8047253bf33f4ea6823f46a427ec17d3d4fa",
|
|
"hazard3_image": "build/task03_freertos_heap4/prog.bin",
|
|
"hazard3_image_sha256": "0d943814bb518fefda254f1cdf3771e4104ba1ad9e5f31bb04e672b5015ce31a"
|
|
},
|
|
"targets": {
|
|
"hazard3-sim": {
|
|
"adapter": "hazard3-reset-load-replay.v1",
|
|
"baseline": "restart-backend",
|
|
"clean_ram": true
|
|
}
|
|
},
|
|
"items": {
|
|
"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": [
|
|
{
|
|
"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.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"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"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,
|
|
"left-join": 1,
|
|
"right-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 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"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"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ń przewiduje układ bloków, a następnie odtwarza osiem deterministycznych stanów tego samego przebiegu Hazard3.",
|
|
"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-b": 1,
|
|
"alloc-c": 1,
|
|
"free-a": 1,
|
|
"fragmented": 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": "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"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"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": "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"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"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, osiem checkpointów, 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: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"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"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), free(C) i free(B). Odtwórz checkpointy E01--E08 i udowodnij, że arena została scalona bez utraty 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.",
|
|
"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"
|
|
]
|
|
}
|
|
],
|
|
"educational_requirement_refs": [
|
|
"FC01.WE01"
|
|
],
|
|
"learning_effect_refs": [
|
|
"FC01.EN01",
|
|
"FC01.EK01"
|
|
],
|
|
"assessment_criterion_ref": "FC01.KW01"
|
|
}
|
|
},
|
|
"tasks_order": [
|
|
"task01"
|
|
]
|
|
}
|