feat: rebuild heap4 card around upstream FreeRTOS allocator

This commit is contained in:
user
2026-07-14 23:24:56 +02:00
parent 032a06fdef
commit 2b17bb4ff9
41 changed files with 1462 additions and 2138 deletions
+4 -13
View File
@@ -1,18 +1,9 @@
*.aux
*.log
*.out
*.fls
*.fdb_latexmk
*.synctex.gz
*.pdf
build-meta.tex
.rv/
.nvim/
.vim/
.build/
.gdb_history
.stem/
host-build/
build/*/prog.bin
build/*/prog.elf
build/*/prog.lst
build/*/prog.map
build/*/*.o
cmake-build-*/
compile_commands.json
+114
View File
@@ -0,0 +1,114 @@
cmake_minimum_required(VERSION 3.13)
if(NOT PICO_SDK_PATH)
if(DEFINED ENV{PICO_SDK_PATH})
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
else()
message(FATAL_ERROR "Set PICO_SDK_PATH to a Raspberry Pi Pico SDK checkout")
endif()
endif()
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(freertos_heap4_rp2350 C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
# The card and allocator are C-only. Debian's compact RISC-V toolchain does
# not ship libstdc++ headers, so keep the SDK's optional new/delete source out
# while preserving the interface targets expected by pico_stdlib.
add_library(pico_cxx_options_headers INTERFACE)
target_include_directories(pico_cxx_options_headers SYSTEM INTERFACE
${PICO_SDK_PATH}/src/rp2_common/pico_cxx_options/include)
add_library(pico_cxx_options INTERFACE)
target_link_libraries(pico_cxx_options INTERFACE pico_cxx_options_headers)
pico_sdk_init()
if(NOT PICO_PLATFORM STREQUAL "rp2350-riscv")
message(FATAL_ERROR "This card targets the RP2350 RISC-V cores")
endif()
if(NOT HEAP4_UPSTREAM_ROOT)
if(DEFINED ENV{HEAP4_UPSTREAM_ROOT})
set(HEAP4_UPSTREAM_ROOT $ENV{HEAP4_UPSTREAM_ROOT})
else()
set(HEAP4_UPSTREAM_ROOT /opt/FreeRTOS-Kernel)
endif()
endif()
if(NOT EXISTS ${HEAP4_UPSTREAM_ROOT}/portable/MemMang/heap_4.c)
message(FATAL_ERROR "Pinned FreeRTOS heap_4.c not found at ${HEAP4_UPSTREAM_ROOT}")
endif()
if(NOT FREERTOS_RP2350_PATH)
if(DEFINED ENV{FREERTOS_RP2350_PATH})
set(FREERTOS_RP2350_PATH $ENV{FREERTOS_RP2350_PATH})
else()
set(FREERTOS_RP2350_PATH /opt/FreeRTOS-RP2350)
endif()
endif()
if(NOT EXISTS ${FREERTOS_RP2350_PATH}/portable/ThirdParty/GCC/RP2350_RISC-V/portASM.S)
message(FATAL_ERROR "Compatible RP2350 FreeRTOS core/port not found at ${FREERTOS_RP2350_PATH}")
endif()
set(FREERTOS_CONFIG_FILE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include)
# Raspberry Pi's port CMake uses FREERTOS_KERNEL_PATH for its matching core.
# Keep the stable allocator root separate and add its heap_4.c explicitly.
set(FREERTOS_KERNEL_PATH ${FREERTOS_RP2350_PATH})
add_subdirectory(
${FREERTOS_RP2350_PATH}/portable/ThirdParty/GCC/RP2350_RISC-V
${CMAKE_BINARY_DIR}/freertos-kernel)
set(HEAP4_PROFILE "observe" CACHE STRING "observe, debug or release")
set_property(CACHE HEAP4_PROFILE PROPERTY STRINGS observe debug release)
set(HEAP4_BINARY_TYPE "no_flash" CACHE STRING "Pico SDK binary type")
set_property(CACHE HEAP4_BINARY_TYPE PROPERTY STRINGS no_flash default)
if(HEAP4_PROFILE STREQUAL "observe")
set(HEAP4_COMPILE_OPTIONS
-O0 -g3 -fno-omit-frame-pointer -fno-inline
-fno-optimize-sibling-calls)
elseif(HEAP4_PROFILE STREQUAL "debug")
set(HEAP4_COMPILE_OPTIONS
-Og -g3 -fno-omit-frame-pointer -fno-optimize-sibling-calls)
elseif(HEAP4_PROFILE STREQUAL "release")
set(HEAP4_COMPILE_OPTIONS -O2)
else()
message(FATAL_ERROR "HEAP4_PROFILE must be observe, debug or release")
endif()
set(MODEL_TASKS
task01_first_fit_split
task02_address_order_coalesce)
foreach(task IN LISTS MODEL_TASKS)
set(task_source src/tasks/${task}.c)
add_executable(${task} ${task_source})
target_include_directories(${task} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/tasks)
set_property(SOURCE ${task_source} APPEND PROPERTY COMPILE_OPTIONS
${HEAP4_COMPILE_OPTIONS} -Wall -Wextra -Wpedantic)
target_link_libraries(${task} pico_stdlib)
pico_enable_stdio_usb(${task} 0)
pico_enable_stdio_uart(${task} 0)
if(NOT HEAP4_BINARY_TYPE STREQUAL "default")
pico_set_binary_type(${task} ${HEAP4_BINARY_TYPE})
endif()
pico_add_extra_outputs(${task})
endforeach()
set(heap4_task task03_freertos_heap4)
set(heap4_source src/tasks/${heap4_task}.c)
add_executable(${heap4_task} ${heap4_source})
target_sources(${heap4_task} PRIVATE
${HEAP4_UPSTREAM_ROOT}/portable/MemMang/heap_4.c)
target_include_directories(${heap4_task} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/include
${CMAKE_CURRENT_LIST_DIR}/src/tasks)
set_property(SOURCE ${heap4_source} APPEND PROPERTY COMPILE_OPTIONS
${HEAP4_COMPILE_OPTIONS} -Wall -Wextra -Wpedantic)
target_link_libraries(${heap4_task} pico_stdlib FreeRTOS-Kernel)
pico_enable_stdio_usb(${heap4_task} 0)
pico_enable_stdio_uart(${heap4_task} 0)
if(NOT HEAP4_BINARY_TYPE STREQUAL "default")
pico_set_binary_type(${heap4_task} ${HEAP4_BINARY_TYPE})
endif()
pico_add_extra_outputs(${heap4_task})
+70 -52
View File
@@ -6,11 +6,18 @@ OBJDUMP := $(RISCV_PREFIX)objdump
SIZE := $(RISCV_PREFIX)size
HOST_CC ?= cc
DEBUG ?= 1
ifeq ($(DEBUG),1)
PROFILE ?= observe
ifeq ($(PROFILE),observe)
OPTFLAGS := -O0 -fno-omit-frame-pointer -fno-inline -fno-optimize-sibling-calls
DBGFLAGS := -g3 -ggdb
else
else ifeq ($(PROFILE),debug)
OPTFLAGS := -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls
DBGFLAGS := -g3 -ggdb
else ifeq ($(PROFILE),release)
OPTFLAGS := -O2
DBGFLAGS :=
else
$(error PROFILE must be observe, debug or release)
endif
RV_ENV_ROOT ?= /opt/rv-env
@@ -21,36 +28,35 @@ LDSCRIPT ?= $(H3_COMMON)/link_hazard3.ld
LAB_RUNTIME_DIR ?= $(RV_ENV_ROOT)/vendor/lab-runtime
MEMOPS ?= $(LAB_RUNTIME_DIR)/memops.c
LOCAL_FREERTOS := $(abspath ../lab-rv32i-freertos-task-stack-vector/vendor/FreeRTOS-Kernel)
FREERTOS_KERNEL_PATH ?= $(if $(wildcard /opt/FreeRTOS-Kernel/include/FreeRTOS.h),/opt/FreeRTOS-Kernel,$(LOCAL_FREERTOS))
FREERTOS_PORT := $(FREERTOS_KERNEL_PATH)/portable/GCC/RISC-V
HEAP4_SOURCE := $(FREERTOS_KERNEL_PATH)/portable/MemMang/heap_4.c
BUILD_ROOT ?= build
HOST_BUILD_ROOT ?= host-build
TASKS := \
task01_heap_map \
task02_free_list \
task03_heap_init \
task04_alignment \
task05_malloc_first_fit \
task06_split_block \
task07_free_insert \
task08_coalescing \
task09_config_macros \
task10_heap4_demo
task01_first_fit_split \
task02_address_order_coalesce \
task03_freertos_heap4
ARCH ?= rv32i_zicsr_zifencei
ABI ?= ilp32
COMMON_FLAGS := -std=c99 -march=$(ARCH) -mabi=$(ABI) -nostdlib -nostartfiles -ffreestanding -fno-builtin -fno-stack-protector $(DBGFLAGS) -Wall -Wextra -Isrc/tasks
LINK_FLAGS := -Wl,--no-relax -Wl,-T,$(LDSCRIPT)
COMMON_FLAGS := -std=c11 -march=$(ARCH) -mabi=$(ABI) -nostdlib -nostartfiles \
-ffreestanding -fno-builtin -fno-stack-protector -ffunction-sections \
-fdata-sections $(OPTFLAGS) $(DBGFLAGS) -Wall -Wextra -Wpedantic
MODEL_CPPFLAGS := -Isrc/tasks
KERNEL_CPPFLAGS := -Iinclude/freestanding -Iinclude -Isrc/tasks \
-I$(FREERTOS_KERNEL_PATH)/include -I$(FREERTOS_PORT)
LINK_FLAGS := -Wl,--no-relax -Wl,--gc-sections -T $(LDSCRIPT)
LIBS := -lgcc
EMIT_FLAGS := -std=c99 -march=$(ARCH) -mabi=$(ABI) -O -ffreestanding -fno-builtin -fno-stack-protector -nostdlib -nostartfiles -Wall -Wextra -Isrc/tasks
CFLAGS_STEP := $(COMMON_FLAGS) -O
HOST_CFLAGS := -std=c99 -O0 -g -Wall -Wextra -DHOST_PRINTF=1 -Isrc/tasks
HOST_FLAGS := -std=c11 $(OPTFLAGS) $(DBGFLAGS) -Wall -Wextra -Wpedantic \
-DHOST_PRINTF=1 -Isrc/tasks
HOST_HEAP4_FLAGS := $(HOST_FLAGS) -DHEAP4_HOST_ADAPTER=1 -Iinclude/host-shim
.PHONY: all tasks host \
task1 task2 task3 task4 task5 task6 task7 task8 task9 task10 \
t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 \
host-task1 host-task2 host-task3 host-task4 host-task5 host-task6 host-task7 host-task8 host-task9 host-task10 \
ht1 ht2 ht3 ht4 ht5 ht6 ht7 ht8 ht9 ht10 \
clean check-support $(TASKS)
.PHONY: all tasks host task1 task2 task3 t1 t2 t3 \
host-task1 host-task2 host-task3 ht1 ht2 ht3 clean check-support check-kernel \
$(TASKS)
all: tasks
tasks: $(TASKS)
@@ -59,54 +65,66 @@ host: $(addprefix $(HOST_BUILD_ROOT)/,$(addsuffix /prog,$(TASKS)))
task1 t1: $(word 1,$(TASKS))
task2 t2: $(word 2,$(TASKS))
task3 t3: $(word 3,$(TASKS))
task4 t4: $(word 4,$(TASKS))
task5 t5: $(word 5,$(TASKS))
task6 t6: $(word 6,$(TASKS))
task7 t7: $(word 7,$(TASKS))
task8 t8: $(word 8,$(TASKS))
task9 t9: $(word 9,$(TASKS))
task10 t10: $(word 10,$(TASKS))
host-task1 ht1: $(HOST_BUILD_ROOT)/$(word 1,$(TASKS))/prog
host-task2 ht2: $(HOST_BUILD_ROOT)/$(word 2,$(TASKS))/prog
host-task3 ht3: $(HOST_BUILD_ROOT)/$(word 3,$(TASKS))/prog
host-task4 ht4: $(HOST_BUILD_ROOT)/$(word 4,$(TASKS))/prog
host-task5 ht5: $(HOST_BUILD_ROOT)/$(word 5,$(TASKS))/prog
host-task6 ht6: $(HOST_BUILD_ROOT)/$(word 6,$(TASKS))/prog
host-task7 ht7: $(HOST_BUILD_ROOT)/$(word 7,$(TASKS))/prog
host-task8 ht8: $(HOST_BUILD_ROOT)/$(word 8,$(TASKS))/prog
host-task9 ht9: $(HOST_BUILD_ROOT)/$(word 9,$(TASKS))/prog
host-task10 ht10: $(HOST_BUILD_ROOT)/$(word 10,$(TASKS))/prog
check-support:
@test -f "$(H3_INIT)" || { echo "Missing $(H3_INIT). Run inside rv-env or set RV_ENV_ROOT=/path/to/rv32i-hazard3-env" >&2; exit 1; }
@test -f "$(H3_INIT)" || { echo "Missing $(H3_INIT). Run inside hazard3-sim." >&2; exit 1; }
@test -f "$(LDSCRIPT)" || { echo "Missing $(LDSCRIPT)." >&2; exit 1; }
@test -f "$(MEMOPS)" || { echo "Missing $(MEMOPS)." >&2; exit 1; }
define task_rules
$(1): $(BUILD_ROOT)/$(1)/prog.bin $(BUILD_ROOT)/$(1)/prog.elf $(BUILD_ROOT)/$(1)/prog.lst $(BUILD_ROOT)/$(1)/$(1).s
check-kernel:
@test -f "$(HEAP4_SOURCE)" || { echo "Missing pinned FreeRTOS heap_4.c at $(HEAP4_SOURCE)." >&2; exit 1; }
@grep -F 'FreeRTOS Kernel V11.3.0' "$(FREERTOS_KERNEL_PATH)/include/FreeRTOS.h" >/dev/null || { echo "Expected FreeRTOS Kernel V11.3.0." >&2; exit 1; }
$(BUILD_ROOT)/$(1)/prog.elf: crt0.S src/tasks/$(1).c src/tasks/heap4_common.h $(H3_INIT) $(LDSCRIPT) $(MEMOPS) | check-support
define model_task_rules
$(1): $(BUILD_ROOT)/$(1)/prog.bin $(BUILD_ROOT)/$(1)/prog.elf $(BUILD_ROOT)/$(1)/prog.lst
$(BUILD_ROOT)/$(1)/prog.elf: crt0.S src/tasks/$(1).c src/tasks/heap4_model.h $(H3_INIT) $(LDSCRIPT) $(MEMOPS) | check-support
mkdir -p $$(@D)
$(CC) $(CFLAGS_STEP) $(LINK_FLAGS) -Wl,-Map,$(BUILD_ROOT)/$(1)/prog.map -o $$@ $(H3_INIT) crt0.S $(MEMOPS) src/tasks/$(1).c $(LIBS)
$(CC) $(COMMON_FLAGS) $(MODEL_CPPFLAGS) $(LINK_FLAGS) -Wl,-Map,$(BUILD_ROOT)/$(1)/prog.map -o $$@ $(H3_INIT) crt0.S $(MEMOPS) src/tasks/$(1).c $(LIBS)
$(SIZE) -A -x $$@
$(BUILD_ROOT)/$(1)/prog.bin: $(BUILD_ROOT)/$(1)/prog.elf
$(OBJCOPY) -O binary $$< $$@
$(BUILD_ROOT)/$(1)/prog.lst: $(BUILD_ROOT)/$(1)/prog.elf
$(OBJDUMP) -d -M no-aliases,numeric $$< > $$@
$(OBJDUMP) -d -S -M no-aliases,numeric $$< > $$@
$(BUILD_ROOT)/$(1)/$(1).s: src/tasks/$(1).c src/tasks/heap4_common.h | check-support
$(HOST_BUILD_ROOT)/$(1)/prog: src/tasks/$(1).c src/tasks/heap4_model.h
mkdir -p $$(@D)
$(CC) $(EMIT_FLAGS) -S -o $$@ $$<
$(HOST_BUILD_ROOT)/$(1)/prog: src/tasks/$(1).c src/tasks/heap4_common.h
mkdir -p $$(@D)
$(HOST_CC) $(HOST_CFLAGS) -o $$@ $$<
$(HOST_CC) $(HOST_FLAGS) -o $$@ $$<
endef
$(foreach task,$(TASKS),$(eval $(call task_rules,$(task))))
$(eval $(call model_task_rules,$(word 1,$(TASKS))))
$(eval $(call model_task_rules,$(word 2,$(TASKS))))
TASK03 := $(word 3,$(TASKS))
TASK03_KERNEL_SOURCES := \
$(FREERTOS_KERNEL_PATH)/tasks.c \
$(FREERTOS_KERNEL_PATH)/list.c \
$(HEAP4_SOURCE) \
$(FREERTOS_PORT)/port.c \
$(FREERTOS_PORT)/portASM.S
$(TASK03): $(BUILD_ROOT)/$(TASK03)/prog.bin $(BUILD_ROOT)/$(TASK03)/prog.elf $(BUILD_ROOT)/$(TASK03)/prog.lst
$(BUILD_ROOT)/$(TASK03)/prog.elf: crt0.S src/tasks/$(TASK03).c include/FreeRTOSConfig.h $(TASK03_KERNEL_SOURCES) $(H3_INIT) $(LDSCRIPT) $(MEMOPS) | check-support check-kernel
mkdir -p $(@D)
$(CC) $(COMMON_FLAGS) $(KERNEL_CPPFLAGS) $(LINK_FLAGS) -Wl,-Map,$(BUILD_ROOT)/$(TASK03)/prog.map -o $@ $(H3_INIT) crt0.S $(MEMOPS) $(TASK03_KERNEL_SOURCES) src/tasks/$(TASK03).c $(LIBS)
$(SIZE) -A -x $@
$(BUILD_ROOT)/$(TASK03)/prog.bin: $(BUILD_ROOT)/$(TASK03)/prog.elf
$(OBJCOPY) -O binary $< $@
$(BUILD_ROOT)/$(TASK03)/prog.lst: $(BUILD_ROOT)/$(TASK03)/prog.elf
$(OBJDUMP) -d -S -M no-aliases,numeric $< > $@
$(HOST_BUILD_ROOT)/$(TASK03)/prog: src/tasks/$(TASK03).c include/host-shim/FreeRTOS.h include/host-shim/task.h $(HEAP4_SOURCE) | check-kernel
mkdir -p $(@D)
$(HOST_CC) $(HOST_HEAP4_FLAGS) -o $@ src/tasks/$(TASK03).c $(HEAP4_SOURCE)
clean:
rm -rf $(BUILD_ROOT) $(HOST_BUILD_ROOT)
+57 -61
View File
@@ -1,73 +1,69 @@
# rv32i-freertos / heap4
# Karta pracy: FreeRTOS `heap_4`
Karta pracy `01/14` z serii `rv32i-freertos`; jest opcjonalnym fundamentem L1.
Trzy krótkie zadania po kartach `pointers` i `structures`:
Temat: mechanika alokatora `heap_4.c` z FreeRTOS-a na małych przykładach
uruchamianych w środowisku RV32I.
1. first-fit + split na arenie 256 B;
2. lista adresowa + scalanie `A | B | C`;
3. niezmodyfikowany FreeRTOS-Kernel V11.3.0 `heap_4.c`.
Ta karta jest pomostem po kartach C: używa wskaźników, struktur, listy
jednokierunkowej, `static`, `typedef`, makr i arytmetyki adresów, ale nie
wymaga jeszcze schedulera, tasków ani przerwań.
Task 3 linkuje upstream `portable/MemMang/heap_4.c` z commita
`9b777ae5c5b8e9e456065a00294d1e5f5f9facf5`.
Kod w `src/tasks` jest dydaktycznym modelem zachowania `heap_4`, a nie kopią
pliku upstream FreeRTOS.
- AMD64: rzeczywisty algorytm + jednowątkowy adapter sekcji krytycznej.
- Hazard3: `tasks.c`, `list.c`, `heap_4.c` i oficjalny port RISC-V.
- RP2350: zgodny core+port z forka Raspberry Pi (`4f7299d6ea74`) oraz
podstawiony stabilny allocator V11.3.0.
## Taski
- `task01_heap_map.c` - mapa pojęć: sterta, blok, nagłówek bloku.
- `task02_free_list.c` - lista wolnych bloków.
- `task03_heap_init.c` - inicjalizacja sterty i wartownik końca listy.
- `task04_alignment.c` - wyrównanie rozmiarów i adresów.
- `task05_malloc_first_fit.c` - pierwszy pasujący wolny blok.
- `task06_split_block.c` - podział większego bloku.
- `task07_free_insert.c` - zwracanie bloku na listę wolnych.
- `task08_coalescing.c` - scalanie sąsiednich wolnych bloków.
- `task09_config_macros.c` - makra konfiguracyjne i warunkowy `printf`.
- `task10_heap4_demo.c` - mini-demonstracja `malloc/free` bez pełnego RTOS-a.
Hostowe `printf` jest dostępne tylko przez `-DHOST_PRINTF=1`; RV32I zapisuje
wyniki w globalnych zmiennych `volatile`.
## Gałęzie zadań
Gałąź `deploy` jest pełną publiczną wersją karty. Każde zadanie ma osobną
gałąź startową `tasks/*`, na przykład `tasks/05-malloc-first-fit`. Taka gałąź
nie jest wycinkiem repozytorium: zawiera dokumentację, treść karty, `Makefile`,
kod pomocniczy i pliki danego zadania.
Uczeń nie commituje bezpośrednio do `tasks/*`. W repozytorium klasy tworzy
gałąź pochodną, na przykład `students/u01/tasks/05-malloc-first-fit`.
## Powiązanie z FreeRTOS Book v1.1.0
To jest opcjonalna karta `K01` z
[mapy serii](../README.md). Jej źródłem pojęciowym jest rozdział 3 książki
*Mastering the FreeRTOS Real Time Kernel*, przede wszystkim sekcja 3.2.4 o
`heap_4` oraz rysunki 3.13.4. Rozdział o heapie nie ma numerowanego programu
z zestawu `Example001``Example025`, dlatego karta rozkłada mechanikę
alokatora na własne, krótsze eksperymenty:
- Taski 0103 odtwarzają mapę bloku, wolną listę i inicjalizację heapu;
- Taski 0408 izolują wyrównanie, first-fit, podział, wstawianie i scalanie;
- Task 09 łączy konfigurację z sekcjami 3.1.33.1.5;
- Task 10 składa te elementy w jeden model zachowania `heap_4`.
Karta nie jest kopią upstreamowego `heap_4.c` ani listingów książki. Jest
modelem dydaktycznym, po którym karta `K02` przechodzi na prawdziwy plik
`portable/MemMang/heap_4.c` z FreeRTOS V11.3.0.
## Budowanie
## Jedno wejście: `stemctl`
```bash
RV_ENV_ROOT=~/dev/workspace/rv/tools/rv32i-hazard3-student-env make tasks
make host
stemctl series cards fetch freertos heap4
stemctl test native-amd64 freertos heap4 1
stemctl test hazard3-sim freertos heap4 2
stemctl debug hazard3-sim freertos heap4 3
stemctl test rp2350 freertos heap4 3
stemctl deploy rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
stemctl debug rp2350 freertos heap4 3 --device /dev/bus/usb/BBB/DDD
```
## Praca przez rvctl
- `debug rp2350`: roboczy ELF `no_flash` ładowany do SRAM.
- `deploy rp2350`: trwały obraz w SPI flash, odczytany przez breakpointy.
## Kontrakt zadań
| Task | Sprawdza | Wynik kluczowy |
|---|---|---|
| 1 | alignment, header, split | `wanted + remainder == 256` |
| 2 | lista po adresie, coalescing | `2 bloki -> 1 blok`, `96 -> 144 B` |
| 3 | prawdziwe API FreeRTOS | free wraca do F0, minimum nie rośnie, OOM = NULL |
## Debugowanie Task 3
Automatyczny start: `heap4_fragmented_checkpoint`.
```gdb
p g_fragmented_stats
p g_final_stats
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
p xStart
p pxEnd
x/160bx ucHeap
b prvInsertBlockIntoFreeList
b pvPortMalloc
b vPortFree
```
W UI: Termdebug po lewej; źródło i listing po prawej; bash pod Neovimem.
`F5` continue, `F9` breakpoint, `F10` next, `F11` step, `Shift-F11`
finish, `F8` stepi, `:StudentStack`, `:StudentMemory`, `:StudentLst`.
## Test repozytorium
```bash
./rvctl series cards fetch inf heap4
./rvctl card use inf heap4
./rvctl tasks list
./rvctl debug rv32i 1
./tests/test_host.sh
bash -n tools/card-action.sh
./scripts/render_pdf.sh
```
Źródło i licencja: [UPSTREAM.md](UPSTREAM.md).
+19
View File
@@ -0,0 +1,19 @@
# Upstream contract
- Project: FreeRTOS-Kernel
- Version: V11.3.0
- Commit: `9b777ae5c5b8e9e456065a00294d1e5f5f9facf5`
- Source used by Task 3: `portable/MemMang/heap_4.c`
- License: MIT (as shipped in the pinned upstream checkout)
RP2350 needs a port that is newer than the V11.3.0 tag:
- Project: Raspberry Pi fork of FreeRTOS-Kernel
- Commit: `4f7299d6ea746b27a9dd19e87af568e34bd65b15`
- Files used on RP2350: matching kernel core plus
`portable/ThirdParty/GCC/RP2350_RISC-V`
- Allocator used on RP2350: still the stable V11.3.0 `heap_4.c` above,
compiled instead of the fork's development-branch allocator.
The canonical containers provide `/opt/FreeRTOS-Kernel`; the RP2350 profile
also provides `/opt/FreeRTOS-RP2350`. The card carries no allocator copy.
-53
View File
@@ -1,53 +0,0 @@
.file "task01_heap_map.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
li a5,8
sw a5,g_header_size,a4
sw a5,g_aligned_header_size,a4
li a5,1
sw a5,g_allocated_bit_set,a4
lla a5,g_total_block_bytes
li a4,24
sw a4,0(a5)
lw a5,0(a5)
addi a5,a5,-8
sw a5,g_user_bytes,a4
li a0,0
ret
.size main, .-main
.globl g_total_block_bytes
.globl g_user_bytes
.globl g_allocated_bit_set
.globl g_aligned_header_size
.globl g_header_size
.section .sbss,"aw",@nobits
.align 2
.type g_total_block_bytes, @object
.size g_total_block_bytes, 4
g_total_block_bytes:
.zero 4
.type g_user_bytes, @object
.size g_user_bytes, 4
g_user_bytes:
.zero 4
.type g_allocated_bit_set, @object
.size g_allocated_bit_set, 4
g_allocated_bit_set:
.zero 4
.type g_aligned_header_size, @object
.size g_aligned_header_size, 4
g_aligned_header_size:
.zero 4
.type g_header_size, @object
.size g_header_size, 4
g_header_size:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-73
View File
@@ -1,73 +0,0 @@
.file "task02_free_list.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
addi sp,sp,-32
addi a5,sp,16
sw a5,24(sp)
li a5,32
sw a5,28(sp)
addi a5,sp,8
sw a5,16(sp)
li a5,80
sw a5,20(sp)
sw zero,8(sp)
li a5,24
sw a5,12(sp)
li a4,0
add a5,sp,a5
.L2:
lw a3,4(a5)
add a4,a4,a3
lw a5,0(a5)
bne a5,zero,.L2
sw a4,g_free_total,a5
li a3,0
addi a5,sp,24
j .L4
.L3:
lw a5,0(a5)
beq a5,zero,.L10
.L4:
lw a4,4(a5)
bgeu a3,a4,.L3
mv a3,a4
j .L3
.L10:
sw a3,g_largest_free,a5
addi a5,sp,24
li a4,0
.L5:
addi a4,a4,1
lw a5,0(a5)
bne a5,zero,.L5
sw a4,g_node_count,a5
li a0,0
addi sp,sp,32
jr ra
.size main, .-main
.globl g_node_count
.globl g_largest_free
.globl g_free_total
.section .sbss,"aw",@nobits
.align 2
.type g_node_count, @object
.size g_node_count, 4
g_node_count:
.zero 4
.type g_largest_free, @object
.size g_largest_free, 4
g_largest_free:
.zero 4
.type g_free_total, @object
.size g_free_total, 4
g_free_total:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-74
View File
@@ -1,74 +0,0 @@
.file "task03_heap_init.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a2,.LANCHOR0
andi a4,a2,7
mv a5,a2
beq a4,zero,.L2
andi a5,a2,-8
addi a5,a5,8
.L2:
lla a3,.LANCHOR0+160
sub a3,a3,a5
addi a4,a5,-8
andi a3,a3,-8
add a4,a4,a3
sw a4,end_marker,a3
sw zero,0(a4)
sw zero,4(a4)
sw a4,0(a5)
sub a3,a4,a5
sw a3,4(a5)
lla a1,start
sw a5,0(a1)
sw zero,4(a1)
sub a5,a5,a2
sw a5,g_aligned_offset,a2
sw a3,g_initial_free_size,a5
lw a5,0(a4)
seqz a5,a5
sw a5,g_end_is_last,a4
li a0,0
ret
.size main, .-main
.globl g_end_is_last
.globl g_aligned_offset
.globl g_initial_free_size
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 168
heap_area:
.zero 168
.section .sbss,"aw",@nobits
.align 2
.type g_end_is_last, @object
.size g_end_is_last, 4
g_end_is_last:
.zero 4
.type g_aligned_offset, @object
.size g_aligned_offset, 4
g_aligned_offset:
.zero 4
.type g_initial_free_size, @object
.size g_initial_free_size, 4
g_initial_free_size:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-47
View File
@@ -1,47 +0,0 @@
.file "task04_alignment.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
addi sp,sp,-32
li a5,16
sw a5,g_request_1,a4
li a5,24
sw a5,g_request_9,a4
li a5,32
sw a5,g_request_17,a4
li a5,7
sw a5,g_address_delta,a4
li a0,0
addi sp,sp,32
jr ra
.size main, .-main
.globl g_address_delta
.globl g_request_17
.globl g_request_9
.globl g_request_1
.section .sbss,"aw",@nobits
.align 2
.type g_address_delta, @object
.size g_address_delta, 4
g_address_delta:
.zero 4
.type g_request_17, @object
.size g_request_17, 4
g_request_17:
.zero 4
.type g_request_9, @object
.size g_request_9, 4
g_request_9:
.zero 4
.type g_request_1, @object
.size g_request_1, 4
g_request_1:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,95 +0,0 @@
.file "task05_malloc_first_fit.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a4,.LANCHOR0
andi a5,a4,7
beq a5,zero,.L2
andi a4,a4,-8
addi a4,a4,8
.L2:
mv a5,a4
addi a3,a4,120
sw a3,end_marker,a2
sw zero,120(a4)
sw zero,4(a3)
sw a3,0(a4)
li a2,120
sw a2,4(a4)
lla a2,start
sw a4,0(a2)
sw zero,4(a2)
li a1,31
j .L5
.L6:
mv a5,a4
.L5:
lw a4,4(a5)
bgtu a4,a1,.L7
lw a4,0(a5)
mv a2,a5
bne a4,a3,.L6
li a5,0
j .L4
.L7:
lw a4,0(a5)
sw a4,0(a2)
sw zero,0(a5)
lw a4,4(a5)
li a3,-2147483648
or a4,a4,a3
sw a4,4(a5)
addi a5,a5,8
.L4:
snez a4,a5
sw a4,g_allocation_ok,a3
lw a5,-4(a5)
slli a5,a5,1
srli a5,a5,1
sw a5,g_allocated_size,a4
lw a5,start
lw a5,4(a5)
sw a5,g_remaining_free,a4
li a0,0
ret
.size main, .-main
.globl g_allocation_ok
.globl g_remaining_free
.globl g_allocated_size
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 136
heap_area:
.zero 136
.section .sbss,"aw",@nobits
.align 2
.type g_allocation_ok, @object
.size g_allocation_ok, 4
g_allocation_ok:
.zero 4
.type g_remaining_free, @object
.size g_remaining_free, 4
g_remaining_free:
.zero 4
.type g_allocated_size, @object
.size g_allocated_size, 4
g_allocated_size:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,117 +0,0 @@
.file "task06_split_block.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a4,.LANCHOR0
andi a5,a4,7
beq a5,zero,.L2
andi a4,a4,-8
addi a4,a4,8
.L2:
mv a5,a4
addi a3,a4,176
sw a3,end_marker,a2
sw zero,176(a4)
sw zero,4(a3)
sw a3,0(a4)
li a2,176
sw a2,4(a4)
lla a2,start
sw a4,0(a2)
sw zero,4(a2)
li a1,39
j .L7
.L10:
mv a5,a4
.L7:
lw a4,4(a5)
bgtu a4,a1,.L13
lw a4,0(a5)
mv a2,a5
bne a4,a3,.L10
li a5,0
j .L6
.L13:
addi a4,a4,-40
li a1,16
bleu a4,a1,.L4
sw a4,44(a5)
lw a4,0(a5)
sw a4,40(a5)
addi a4,a5,40
sw a4,0(a2)
li a4,40
sw a4,4(a5)
.L5:
sw zero,0(a5)
lw a4,4(a5)
li a2,-2147483648
or a4,a4,a2
sw a4,4(a5)
addi a5,a5,8
.L6:
lw a5,-4(a5)
slli a5,a5,1
srli a5,a5,1
sw a5,g_allocated_size,a4
lw a5,start
lw a4,4(a5)
sw a4,g_split_free_size,a2
beq a5,a3,.L11
li a4,0
.L9:
addi a4,a4,1
lw a5,0(a5)
bne a5,a3,.L9
.L8:
sw a4,g_free_nodes,a5
li a0,0
ret
.L4:
lw a4,0(a5)
sw a4,0(a2)
j .L5
.L11:
li a4,0
j .L8
.size main, .-main
.globl g_free_nodes
.globl g_split_free_size
.globl g_allocated_size
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 200
heap_area:
.zero 200
.section .sbss,"aw",@nobits
.align 2
.type g_free_nodes, @object
.size g_free_nodes, 4
g_free_nodes:
.zero 4
.type g_split_free_size, @object
.size g_split_free_size, 4
g_split_free_size:
.zero 4
.type g_allocated_size, @object
.size g_allocated_size, 4
g_allocated_size:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,116 +0,0 @@
.file "task07_free_insert.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a5,.LANCHOR0
lla a4,.LANCHOR0+176
sw a4,end_marker,a3
sw a5,start,a3
lla a3,.LANCHOR0+128
sw a3,0(a5)
sw a4,128(a5)
sw zero,176(a5)
li a4,40
sw a4,4(a5)
li a4,48
sw a4,132(a5)
sw zero,180(a5)
li a4,32
sw a4,68(a5)
lla a5,start
lla a3,.LANCHOR0+64
.L2:
mv a4,a5
lw a5,0(a5)
bltu a5,a3,.L2
lla a3,.LANCHOR0
sw a5,64(a3)
lla a5,.LANCHOR0+64
sw a5,0(a4)
lw a5,start
beq a5,a3,.L16
sw zero,g_order_ok,a4
lla a4,.LANCHOR0+176
beq a5,a4,.L5
.L9:
mv a4,a5
li a3,0
lla a2,.LANCHOR0+176
.L6:
addi a3,a3,1
lw a4,0(a4)
bne a4,a2,.L6
sw a3,g_free_count,a4
li a4,0
lla a2,.LANCHOR0+176
.L7:
lw a3,4(a5)
add a4,a4,a3
lw a5,0(a5)
bne a5,a2,.L7
.L8:
sw a4,g_total_free,a5
li a0,0
ret
.L16:
lla a3,.LANCHOR0+64
lw a2,.LANCHOR0
li a4,0
beq a2,a3,.L17
.L4:
sw a4,g_order_ok,a3
j .L9
.L17:
lla a3,.LANCHOR0+128
lw a2,.LANCHOR0+64
bne a2,a3,.L4
lla a3,.LANCHOR0+176
lw a4,.LANCHOR0+128
sub a4,a4,a3
seqz a4,a4
j .L4
.L5:
sw zero,g_free_count,a5
li a4,0
j .L8
.size main, .-main
.globl g_total_free
.globl g_free_count
.globl g_order_ok
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 192
heap_area:
.zero 192
.section .sbss,"aw",@nobits
.align 2
.type g_total_free, @object
.size g_total_free, 4
g_total_free:
.zero 4
.type g_free_count, @object
.size g_free_count, 4
g_free_count:
.zero 4
.type g_order_ok, @object
.size g_order_ok, 4
g_order_ok:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-116
View File
@@ -1,116 +0,0 @@
.file "task08_coalescing.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
lla a5,.LANCHOR0
lla a4,.LANCHOR0+160
sw a4,end_marker,a3
sw a5,start,a3
lla a3,.LANCHOR0+96
sw a3,0(a5)
sw a4,96(a5)
sw zero,160(a5)
li a4,48
sw a4,4(a5)
sw a4,52(a5)
li a4,64
sw a4,100(a5)
sw zero,164(a5)
lla a5,start
lla a3,.LANCHOR0+48
.L2:
mv a4,a5
lw a5,0(a5)
bltu a5,a3,.L2
lla a3,.LANCHOR0+96
beq a5,a3,.L12
.L3:
sw a5,.LANCHOR0+48,a3
lla a5,start
lla a3,.LANCHOR0+48
beq a4,a5,.L4
lw a2,4(a4)
slli a5,a2,1
srli a5,a5,1
add a5,a4,a5
beq a5,a3,.L13
.L4:
sw a3,0(a4)
lw a2,start
lla a5,.LANCHOR0+160
beq a2,a5,.L9
mv a5,a2
li a4,0
lla a3,.LANCHOR0+160
.L6:
addi a4,a4,1
lw a5,0(a5)
bne a5,a3,.L6
.L5:
sw a4,g_free_count,a5
lw a5,4(a2)
sw a5,g_merged_size,a4
lw a5,0(a2)
lla a4,.LANCHOR0+160
sub a5,a5,a4
seqz a5,a5
sw a5,g_merged_with_right,a4
li a0,0
ret
.L12:
li a5,112
sw a5,.LANCHOR0+52,a3
lw a5,0(a4)
lw a5,0(a5)
j .L3
.L13:
lla a3,.LANCHOR0
lw a5,52(a3)
add a5,a5,a2
sw a5,4(a4)
lw a3,48(a3)
j .L4
.L9:
li a4,0
j .L5
.size main, .-main
.globl g_merged_with_right
.globl g_merged_size
.globl g_free_count
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 192
heap_area:
.zero 192
.section .sbss,"aw",@nobits
.align 2
.type g_merged_with_right, @object
.size g_merged_with_right, 4
g_merged_with_right:
.zero 4
.type g_merged_size, @object
.size g_merged_size, 4
g_merged_size:
.zero 4
.type g_free_count, @object
.size g_free_count, 4
g_free_count:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
@@ -1,44 +0,0 @@
.file "task09_config_macros.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.globl main
.type main, @function
main:
li a5,256
sw a5,g_total_heap,a4
li a5,248
sw a5,g_adjusted_heap,a4
li a5,1
sw a5,g_dynamic_enabled,a4
sw zero,g_trace_is_host_only,a5
li a0,0
ret
.size main, .-main
.globl g_trace_is_host_only
.globl g_dynamic_enabled
.globl g_adjusted_heap
.globl g_total_heap
.section .sbss,"aw",@nobits
.align 2
.type g_trace_is_host_only, @object
.size g_trace_is_host_only, 4
g_trace_is_host_only:
.zero 4
.type g_dynamic_enabled, @object
.size g_dynamic_enabled, 4
g_dynamic_enabled:
.zero 4
.type g_adjusted_heap, @object
.size g_adjusted_heap, 4
g_adjusted_heap:
.zero 4
.type g_total_heap, @object
.size g_total_heap, 4
g_total_heap:
.zero 4
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
-253
View File
@@ -1,253 +0,0 @@
.file "task10_heap4_demo.c"
.option nopic
.attribute arch, "rv32i2p1_zicsr2p0_zifencei2p0"
.attribute unaligned_access, 0
.attribute stack_align, 16
.text
.align 2
.type heap_malloc_model, @function
heap_malloc_model:
beq a0,zero,.L1
addi a4,a0,8
andi a0,a0,7
beq a0,zero,.L3
andi a4,a4,-8
addi a4,a4,8
.L3:
lw a0,start
lw a3,end_marker
beq a0,a3,.L10
lla a2,start
j .L8
.L11:
mv a0,a5
.L8:
lw a5,4(a0)
bgeu a5,a4,.L12
lw a5,0(a0)
mv a2,a0
bne a5,a3,.L11
li a0,0
ret
.L12:
sub a3,a5,a4
li a1,16
bleu a3,a1,.L5
add a5,a0,a4
sw a3,4(a5)
lw a3,0(a0)
sw a3,0(a5)
sw a5,0(a2)
sw a4,4(a0)
.L6:
sw zero,0(a0)
lw a5,4(a0)
li a3,-2147483648
or a5,a5,a3
sw a5,4(a0)
lla a3,free_bytes_remaining
lw a5,0(a3)
sub a5,a5,a4
sw a5,0(a3)
lw a4,minimum_ever_free
bgeu a5,a4,.L7
sw a5,minimum_ever_free,a4
.L7:
addi a0,a0,8
ret
.L5:
lw a4,0(a0)
sw a4,0(a2)
mv a4,a5
j .L6
.L10:
li a0,0
.L1:
ret
.size heap_malloc_model, .-heap_malloc_model
.align 2
.type heap_free_model, @function
heap_free_model:
beq a0,zero,.L13
lw a5,-4(a0)
blt a5,zero,.L19
.L13:
ret
.L19:
addi a3,a0,-8
slli a5,a5,1
srli a2,a5,1
sw a2,-4(a0)
lla a4,free_bytes_remaining
lw a5,0(a4)
add a5,a5,a2
sw a5,0(a4)
lla a5,start
.L15:
mv a4,a5
lw a5,0(a5)
bgtu a3,a5,.L15
add a1,a3,a2
beq a5,a1,.L20
.L16:
sw a5,-8(a0)
lla a5,start
beq a4,a5,.L17
lw a2,4(a4)
slli a5,a2,1
srli a5,a5,1
add a5,a4,a5
beq a3,a5,.L21
.L17:
sw a3,0(a4)
ret
.L20:
lw a5,4(a5)
add a5,a5,a2
sw a5,-4(a0)
lw a5,0(a4)
lw a5,0(a5)
j .L16
.L21:
lw a5,-4(a0)
add a5,a5,a2
sw a5,4(a4)
lw a5,-8(a0)
sw a5,0(a4)
ret
.size heap_free_model, .-heap_free_model
.align 2
.globl main
.type main, @function
main:
addi sp,sp,-32
sw ra,28(sp)
sw s0,24(sp)
sw s1,20(sp)
sw s2,16(sp)
sw s3,12(sp)
sw s4,8(sp)
lla a4,.LANCHOR0
andi a3,a4,7
mv a5,a4
beq a3,zero,.L23
andi a4,a4,-8
addi a5,a4,8
.L23:
lla s0,.LANCHOR0+256
sub s0,s0,a5
andi s0,s0,-8
addi a4,a5,-8
add s0,s0,a4
sw s0,end_marker,a4
sw zero,0(s0)
sw zero,4(s0)
sw s0,0(a5)
sub a4,s0,a5
sw a4,4(a5)
lla s4,start
sw a5,0(s4)
sw zero,4(s4)
sw a4,free_bytes_remaining,a5
sw a4,minimum_ever_free,a5
li a0,24
call heap_malloc_model
mv s2,a0
li a0,40
call heap_malloc_model
mv s3,a0
mv a0,s2
call heap_free_model
li a0,16
call heap_malloc_model
mv s1,a0
mv a0,s3
call heap_free_model
mv a0,s1
call heap_free_model
snez s2,s2
snez s3,s3
and s2,s2,s3
snez s1,s1
and s1,s1,s2
sw s1,g_allocations_ok,a5
lw a5,0(s4)
beq s0,a5,.L24
mv a4,a5
li a3,0
.L25:
addi a3,a3,1
lw a4,0(a4)
bne s0,a4,.L25
sw a3,g_final_free_nodes,a2
li a3,0
.L26:
lw a2,4(a5)
add a3,a3,a2
lw a5,0(a5)
bne a5,a4,.L26
.L27:
sw a3,g_final_free_total,a5
lw a5,minimum_ever_free
sw a5,g_minimum_ever_free,a4
li a0,0
lw ra,28(sp)
lw s0,24(sp)
lw s1,20(sp)
lw s2,16(sp)
lw s3,12(sp)
lw s4,8(sp)
addi sp,sp,32
jr ra
.L24:
sw zero,g_final_free_nodes,a5
li a3,0
j .L27
.size main, .-main
.globl g_minimum_ever_free
.globl g_final_free_total
.globl g_final_free_nodes
.globl g_allocations_ok
.bss
.align 2
.set .LANCHOR0,. + 0
.type heap_area, @object
.size heap_area, 264
heap_area:
.zero 264
.section .sbss,"aw",@nobits
.align 2
.type minimum_ever_free, @object
.size minimum_ever_free, 4
minimum_ever_free:
.zero 4
.type free_bytes_remaining, @object
.size free_bytes_remaining, 4
free_bytes_remaining:
.zero 4
.type g_minimum_ever_free, @object
.size g_minimum_ever_free, 4
g_minimum_ever_free:
.zero 4
.type g_final_free_total, @object
.size g_final_free_total, 4
g_final_free_total:
.zero 4
.type g_final_free_nodes, @object
.size g_final_free_nodes, 4
g_final_free_nodes:
.zero 4
.type g_allocations_ok, @object
.size g_allocations_ok, 4
g_allocations_ok:
.zero 4
.type end_marker, @object
.size end_marker, 4
end_marker:
.zero 4
.type start, @object
.size start, 8
start:
.zero 8
.ident "GCC: (15.2.0-23) 15.2.0"
.section .note.GNU-stack,"",@progbits
+276 -256
View File
@@ -1,21 +1,18 @@
\documentclass[12pt]{article}
\documentclass[11pt]{article}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[polish]{babel}
\usepackage[a4paper,margin=2.0cm]{geometry}
\usepackage{array}
\usepackage{tabularx}
\usepackage{xcolor}
\usepackage{listings}
\usepackage{hyperref}
\usepackage{fancyhdr}
\usepackage{lastpage}
\usepackage[a4paper,margin=1.75cm]{geometry}
\usepackage{array,tabularx,booktabs}
\usepackage{amsmath,amssymb}
\usepackage{xcolor,listings}
\usepackage{hyperref,fancyhdr,lastpage}
\usepackage{enumitem}
\definecolor{accent}{HTML}{16324A}
\definecolor{accentlight}{HTML}{EEF3F7}
\definecolor{rulegray}{HTML}{D7DEE5}
\hypersetup{colorlinks=true,linkcolor=blue,urlcolor=blue,citecolor=blue}
\hypersetup{colorlinks=true,linkcolor=accent,urlcolor=blue}
\IfFileExists{build-meta.tex}{\input{build-meta.tex}}{\newcommand{\BuildCommit}{local}}
\newcommand{\PublisherDomain}{mpabi}
\newcommand{\DocumentAuthor}{M. Pabiszczak}
@@ -25,267 +22,290 @@
\newcommand{\CardNumber}{01}
\newcommand{\CardCount}{14}
\newcommand{\CardSlug}{heap4}
\newcommand{\CardVersion}{v0.1}
\newcommand{\CardVersion}{v0.2}
\newcommand{\DocumentKey}{\PublisherDomain/\CardArea/\CardSeries/\CardNumber/\CardSlug/\CardVersion}
\newcommand{\DocumentUUID}{4654524c-73f2-46df-83a3-d448ba6d6c5a}
\renewcommand{\lstlistingname}{Listing}
\renewcommand{\lstlistlistingname}{Lista listingów}
\lstset{basicstyle=\ttfamily\small,columns=fullflexible,keepspaces=true,frame=single,breaklines=true,showstringspaces=false,numbers=left,numberstyle=\tiny\color{accent},stepnumber=1,numbersep=8pt,backgroundcolor=\color{accentlight},rulecolor=\color{rulegray},captionpos=b}
\newcommand{\TaskSection}[5]{\section{Task #1: #3}\subsection*{Algorytm i zakresy linii}#4\lstinputlisting[language=C,caption={Task #1: #3},label={lst:task#1-c}]{../src/tasks/#2.c}\subsection*{Co sprawdzić w ASM}#5\lstinputlisting[basicstyle=\ttfamily\scriptsize,caption={ASM dla task #1},label={lst:task#1-asm}]{../build/#2/#2.s}}
\lstset{
language=C,basicstyle=\ttfamily\scriptsize,columns=fullflexible,
keepspaces=true,frame=single,breaklines=true,showstringspaces=false,
numbers=left,numberstyle=\tiny\color{accent},numbersep=6pt,
backgroundcolor=\color{accentlight},rulecolor=\color{rulegray}
}
\pagestyle{fancy}
\fancyhf{}
\lhead{\textbf{FreeRTOS: heap\_4}}
\rhead{\small \DocumentAuthor, \DocumentYear}
\rhead{\small karta 01/14}
\lfoot{\scriptsize commit \BuildCommit}
\cfoot{\scriptsize \thepage/\pageref{LastPage}}
\rfoot{\scriptsize uuid \DocumentUUID}
\renewcommand{\headrulewidth}{0.4pt}
\setlength{\headheight}{18pt}
\setlength{\footskip}{24pt}
\rfoot{\scriptsize V11.3.0 / \CardVersion}
\setlength{\headheight}{15pt}
\setlength{\footskip}{22pt}
\setlist[itemize]{nosep,leftmargin=1.5em}
\setlist[enumerate]{itemsep=.25em,leftmargin=1.7em}
\newcommand{\checkline}{\(\square\)\;}
\begin{document}
\sloppy
\noindent
\begin{tabular}{@{}lp{0.72\textwidth}@{}}
Project: & FreeRTOS: \texttt{heap\_4} \\
Subject: & Informatyka \\
Level: & 1, Zakres Podstawowy, PP \\
Topic: & dynamiczna alokacja pamięci, lista wolnych bloków, scalanie bloków \\
Revision date: & 14.07.2026 \\
Status: & Draft \\
Card in series: & \small\texttt{\CardNumber/\CardCount} \\
Card version: & \small\texttt{\CardVersion} \\
Card key: & \small\texttt{\DocumentKey} \\
UUID: & \small\texttt{\DocumentUUID} \\
Commit: & \small\texttt{\BuildCommit} \\
Scope: & pierwsza karta serii FreeRTOS; model dydaktyczny mechaniki \texttt{heap\_4.c} \\
Files used: & \small
\begin{tabular}[t]{@{}l@{}}
Task01: \texttt{task01\_heap\_map.c} \\
Task02: \texttt{task02\_free\_list.c} \\
Task03: \texttt{task03\_heap\_init.c} \\
Task04: \texttt{task04\_alignment.c} \\
Task05: \texttt{task05\_malloc\_first\_fit.c} \\
Task06: \texttt{task06\_split\_block.c} \\
Task07: \texttt{task07\_free\_insert.c} \\
Task08: \texttt{task08\_coalescing.c} \\
Task09: \texttt{task09\_config\_macros.c} \\
Task10: \texttt{task10\_heap4\_demo.c} \\
Common: \texttt{heap4\_common.h} \\
Runtime: \texttt{memops.c} \\
Startup: \texttt{crt0.S}
\end{tabular}
\end{tabular}
\vspace{0.8em}
\noindent\rule{\textwidth}{0.5pt}
\begin{center}
{\LARGE\bfseries Karta pracy: FreeRTOS \texttt{heap\_4}}\par
\vspace{.4em}
{\large model $\rightarrow$ prawdziwy upstream $\rightarrow$ RP2350}\par
\end{center}
\section{Cel}
Ta karta zaczyna serię FreeRTOS od \texttt{heap\_4}, ponieważ ten plik jest
naturalnym pomostem po kartach z języka C. Wymaga wskaźników, tablic,
struktur, listy jednokierunkowej, \texttt{static}, \texttt{typedef}, makr oraz
arytmetyki adresów, ale nie wymaga jeszcze schedulera, przerwań ani
uruchamiania tasków.
Przykłady są małym modelem edukacyjnym mechaniki \texttt{heap\_4.c}. Nie są
kopią pliku upstream FreeRTOS. Na RV32I wyniki są zapisywane w globalnych
zmiennych \texttt{volatile}; \texttt{printf} jest dostępny wyłącznie w wariancie
hostowym przez flagę \texttt{-DHOST\_PRINTF=1}.
W tej karcie słowo „sterta” oznacza obszar pamięci, którym zarządza alokator.
W \texttt{heap\_4} taki obszar jest zwykle statyczną tablicą bajtów, więc linker
może umieścić go w sekcji \texttt{.bss}. Sama sekcja \texttt{.bss} nie jest jednak
alokacją dynamiczną: dopiero kod \texttt{pvPortMalloc}/\texttt{vPortFree} albo
model z tej karty dzieli bajty tego bufora na bloki, prowadzi listę wolnych
bloków i wykonuje scalanie. W Task 03 widać ten moment przejścia od zwykłego
statycznego bufora do sterty zarządzanej przez algorytm.
Listingi C i ASM mają numerowane linie. Opisy w sekcjach tasków odnoszą się
do numerów widocznych po lewej stronie listingów C.
\subsection{Powiązanie z FreeRTOS Book v1.1.0}
Karta K01 odpowiada rozdziałowi 3 książki \emph{Mastering the FreeRTOS Real
Time Kernel}, przede wszystkim sekcji 3.2.4 o \texttt{heap\_4} oraz rysunkom
3.1--3.4. Ten rozdział nie ma programu z numerowanego zestawu
\texttt{Example001}--\texttt{Example025}. Dlatego Taski 01--08 są krótkimi,
własnymi eksperymentami izolującymi kolejne elementy algorytmu, a Task 10
składa je w jeden model.
Nie kopiujemy listingu upstream ani projektu Win32/MSVC. Ta karta służy do
zrozumienia mechaniki; karta K02 przechodzi na niezmodyfikowany
\texttt{portable/MemMang/heap\_4.c} z FreeRTOS V11.3.0. Pełna mapa książki,
lekcji i kart znajduje się w pliku \texttt{series/inf/README.md}.
\lstinputlisting[language=C,caption={Wspólny nagłówek dla karty \texttt{heap\_4}},label={lst:common-h}]{../src/tasks/heap4_common.h}
\subsection{\texttt{static inline} w nagłówku}
Helpery z Listingu~\ref{lst:common-h} są zapisane jako \texttt{static inline},
bo każdy task jest osobnym programem i dołącza ten sam nagłówek. \texttt{static}
daje prywatną kopię funkcji w każdym pliku \texttt{.c}, a \texttt{inline}
pozwala kompilatorowi wstawić krótkie obliczenia, takie jak wyrównanie rozmiaru
lub sprawdzenie bitu alokacji, bez kosztu zwykłego wywołania funkcji.
\section{Organizacja gałęzi}
Gałąź \texttt{deploy} jest pełną publiczną wersją karty. Każde zadanie ma
osobną gałąź startową \texttt{tasks/*}, na przykład
\texttt{tasks/05-malloc-first-fit}. Taka gałąź nie jest wycinkiem repozytorium:
zawiera dokumentację, treść karty, \texttt{Makefile}, kod pomocniczy i pliki
danego zadania.
Uczeń nie commituje bezpośrednio do \texttt{tasks/*}. W repozytorium klasy
tworzy gałąź pochodną, na przykład
\texttt{students/u01/tasks/05-malloc-first-fit}.
\section{Mapa karty}
\begin{tabularx}{\textwidth}{|p{2.1cm}|X|}
\hline
Task & Temat \\
\hline
1 & Mapa pojęć: obszar sterty, nagłówek bloku i bit alokacji. \\
\hline
2 & Lista wolnych bloków jako lista jednokierunkowa. \\
\hline
3 & Inicjalizacja sterty, pierwszy wolny blok i wartownik końca listy. \\
\hline
4 & Wyrównanie rozmiarów żądań i adresu początku sterty. \\
\hline
5 & Pierwszy pasujący blok, czyli zasada \texttt{first fit}. \\
\hline
6 & Podział dużego bloku na część przydzieloną i pozostały blok wolny. \\
\hline
7 & Zwracanie bloku i wstawianie go do listy według adresu. \\
\hline
8 & Scalanie sąsiednich wolnych bloków. \\
\hline
9 & Makra konfiguracyjne oraz hostowy \texttt{TRACE\_PRINTF}. \\
\hline
10 & Mini-demonstracja sekwencji \texttt{malloc/free}. \\
\hline
\begin{tabularx}{\textwidth}{@{}lX@{}}
\toprule
Klucz & \texttt{\DocumentKey} \\
Wersja / commit & \texttt{\CardVersion} / \texttt{\BuildCommit} \\
Data & 14.07.2026 \\
Allocator & FreeRTOS-Kernel V11.3.0, \texttt{9b777ae5c5b8} \\
Port RP2350 & fork Raspberry Pi, \texttt{4f7299d6ea74} \\
Źródło & \texttt{portable/MemMang/heap\_4.c} — bez modyfikacji \\
Punkt wejścia & \texttt{stemctl} \\
\bottomrule
\end{tabularx}
\TaskSection{01}{task01_heap_map}{mapa \texttt{heap\_4}}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--7 definiują zmienne \texttt{volatile}, czyli obserwowalne wyniki taska.
\item Linie 11--16 tworzą pojedynczy blok i zapisują w jego polu \texttt{size} rozmiar z bitem alokacji.
\item Linie 18--22 rozbijają nagłówek bloku na wartości pomocnicze: rozmiar nagłówka, rozmiar wyrównany, bit zajętości, rozmiar całego bloku i część użytkownika.
\item Linie 24--27 wypisują te same wartości tylko w wariancie hostowym; na RV32I makro \texttt{TRACE\_PRINTF} znika.
\item Wynik sprawdzaj w \texttt{g\_header\_size}, \texttt{g\_allocated\_bit\_set}, \texttt{g\_total\_block\_bytes} i \texttt{g\_user\_bytes}.
\end{itemize}
}{
W ASM szukaj operacji ustawiania najwyższego bitu rozmiaru oraz zapisów do symboli globalnych \texttt{g\_*}. Dostęp do pól \texttt{block.next} i \texttt{block.size} powinien być zwykłym zapisem pod adres bazowy stosu z przesunięciem.
}
\TaskSection{02}{task02_free_list}{lista wolnych bloków}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 7--16 implementują sumowanie rozmiarów bloków na liście.
\item Linie 18--28 przechodzą po tej samej liście i zapamiętują największy blok.
\item Linie 30--39 liczą liczbę węzłów listy.
\item Linie 43--55 budują ręcznie listę \texttt{start -> a -> b -> c -> NULL}.
\item Linie 57--59 uruchamiają trzy funkcje pomiarowe i zapisują wyniki w \texttt{volatile}.
\end{itemize}
}{
W ASM zwróć uwagę na pętle po wskaźniku \texttt{block = block->next}. To jest podstawowy idiom przechodzenia po liście jednokierunkowej, widoczny jako ładowanie pola \texttt{next} i skok warunkowy.
}
\TaskSection{03}{task03_heap_init}{inicjalizacja sterty}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--7 definiują bufor sterty, węzeł startowy listy i wskaźnik końcowego wartownika.
\item Linie 20--23 wyrównują początek sterty i obcinają dostępną długość do wielokrotności wyrównania.
\item Linie 25--28 ustawiają adres i pola końcowego wartownika.
\item Linie 30--34 tworzą pierwszy wolny blok i podpinają go za \texttt{start}.
\item Linie 39--43 wykonują inicjalizację i zapisują trzy wartości kontrolne.
\end{itemize}
}{
W ASM szukaj maskowania dolnych bitów adresu i rozmiaru. Warto też porównać zapisy do \texttt{first->next}, \texttt{first->size}, \texttt{start.next} i \texttt{start.size}; każdy z nich jest dostępem do pola struktury.
}
\TaskSection{04}{task04_alignment}{wyrównanie}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--6 definiują wyniki pokazujące wpływ wyrównania.
\item Linie 14--16 liczą rzeczywiste rozmiary bloków dla żądań 1, 9 i 17 bajtów, po dodaniu nagłówka.
\item Linie 18--20 biorą celowo nie-wyrównany adres \texttt{\&bytes[1]} i wyliczają przesunięcie do kolejnej granicy wyrównania.
\item Linie 22--24 są hostowym wypisem kontrolnym.
\end{itemize}
}{
W ASM znajdź operacje \texttt{and} używane do testowania dolnych bitów oraz dodawanie brakującej liczby bajtów do wyrównania. To jest ten sam mechanizm, którego używa alokator dla rozmiaru bloku i początku sterty.
}
\TaskSection{05}{task05_malloc_first_fit}{\texttt{first fit}}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 13--30 inicjalizują stertę z jednym wolnym blokiem oraz wartownikiem końca.
\item Linie 38--40 zamieniają żądanie użytkownika na całkowity rozmiar bloku i ustawiają początek przeszukiwania listy.
\item Linie 42--51 realizują algorytm \texttt{first fit}: idą po liście do pierwszego bloku o wystarczającym rozmiarze.
\item Linie 44--47 zdejmują znaleziony blok z listy wolnych, oznaczają go jako zajęty i zwracają adres danych użytkownika.
\item Linie 61--67 wykonują alokację i zapisują, czy się udała, jaki rozmiar dostał blok i ile wolnego miejsca zostało na liście.
\end{itemize}
}{
W ASM obserwuj porównanie \texttt{block->size >= total}, zmianę \texttt{previous->next} oraz dodanie \texttt{sizeof(HeapBlock)} do adresu bloku przed zwróceniem wskaźnika użytkownika.
}
\TaskSection{06}{task06_split_block}{podział bloku}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--12 definiują rozmiar sterty, minimalny sensowny rozmiar reszty i zmienne wynikowe.
\item Linie 14--28 tworzą jeden duży wolny blok.
\item Linie 38--44 szukają bloku pasującego do żądania i zapamiętują jego pierwotny rozmiar.
\item Linie 45--50 wykonują podział: nowy blok \texttt{remainder} zaczyna się pod adresem \texttt{block + total}.
\item Linie 51--56 obsługują przypadek bez podziału, oznaczają blok jako zajęty i zwracają adres użytkownika.
\item Linie 65--73 liczą pozostałe wolne bloki, a linie 81--87 zapisują wynik demonstracji.
\end{itemize}
}{
W ASM szukaj arytmetyki adresów dla \texttt{remainder}: adres bloku plus \texttt{total}. To najważniejszy fragment pokazujący, że dzielenie bloku jest tylko przesunięciem wskaźnika i zapisaniem nowego nagłówka.
}
\TaskSection{07}{task07_free_insert}{\texttt{free} i wstawianie}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 11--20 wstawiają blok do listy w miejscu wynikającym z porządku adresów.
\item Linie 22--32 modelują \texttt{free}: z adresu użytkownika wracają do nagłówka bloku, czyszczą bit alokacji i wywołują wstawianie.
\item Linie 34--54 zawierają funkcje kontrolne liczące węzły oraz łączny rozmiar wolnej pamięci.
\item Linie 63--76 budują układ \texttt{a}, \texttt{b}, \texttt{c}, gdzie \texttt{b} jest zajętym blokiem między dwoma wolnymi.
\item Linie 78--83 zwalniają \texttt{b} i sprawdzają, czy lista ma kolejność \texttt{a -> b -> c -> end}.
\end{itemize}
}{
W ASM zwróć uwagę na porównywanie adresów w pętli z linii 15. To porządkowanie po adresie jest przygotowaniem do scalania bloków w następnym tasku.
}
\TaskSection{08}{task08_coalescing}{scalanie bloków}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 11--17 znajdują miejsce w liście, w którym powinien znaleźć się zwracany blok.
\item Linie 19--25 sprawdzają sąsiedztwo z prawym blokiem; jeśli adres końca zwracanego bloku jest adresem następnego, rozmiary są łączone.
\item Linie 27--32 sprawdzają sąsiedztwo z lewym blokiem i ewentualnie dołączają scalony wynik do lewej strony.
\item Linie 35--44 liczą końcową liczbę wolnych bloków.
\item Linie 52--65 budują trzy sąsiednie bloki w pamięci, a linia 67 uruchamia scalanie środkowego bloku.
\item Linie 69--71 zapisują, czy po scaleniu został jeden blok i jaki ma rozmiar.
\end{itemize}
}{
W ASM szukaj testu \texttt{(uint8\_t *)block + block->size == iterator->next}. To jest sedno scalania: alokator nie porównuje indeksów, tylko rzeczywiste adresy końca i początku bloków.
}
\TaskSection{09}{task09_config_macros}{makra konfiguracyjne}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 3--9 definiują wartości domyślne tylko wtedy, gdy nie zostały podane z zewnątrz przez kompilator.
\item Linia 11 tworzy wartość pochodną od konfiguracji i wyrównania.
\item Linie 20--22 zapisują wartości makr do zmiennych \texttt{volatile}, żeby były widoczne w debugerze.
\item Linie 24--28 pokazują kompilację warunkową: host ustawia \texttt{g\_trace\_is\_host\_only} na 1, RV32I na 0.
\item Linie 30--32 wypisują wynik tylko w wariancie hostowym.
\end{itemize}
}{
W ASM dla RV32I nie powinno być wywołania \texttt{printf}. Sprawdź też, że makra konfiguracyjne nie są zmiennymi w pamięci, tylko stałymi wstawionymi do kodu przez preprocesor.
}
\TaskSection{10}{task10_heap4_demo}{mini \texttt{heap\_4}}{
\begin{itemize}[leftmargin=1.6em]
\item Linie 18--38 to funkcje diagnostyczne przechodzące po liście wolnych bloków.
\item Linie 40--62 wstawiają blok i scalają go z prawym oraz lewym sąsiadem.
\item Linie 64--88 inicjalizują stertę: wyrównują początek, ustawiają wartownika, pierwszy wolny blok i liczniki wolnej pamięci.
\item Linie 90--131 implementują alokację: sprawdzają rozmiar żądania, szukają bloku, dzielą go lub zdejmują w całości z listy, oznaczają jako zajęty i aktualizują minimum wolnej pamięci.
\item Linie 133--149 implementują zwalnianie: wracają z adresu użytkownika do nagłówka, sprawdzają bit alokacji, przywracają rozmiar i scalają blok.
\item Linie 157--169 wykonują sekwencję \texttt{malloc/free} i zapisują stan końcowy alokatora.
\end{itemize}
}{
W ASM porównaj trzy fragmenty: inicjalizację listy, pętlę szukania wolnego bloku oraz wywołanie \texttt{insert\_block\_merge}. To pokazuje, że pełny model składa się z prostych operacji wskaźnikowych poznanych w poprzednich taskach.
}
\section{Polecenia}
\begin{enumerate}[leftmargin=1.6em]
\item Zbuduj kartę dla RV32I poleceniem \texttt{make tasks}.
\item Zbuduj wariant hostowy poleceniem \texttt{make host}; wypisywanie działa tylko przez \texttt{HOST\_PRINTF}.
\item W tasku 6 zmień próg \texttt{MIN\_SPLIT\_SIZE} i sprawdź, kiedy blok nie jest dzielony.
\item W tasku 8 zmień rozmiary bloków tak, aby scalanie działało tylko z prawym sąsiadem.
\item W tasku 10 dodaj większą alokację, która ma się nie udać, i zapisz status w nowej zmiennej \texttt{volatile}.
\section*{Tylko trzy kroki}
\begin{enumerate}
\item First-fit + split: skąd bierze się drugi nagłówek.
\item Free-list + coalescing: dlaczego lista jest uporządkowana po adresie.
\item API FreeRTOS: \texttt{pvPortMalloc}, \texttt{vPortFree}, stats, OOM.
\end{enumerate}
\section*{Warunek zaliczenia}
\begin{itemize}
\item \checkline przewiduję rozmiary nagłówków i bloków na AMD64/RV32;
\item \checkline rozpoznaję fragmentację: suma wolnych bajtów $\neq$ największy blok;
\item \checkline pokazuję split i dwa kierunki scalania w Memory;
\item \checkline odczytuję \texttt{xStart}, \texttt{pxEnd}, minimum-ever i stack frame;
\item \checkline Task 3 daje \texttt{pass=1} na host, Hazard3 i RP2350.
\end{itemize}
\newpage
\section{Start — polecenia i platformy}
\begin{lstlisting}[language=bash,numbers=none]
stemctl test native-amd64 freertos heap4 1
stemctl test hazard3-sim freertos heap4 2
stemctl debug hazard3-sim freertos heap4 3
stemctl test rp2350 freertos heap4 3
stemctl deploy rp2350 freertos heap4 3 \
--device /dev/bus/usb/BBB/DDD
\end{lstlisting}
\begin{tabularx}{\textwidth}{@{}p{3.1cm}XXX@{}}
\toprule
& AMD64 & Hazard3 & RP2350 \\
\midrule
Task 1--2 & model + sanitizery & model na RTL & build \\
Task 3 & V11.3 + adapter 1-thread & pełny V11.3 + port RISC-V & zgodny core+port RP; allocator V11.3 \\
Debug & GDB & GDB stub RTL & OpenOCD; SRAM \\
Deploy & --- & --- & SPI flash + verify \\
\bottomrule
\end{tabularx}
\subsection*{Termdebug}
\begin{tabularx}{\textwidth}{@{}lXlX@{}}
\toprule
\texttt{F5} & continue & \texttt{F9} & breakpoint \\
\texttt{F10} & next & \texttt{F11} & step \\
\texttt{Shift-F11} & finish & \texttt{F8} & stepi \\
\texttt{:StudentStack} & frame + stos & \texttt{:StudentLst} & listing ELF-a \\
\bottomrule
\end{tabularx}
\textbf{Strategia obserwacji}: breakpoint przed zmianą $\rightarrow$ Memory
$\rightarrow$ continue/finish $\rightarrow$ Memory. Nie opieramy karty na data
watchpointach.
\newpage
\section{Task 1 — first-fit i split}
\subsection*{Policz przed uruchomieniem}
\begin{tabular}{@{}lcc@{}}
\toprule
& AMD64 & RV32 \\
\midrule
\texttt{sizeof(ModelBlock)} & \rule{1.6cm}{.2pt} & \rule{1.6cm}{.2pt} \\
request & 13 B & 13 B \\
\texttt{align8(header + request)} & \rule{1.6cm}{.2pt} & \rule{1.6cm}{.2pt} \\
remainder z 256 B & \rule{1.6cm}{.2pt} & \rule{1.6cm}{.2pt} \\
offset payloadu & \rule{1.6cm}{.2pt} & \rule{1.6cm}{.2pt} \\
\bottomrule
\end{tabular}
\subsection*{Do wykonania}
\begin{enumerate}
\item Wskaż nagłówek bloku przydzielonego i nagłówek remainder.
\item Sprawdź: \texttt{wanted + remainder == 256}.
\item Zmień request 13 na 17; przewidź wynik bez uruchamiania.
\item W listingu znajdź dodawanie bajtowego offsetu do adresu areny.
\end{enumerate}
\lstinputlisting{../src/tasks/task01_first_fit_split.c}
\newpage
\section{Task 2 — lista adresowa i coalescing}
\subsection*{Eksperyment}
\begin{center}
\texttt{A:48 | B:48 | C:48}\quad
$\xrightarrow{\text{free A, free C}}$\quad
\texttt{A:48 -> C:48}\quad
$\xrightarrow{\text{free B}}$\quad
\texttt{A:144}
\end{center}
\subsection*{Do wykonania}
\begin{enumerate}
\item Zatrzymaj program w \texttt{task02\_fragmented\_checkpoint}.
\item Zapisz adresy i rozmiary dwóch elementów free-list.
\item Wykonaj \texttt{F5}; zatrzymaj w \texttt{task02\_debug\_checkpoint}.
\item Wskaż pierwszy warunek scalania: z blokiem po prawej.
\item Wskaż drugi warunek scalania: z blokiem po lewej.
\item Wyjaśnij, czemu kolejność po adresie redukuje koszt szukania sąsiadów.
\end{enumerate}
\subsection*{Tabela obserwacji}
\begin{tabular}{@{}lccc@{}}
\toprule
Stan & liczba bloków & suma & największy \\
\midrule
przed B & \rule{1.4cm}{.2pt} & \rule{1.4cm}{.2pt} & \rule{1.4cm}{.2pt} \\
po B & \rule{1.4cm}{.2pt} & \rule{1.4cm}{.2pt} & \rule{1.4cm}{.2pt} \\
\bottomrule
\end{tabular}
\lstinputlisting{../src/tasks/task02_address_order_coalesce.c}
\newpage
\section{Task 3 — prawdziwy FreeRTOS \texttt{heap\_4.c}}
\subsection*{Sekwencja testowa}
\begin{enumerate}
\item init przez pierwsze \texttt{pvPortMalloc(1)} i \texttt{vPortFree}; zapisz $F_0$;
\item malloc: 24 B, 40 B, 16 B; zapisz $F_1$;
\item free pierwszego i trzeciego; oczekuj 2 wolnych bloków;
\item free środkowego; oczekuj jednego bloku i $F_{final}=F_0$;
\item malloc większy niż heap; oczekuj \texttt{NULL} i jednego hooka.
\end{enumerate}
\subsection*{Inwarianty — zaznacz po teście}
\begin{itemize}
\item \checkline $F_0 > F_1$;
\item \checkline $F_{final}=F_0$;
\item \checkline minimum-ever $\leq F_1$ i nie rośnie po free;
\item \checkline każdy payload ma adres podzielny przez 8;
\item \checkline OOM zwraca \texttt{NULL};
\item \checkline końcowa free-list ma dokładnie jeden blok.
\end{itemize}
\lstinputlisting{../src/tasks/task03_freertos_heap4.c}
\newpage
\section{Czytanie upstream \texttt{heap\_4.c}}
\subsection*{Znajdź w pliku — nie czytaj całego od góry}
\begin{lstlisting}[language=bash,numbers=none]
K=/opt/FreeRTOS-Kernel/portable/MemMang/heap_4.c
rg -n 'xHeapStructSize|pvPortMalloc|prvHeapInit' "$K"
rg -n 'prvInsertBlockIntoFreeList|vPortFree' "$K"
rg -n 'xFreeBytesRemaining|xMinimumEver' "$K"
rg -n 'heapADD_WILL_OVERFLOW|heapBLOCK_ALLOCATED' "$K"
\end{lstlisting}
\begin{itemize}[leftmargin=*,itemsep=2pt]
\item \texttt{xHeapStructSize}: dlaczego jest wyrównany oddzielnie od
\texttt{sizeof}?
\item \texttt{heapBLOCK\_ALLOCATED\_BITMASK}: który bit rozmiaru oznacza
właściciela?
\item \texttt{pvPortMalloc}: gdzie są overflow, alignment, first-fit i split?
\item \texttt{vPortFree}: jak odzyskuje nagłówek z payloadu?
\item \texttt{prvInsertBlockIntoFreeList}: gdzie są dwa kierunki scalania?
\item \texttt{xMinimumEverFreeBytesRemaining}: dlaczego free go nie zwiększa?
\end{itemize}
\subsection*{Breakpointy}
\begin{lstlisting}[numbers=none]
b pvPortMalloc
b vPortFree
b prvInsertBlockIntoFreeList
p xStart
p pxEnd
p xFreeBytesRemaining
p xMinimumEverFreeBytesRemaining
x/160bx ucHeap
\end{lstlisting}
\newpage
\section{RP2350 — deploy i dowód sprzętowy}
\subsection*{Polecenie}
\begin{lstlisting}[language=bash,numbers=none]
stemctl probe list
stemctl deploy rp2350 freertos heap4 3 \
--device /dev/bus/usb/BBB/DDD
\end{lstlisting}
\subsection*{Automatyczny test zatrzymuje się dwa razy}
\begin{enumerate}
\item \texttt{heap4\_fragmented\_checkpoint}: 2 wolne bloki;
\item \texttt{task03\_debug\_checkpoint}: heap odtworzony, OOM obsłużony.
\end{enumerate}
\subsection*{Zapis ucznia}
\begin{tabular}{@{}ll@{}}
\toprule
Wielkość & Wartość \\
\midrule
\texttt{initial free} & \rule{4cm}{.2pt} \\
\texttt{after allocations} & \rule{4cm}{.2pt} \\
\texttt{minimum ever} & \rule{4cm}{.2pt} \\
\texttt{final free} & \rule{4cm}{.2pt} \\
\texttt{sp / fp / pc} & \rule{7cm}{.2pt} \\
\bottomrule
\end{tabular}
\subsection*{Kontrakt}
\begin{itemize}
\item flash: write + verify; ELF: RISC-V;
\item breakpointy sprzętowe, nie data watchpoint;
\item \texttt{aligned=1 oom=1 hooks=1 asserts=0 pass=1};
\item \texttt{sp \& 15 == 0};
\item \texttt{rp2350-hardware-ok task=task03 heap\_4=upstream}.
\end{itemize}
\textbf{Operacje są rozdzielone}: \texttt{debug} ładuje do SRAM;
\texttt{deploy} zapisuje trwały obraz do SPI flash.
\newpage
\section{Podsumowanie i oddanie}
\subsection*{Odpowiedz jednym zdaniem}
\begin{enumerate}
\item Dlaczego \texttt{heap\_4} scala, a nie tylko odkłada blok na listę?\\[.6em]
\rule{\textwidth}{.2pt}
\item Dlaczego „wolne razem” nie oznacza „można przydzielić jeden duży blok”?\\[.6em]
\rule{\textwidth}{.2pt}
\item Gdzie leży metadata bloku przydzielonego aplikacji?\\[.6em]
\rule{\textwidth}{.2pt}
\item Co mierzy minimum-ever-free, a czego nie mierzy?\\[.6em]
\rule{\textwidth}{.2pt}
\end{enumerate}
\subsection*{Oddaj}
\begin{itemize}
\item \checkline wyniki 3 tasków na AMD64;
\item \checkline Hazard3: trzy kody wyjścia 0;
\item \checkline zrzut Task 2: przed i po scaleniu;
\item \checkline zrzut Task 3: source + listing + Memory + Stack;
\item \checkline wynik RP2350 \texttt{rp2350-hardware-ok};
\item \checkline wypełnione inwarianty i cztery odpowiedzi.
\end{itemize}
\vfill
\noindent\textbf{Dalej}: taski FreeRTOS, scheduler, osobne stosy, 1 kHz tick;
następnie cienki C++ header wrapper i użycie w projekcie hoveboard.
\end{document}
+68
View File
@@ -0,0 +1,68 @@
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#define configCPU_CLOCK_HZ ( 150000000UL )
#define configTICK_RATE_HZ ( 1000U )
#define configMTIME_BASE_ADDRESS ( 0xC0000100UL )
#define configMTIMECMP_BASE_ADDRESS ( 0xC0000108UL )
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
#define configUSE_IDLE_HOOK 0
#define configUSE_PASSIVE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configMAX_PRIORITIES 4
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 12
#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_32_BITS
#define configIDLE_SHOULD_YIELD 1
#define configNUMBER_OF_CORES 1
#define configTICK_CORE 0
#define configUSE_CORE_AFFINITY 0
#define configRUN_MULTIPLE_PRIORITIES 0
#define configSUPPORT_PICO_SYNC_INTEROP 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configSUPPORT_STATIC_ALLOCATION 0
#define configTOTAL_HEAP_SIZE ( 4096U )
#define configAPPLICATION_ALLOCATED_HEAP 1
#define configUSE_MALLOC_FAILED_HOOK 1
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
#define configENABLE_HEAP_PROTECTOR 0
#define configUSE_TIMERS 0
#define configUSE_MUTEXES 0
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configUSE_TASK_NOTIFICATIONS 1
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
#define configGENERATE_RUN_TIME_STATS 0
#define configUSE_CO_ROUTINES 0
#define configUSE_NEWLIB_REENTRANT 0
#define configUSE_POSIX_ERRNO 0
#define configCHECK_FOR_STACK_OVERFLOW 0
#define INCLUDE_vTaskDelay 0
#define INCLUDE_vTaskDelayUntil 0
#define INCLUDE_vTaskDelete 0
#define INCLUDE_vTaskSuspend 0
#define INCLUDE_xTaskGetSchedulerState 0
#define configENABLE_FPU 0
#define configENABLE_VPU 0
#if !defined(PICO_RP2350)
#define configISR_STACK_SIZE_WORDS 128
#endif
#ifndef __ASSEMBLER__
void heap4_lab_assert(const char *file, int line);
#define configASSERT(condition) \
do { if (!(condition)) heap4_lab_assert(__FILE__, __LINE__); } while (0)
#else
#define configASSERT(condition)
#endif
#endif
@@ -0,0 +1,15 @@
#ifndef FREERTOS_RISC_V_CHIP_SPECIFIC_EXTENSIONS_H
#define FREERTOS_RISC_V_CHIP_SPECIFIC_EXTENSIONS_H
/* Hazard3 adds no architectural registers to the base RV32I context. */
#define portasmHAS_MTIME 1
#define portasmHAS_SIFIVE_CLINT 0
#define portasmADDITIONAL_CONTEXT_SIZE 0
.macro portasmSAVE_ADDITIONAL_REGISTERS
.endm
.macro portasmRESTORE_ADDITIONAL_REGISTERS
.endm
#endif
+4
View File
@@ -0,0 +1,4 @@
#ifndef HEAP4_FREESTANDING_STDLIB_H
#define HEAP4_FREESTANDING_STDLIB_H
#include <stddef.h>
#endif
+7
View File
@@ -0,0 +1,7 @@
#ifndef HEAP4_FREESTANDING_STRING_H
#define HEAP4_FREESTANDING_STRING_H
#include <stddef.h>
void *memcpy(void *destination, const void *source, size_t count);
void *memset(void *destination, int value, size_t count);
size_t strlen(const char *text);
#endif
+50
View File
@@ -0,0 +1,50 @@
#ifndef HEAP4_HOST_FREERTOS_H
#define HEAP4_HOST_FREERTOS_H
#include <stddef.h>
#include <stdint.h>
typedef int BaseType_t;
typedef uintptr_t portPOINTER_SIZE_TYPE;
typedef struct xHeapStats {
size_t xAvailableHeapSpaceInBytes;
size_t xSizeOfLargestFreeBlockInBytes;
size_t xSizeOfSmallestFreeBlockInBytes;
size_t xNumberOfFreeBlocks;
size_t xMinimumEverFreeBytesRemaining;
size_t xNumberOfSuccessfulAllocations;
size_t xNumberOfSuccessfulFrees;
} HeapStats_t;
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE ((size_t)4096U)
#define configAPPLICATION_ALLOCATED_HEAP 1
#define configUSE_MALLOC_FAILED_HOOK 1
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
#define configENABLE_HEAP_PROTECTOR 0
#define portBYTE_ALIGNMENT 8U
#define portBYTE_ALIGNMENT_MASK (portBYTE_ALIGNMENT - 1U)
#define portMAX_DELAY (~(size_t)0U)
#define PRIVILEGED_DATA
#define PRIVILEGED_FUNCTION
#define mtCOVERAGE_TEST_MARKER() do { } while (0)
#define traceMALLOC(pointer, size) do { (void)(pointer); (void)(size); } while (0)
#define traceFREE(pointer, size) do { (void)(pointer); (void)(size); } while (0)
#define taskENTER_CRITICAL() do { } while (0)
#define taskEXIT_CRITICAL() do { } while (0)
void heap4_lab_assert(const char *file, int line);
void vApplicationMallocFailedHook(void);
void *pvPortMalloc(size_t wanted_size);
void vPortFree(void *pointer);
size_t xPortGetFreeHeapSize(void);
size_t xPortGetMinimumEverFreeHeapSize(void);
void vPortGetHeapStats(HeapStats_t *stats);
void vPortHeapResetState(void);
#define configASSERT(condition) \
do { if (!(condition)) heap4_lab_assert(__FILE__, __LINE__); } while (0)
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef HEAP4_HOST_TASK_H
#define HEAP4_HOST_TASK_H
#include "FreeRTOS.h"
void vTaskSuspendAll(void);
BaseType_t xTaskResumeAll(void);
#endif
-61
View File
@@ -1,61 +0,0 @@
#ifndef HEAP4_COMMON_H
#define HEAP4_COMMON_H
#include <stddef.h>
#include <stdint.h>
#ifdef HOST_PRINTF
#include <stdio.h>
#define TRACE_PRINTF(...) printf(__VA_ARGS__)
#else
#define TRACE_PRINTF(...) ((void)0)
#endif
#define HEAP4_ARRAY_LEN(a) ((int)(sizeof(a) / sizeof((a)[0])))
#define HEAP4_ALIGNMENT ((size_t)8)
#define HEAP4_ALIGNMENT_MASK (HEAP4_ALIGNMENT - 1U)
#define HEAP4_ALLOCATED_BIT ((size_t)1U << (sizeof(size_t) * 8U - 1U))
typedef struct HeapBlock {
struct HeapBlock *next;
size_t size;
} HeapBlock;
static inline size_t heap4_align_up(size_t value)
{
if ((value & HEAP4_ALIGNMENT_MASK) != 0U)
value += HEAP4_ALIGNMENT - (value & HEAP4_ALIGNMENT_MASK);
return value;
}
static inline uintptr_t heap4_align_address(uintptr_t address)
{
uintptr_t mask;
mask = (uintptr_t)HEAP4_ALIGNMENT_MASK;
if ((address & mask) != 0U)
address += (uintptr_t)HEAP4_ALIGNMENT - (address & mask);
return address;
}
static inline size_t heap4_clear_allocated(size_t size)
{
return size & ~HEAP4_ALLOCATED_BIT;
}
static inline size_t heap4_mark_allocated(size_t size)
{
return size | HEAP4_ALLOCATED_BIT;
}
static inline int heap4_is_allocated(size_t size)
{
return (size & HEAP4_ALLOCATED_BIT) != 0U;
}
static inline int heap4_blocks_touch(HeapBlock *left, HeapBlock *right)
{
return (uint8_t *)left + heap4_clear_allocated(left->size) == (uint8_t *)right;
}
#endif
+25
View File
@@ -0,0 +1,25 @@
#ifndef HEAP4_MODEL_H
#define HEAP4_MODEL_H
#include <stddef.h>
#include <stdint.h>
#define MODEL_ALIGNMENT ((size_t)8U)
typedef struct ModelBlock ModelBlock;
struct ModelBlock {
ModelBlock *next;
size_t size;
};
static inline size_t model_align_up(size_t value)
{
return (value + MODEL_ALIGNMENT - 1U) & ~(MODEL_ALIGNMENT - 1U);
}
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
#endif
+72
View File
@@ -0,0 +1,72 @@
#include "heap4_model.h"
enum {
ARENA_BYTES = 256,
REQUEST_BYTES = 13,
WANTED_BYTES = (sizeof(ModelBlock) + REQUEST_BYTES + 7U) & ~7U
};
typedef struct {
ModelBlock allocated;
uint8_t payload[WANTED_BYTES - sizeof(ModelBlock)];
ModelBlock remainder;
uint8_t free_payload[ARENA_BYTES - WANTED_BYTES - sizeof(ModelBlock)];
} SplitArena;
typedef union {
uint64_t force_alignment;
SplitArena blocks;
} AlignedSplitArena;
AlignedSplitArena g_split_arena;
ModelBlock *g_free_head;
volatile size_t g_header_bytes;
volatile size_t g_wanted_bytes;
volatile size_t g_remainder_bytes;
volatile size_t g_payload_offset;
volatile int g_split_addresses_aligned;
volatile int g_task01_pass;
__attribute__((noinline)) void task01_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
SplitArena *arena = &g_split_arena.blocks;
uintptr_t base = (uintptr_t)&arena->allocated;
uintptr_t remainder = (uintptr_t)&arena->remainder;
arena->allocated.next = NULL;
arena->allocated.size = WANTED_BYTES;
arena->remainder.next = NULL;
arena->remainder.size = ARENA_BYTES - WANTED_BYTES;
arena->payload[0] = (uint8_t)'A';
g_free_head = &arena->remainder;
g_header_bytes = sizeof(ModelBlock);
g_wanted_bytes = WANTED_BYTES;
g_remainder_bytes = arena->remainder.size;
g_payload_offset = (size_t)((uintptr_t)&arena->payload[0] - base);
g_split_addresses_aligned =
(base % MODEL_ALIGNMENT) == 0U &&
(remainder % MODEL_ALIGNMENT) == 0U;
g_task01_pass =
g_wanted_bytes == model_align_up(sizeof(ModelBlock) + REQUEST_BYTES) &&
(size_t)(remainder - base) == g_wanted_bytes &&
g_remainder_bytes + g_wanted_bytes == ARENA_BYTES &&
g_payload_offset == sizeof(ModelBlock) &&
g_split_addresses_aligned;
task01_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("header=%zu wanted=%zu remainder=%zu payload=%zu aligned=%d pass=%d\n",
g_header_bytes, g_wanted_bytes, g_remainder_bytes,
g_payload_offset, g_split_addresses_aligned, g_task01_pass);
#endif
return g_task01_pass ? 0 : 1;
}
-29
View File
@@ -1,29 +0,0 @@
#include "heap4_common.h"
volatile size_t g_header_size;
volatile size_t g_aligned_header_size;
volatile size_t g_allocated_bit_set;
volatile size_t g_user_bytes;
volatile size_t g_total_block_bytes;
int main(void)
{
HeapBlock block;
size_t requested;
requested = 13U;
block.next = 0;
block.size = heap4_mark_allocated(heap4_align_up(requested + sizeof(HeapBlock)));
g_header_size = sizeof(HeapBlock);
g_aligned_header_size = heap4_align_up(sizeof(HeapBlock));
g_allocated_bit_set = heap4_is_allocated(block.size);
g_total_block_bytes = heap4_clear_allocated(block.size);
g_user_bytes = g_total_block_bytes - sizeof(HeapBlock);
TRACE_PRINTF("header=%u aligned=%u allocated=%u total=%u user=%u\n",
(unsigned)g_header_size, (unsigned)g_aligned_header_size,
(unsigned)g_allocated_bit_set, (unsigned)g_total_block_bytes,
(unsigned)g_user_bytes);
return 0;
}
+136
View File
@@ -0,0 +1,136 @@
#include "heap4_model.h"
enum { BLOCK_BYTES = 48 };
typedef struct {
ModelBlock first;
uint8_t first_payload[BLOCK_BYTES - sizeof(ModelBlock)];
ModelBlock middle;
uint8_t middle_payload[BLOCK_BYTES - sizeof(ModelBlock)];
ModelBlock last;
uint8_t last_payload[BLOCK_BYTES - sizeof(ModelBlock)];
} CoalesceArena;
typedef union {
uint64_t force_alignment;
CoalesceArena blocks;
} AlignedCoalesceArena;
AlignedCoalesceArena g_coalesce_arena;
ModelBlock *g_free_head;
volatile size_t g_before_nodes;
volatile size_t g_before_total;
volatile size_t g_before_largest;
volatile size_t g_after_nodes;
volatile size_t g_after_total;
volatile size_t g_after_largest;
volatile int g_task02_pass;
static void insert_and_coalesce(ModelBlock *block)
{
ModelBlock *previous = NULL;
ModelBlock *current = g_free_head;
while (current != NULL && (uintptr_t)current < (uintptr_t)block)
{
previous = current;
current = current->next;
}
block->next = current;
if (current != NULL &&
(uint8_t *)block + block->size == (uint8_t *)current)
{
block->size += current->size;
block->next = current->next;
}
if (previous != NULL &&
(uint8_t *)previous + previous->size == (uint8_t *)block)
{
previous->size += block->size;
previous->next = block->next;
}
else if (previous != NULL)
{
previous->next = block;
}
else
{
g_free_head = block;
}
}
static void measure(size_t *nodes, size_t *total, size_t *largest)
{
ModelBlock *block;
*nodes = 0U;
*total = 0U;
*largest = 0U;
for (block = g_free_head; block != NULL; block = block->next)
{
(*nodes)++;
*total += block->size;
if (block->size > *largest)
{
*largest = block->size;
}
}
}
__attribute__((noinline)) void task02_fragmented_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void task02_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
CoalesceArena *arena = &g_coalesce_arena.blocks;
size_t nodes;
size_t total;
size_t largest;
arena->first.size = BLOCK_BYTES;
arena->middle.size = BLOCK_BYTES;
arena->last.size = BLOCK_BYTES;
g_free_head = NULL;
insert_and_coalesce(&arena->first);
insert_and_coalesce(&arena->last);
measure(&nodes, &total, &largest);
g_before_nodes = nodes;
g_before_total = total;
g_before_largest = largest;
task02_fragmented_checkpoint();
insert_and_coalesce(&arena->middle);
measure(&nodes, &total, &largest);
g_after_nodes = nodes;
g_after_total = total;
g_after_largest = largest;
g_task02_pass =
g_before_nodes == 2U && g_before_total == 96U &&
g_before_largest == 48U && g_after_nodes == 1U &&
g_after_total == 144U && g_after_largest == 144U &&
g_free_head == &arena->first && g_free_head->next == NULL;
task02_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("before_nodes=%zu before_total=%zu before_largest=%zu "
"after_nodes=%zu after_total=%zu after_largest=%zu pass=%d\n",
g_before_nodes, g_before_total, g_before_largest,
g_after_nodes, g_after_total, g_after_largest, g_task02_pass);
#endif
return g_task02_pass ? 0 : 1;
}
-64
View File
@@ -1,64 +0,0 @@
#include "heap4_common.h"
volatile size_t g_free_total;
volatile size_t g_largest_free;
volatile int g_node_count;
static size_t list_total(HeapBlock *start)
{
HeapBlock *block;
size_t total;
total = 0U;
for (block = start->next; block != 0; block = block->next)
total += block->size;
return total;
}
static size_t list_largest(HeapBlock *start)
{
HeapBlock *block;
size_t largest;
largest = 0U;
for (block = start->next; block != 0; block = block->next)
if (block->size > largest)
largest = block->size;
return largest;
}
static int list_count(HeapBlock *start)
{
HeapBlock *block;
int count;
count = 0;
for (block = start->next; block != 0; block = block->next)
count++;
return count;
}
int main(void)
{
HeapBlock start;
HeapBlock a;
HeapBlock b;
HeapBlock c;
start.next = &a;
start.size = 0U;
a.next = &b;
a.size = 32U;
b.next = &c;
b.size = 80U;
c.next = 0;
c.size = 24U;
g_free_total = list_total(&start);
g_largest_free = list_largest(&start);
g_node_count = list_count(&start);
TRACE_PRINTF("free=%u largest=%u count=%d\n",
(unsigned)g_free_total, (unsigned)g_largest_free, g_node_count);
return 0;
}
+122
View File
@@ -0,0 +1,122 @@
#include <stddef.h>
#include <stdint.h>
#include "FreeRTOS.h"
#include "task.h"
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
uint8_t ucHeap[configTOTAL_HEAP_SIZE] __attribute__((aligned(portBYTE_ALIGNMENT)));
HeapStats_t g_fragmented_stats;
HeapStats_t g_final_stats;
volatile size_t g_initial_free;
volatile size_t g_after_allocations;
volatile size_t g_final_free;
volatile size_t g_minimum_ever_free;
volatile int g_allocations_aligned;
volatile int g_oom_is_null;
volatile int g_malloc_failed_hooks;
volatile int g_assert_failures;
volatile int g_task03_pass;
void vApplicationMallocFailedHook(void)
{
g_malloc_failed_hooks++;
}
void heap4_lab_assert(const char *file, int line)
{
(void)file;
(void)line;
g_assert_failures++;
}
#if defined(HEAP4_HOST_ADAPTER)
void vTaskSuspendAll(void)
{
}
BaseType_t xTaskResumeAll(void)
{
return 0;
}
#endif
__attribute__((noinline)) void heap4_fragmented_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
__attribute__((noinline)) void task03_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
void *probe;
void *a;
void *b;
void *c;
void *too_large;
vPortHeapResetState();
probe = pvPortMalloc(1U);
if (probe != NULL)
{
vPortFree(probe);
}
g_initial_free = xPortGetFreeHeapSize();
a = pvPortMalloc(24U);
b = pvPortMalloc(40U);
c = pvPortMalloc(16U);
g_after_allocations = xPortGetFreeHeapSize();
g_allocations_aligned =
a != NULL && b != NULL && c != NULL &&
((uintptr_t)a % portBYTE_ALIGNMENT) == 0U &&
((uintptr_t)b % portBYTE_ALIGNMENT) == 0U &&
((uintptr_t)c % portBYTE_ALIGNMENT) == 0U;
vPortFree(a);
vPortFree(c);
vPortGetHeapStats(&g_fragmented_stats);
heap4_fragmented_checkpoint();
vPortFree(b);
g_final_free = xPortGetFreeHeapSize();
g_minimum_ever_free = xPortGetMinimumEverFreeHeapSize();
vPortGetHeapStats(&g_final_stats);
too_large = pvPortMalloc(configTOTAL_HEAP_SIZE * 2U);
g_oom_is_null = too_large == NULL;
g_task03_pass =
probe != NULL && g_allocations_aligned &&
g_initial_free > g_after_allocations &&
g_final_free == g_initial_free &&
g_minimum_ever_free <= g_after_allocations &&
g_fragmented_stats.xNumberOfFreeBlocks == 2U &&
g_final_stats.xNumberOfFreeBlocks == 1U &&
g_final_stats.xSizeOfLargestFreeBlockInBytes == g_initial_free &&
g_oom_is_null && g_malloc_failed_hooks == 1 &&
g_assert_failures == 0;
task03_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("initial=%zu after=%zu fragmented=%zu final=%zu min=%zu "
"aligned=%d oom=%d hooks=%d asserts=%d pass=%d\n",
g_initial_free, g_after_allocations,
g_fragmented_stats.xNumberOfFreeBlocks, g_final_free,
g_minimum_ever_free, g_allocations_aligned, g_oom_is_null,
g_malloc_failed_hooks, g_assert_failures, g_task03_pass);
#endif
return g_task03_pass ? 0 : 1;
}
-49
View File
@@ -1,49 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 160U
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile size_t g_initial_free_size;
volatile uintptr_t g_aligned_offset;
volatile int g_end_is_last;
static void heap_init(void)
{
uintptr_t raw;
uintptr_t aligned;
size_t available;
HeapBlock *first;
raw = (uintptr_t)&heap_area[0];
aligned = heap4_align_address(raw);
available = HEAP_BYTES - (size_t)(aligned - raw);
available &= ~HEAP4_ALIGNMENT_MASK;
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + available - sizeof(HeapBlock));
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
}
int main(void)
{
heap_init();
g_aligned_offset = (uintptr_t)start.next - (uintptr_t)&heap_area[0];
g_initial_free_size = start.next->size;
g_end_is_last = (start.next->next == end_marker) && (end_marker->next == 0);
TRACE_PRINTF("offset=%u free=%u end_last=%d\n",
(unsigned)g_aligned_offset, (unsigned)g_initial_free_size,
g_end_is_last);
return 0;
}
-26
View File
@@ -1,26 +0,0 @@
#include "heap4_common.h"
volatile size_t g_request_1;
volatile size_t g_request_9;
volatile size_t g_request_17;
volatile uintptr_t g_address_delta;
int main(void)
{
uint8_t bytes[32];
uintptr_t raw;
uintptr_t aligned;
g_request_1 = heap4_align_up(1U + sizeof(HeapBlock));
g_request_9 = heap4_align_up(9U + sizeof(HeapBlock));
g_request_17 = heap4_align_up(17U + sizeof(HeapBlock));
raw = (uintptr_t)&bytes[1];
aligned = heap4_align_address(raw);
g_address_delta = aligned - raw;
TRACE_PRINTF("r1=%u r9=%u r17=%u delta=%u\n",
(unsigned)g_request_1, (unsigned)g_request_9,
(unsigned)g_request_17, (unsigned)g_address_delta);
return 0;
}
-73
View File
@@ -1,73 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 128U
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile size_t g_allocated_size;
volatile size_t g_remaining_free;
volatile int g_allocation_ok;
static void heap_init_one_block(void)
{
uintptr_t raw;
uintptr_t aligned;
HeapBlock *first;
raw = (uintptr_t)&heap_area[0];
aligned = heap4_align_address(raw);
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + 120U);
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
}
static void *heap_malloc_whole_block(size_t wanted)
{
HeapBlock *previous;
HeapBlock *block;
size_t total;
total = heap4_align_up(wanted + sizeof(HeapBlock));
previous = &start;
block = start.next;
while (block != end_marker) {
if (block->size >= total) {
previous->next = block->next;
block->next = 0;
block->size = heap4_mark_allocated(block->size);
return (uint8_t *)block + sizeof(HeapBlock);
}
previous = block;
block = block->next;
}
return 0;
}
int main(void)
{
void *p;
HeapBlock *allocated;
heap_init_one_block();
p = heap_malloc_whole_block(24U);
allocated = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
g_allocation_ok = p != 0;
g_allocated_size = heap4_clear_allocated(allocated->size);
g_remaining_free = start.next->size;
TRACE_PRINTF("ok=%d allocated=%u remaining=%u\n",
g_allocation_ok, (unsigned)g_allocated_size,
(unsigned)g_remaining_free);
return 0;
}
-93
View File
@@ -1,93 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 192U
#define MIN_SPLIT_SIZE ((size_t)(sizeof(HeapBlock) * 2U))
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile size_t g_allocated_size;
volatile size_t g_split_free_size;
volatile int g_free_nodes;
static void heap_init_one_block(void)
{
uintptr_t aligned;
HeapBlock *first;
aligned = heap4_align_address((uintptr_t)&heap_area[0]);
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + 176U);
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
}
static void *heap_malloc_split(size_t wanted)
{
HeapBlock *previous;
HeapBlock *block;
HeapBlock *remainder;
size_t total;
size_t original;
total = heap4_align_up(wanted + sizeof(HeapBlock));
previous = &start;
block = start.next;
while (block != end_marker) {
if (block->size >= total) {
original = block->size;
if (original - total > MIN_SPLIT_SIZE) {
remainder = (HeapBlock *)((uint8_t *)block + total);
remainder->size = original - total;
remainder->next = block->next;
previous->next = remainder;
block->size = total;
} else {
previous->next = block->next;
}
block->next = 0;
block->size = heap4_mark_allocated(block->size);
return (uint8_t *)block + sizeof(HeapBlock);
}
previous = block;
block = block->next;
}
return 0;
}
static int count_free_nodes(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
int main(void)
{
void *p;
HeapBlock *allocated;
heap_init_one_block();
p = heap_malloc_split(32U);
allocated = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
g_allocated_size = heap4_clear_allocated(allocated->size);
g_split_free_size = start.next->size;
g_free_nodes = count_free_nodes();
TRACE_PRINTF("allocated=%u split=%u nodes=%d\n",
(unsigned)g_allocated_size, (unsigned)g_split_free_size,
g_free_nodes);
return 0;
}
-88
View File
@@ -1,88 +0,0 @@
#include "heap4_common.h"
static uint8_t heap_area[192];
static HeapBlock start;
static HeapBlock *end_marker;
volatile int g_order_ok;
volatile int g_free_count;
volatile size_t g_total_free;
static void insert_block(HeapBlock *block)
{
HeapBlock *iterator;
for (iterator = &start; iterator->next < block; iterator = iterator->next)
;
block->next = iterator->next;
iterator->next = block;
}
static void heap_free_model(void *p)
{
HeapBlock *block;
if (p == 0)
return;
block = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
block->size = heap4_clear_allocated(block->size);
insert_block(block);
}
static int count_free(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
static size_t total_free(void)
{
HeapBlock *block;
size_t total;
total = 0U;
for (block = start.next; block != end_marker; block = block->next)
total += block->size;
return total;
}
int main(void)
{
HeapBlock *a;
HeapBlock *b;
HeapBlock *c;
void *payload;
a = (HeapBlock *)&heap_area[0];
b = (HeapBlock *)&heap_area[64];
c = (HeapBlock *)&heap_area[128];
end_marker = (HeapBlock *)&heap_area[176];
start.next = a;
a->next = c;
c->next = end_marker;
end_marker->next = 0;
a->size = 40U;
b->size = heap4_mark_allocated(32U);
c->size = 48U;
end_marker->size = 0U;
payload = (uint8_t *)b + sizeof(HeapBlock);
heap_free_model(payload);
g_order_ok = start.next == a && a->next == b && b->next == c && c->next == end_marker;
g_free_count = count_free();
g_total_free = total_free();
TRACE_PRINTF("order=%d count=%d total=%u\n",
g_order_ok, g_free_count, (unsigned)g_total_free);
return 0;
}
-76
View File
@@ -1,76 +0,0 @@
#include "heap4_common.h"
static uint8_t heap_area[192];
static HeapBlock start;
static HeapBlock *end_marker;
volatile int g_free_count;
volatile size_t g_merged_size;
volatile int g_merged_with_right;
static void insert_block_merge(HeapBlock *block)
{
HeapBlock *iterator;
uint8_t *block_end;
for (iterator = &start; iterator->next < block; iterator = iterator->next)
;
block_end = (uint8_t *)block + block->size;
if (block_end == (uint8_t *)iterator->next) {
block->size += iterator->next->size;
block->next = iterator->next->next;
} else {
block->next = iterator->next;
}
if (iterator != &start && heap4_blocks_touch(iterator, block)) {
iterator->size += block->size;
iterator->next = block->next;
} else {
iterator->next = block;
}
}
static int count_free(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
int main(void)
{
HeapBlock *left;
HeapBlock *middle;
HeapBlock *right;
left = (HeapBlock *)&heap_area[0];
middle = (HeapBlock *)&heap_area[48];
right = (HeapBlock *)&heap_area[96];
end_marker = (HeapBlock *)&heap_area[160];
start.next = left;
left->next = right;
right->next = end_marker;
end_marker->next = 0;
left->size = 48U;
middle->size = 48U;
right->size = 64U;
end_marker->size = 0U;
insert_block_merge(middle);
g_free_count = count_free();
g_merged_size = start.next->size;
g_merged_with_right = start.next->next == end_marker;
TRACE_PRINTF("count=%d size=%u merged_right=%d\n",
g_free_count, (unsigned)g_merged_size, g_merged_with_right);
return 0;
}
-34
View File
@@ -1,34 +0,0 @@
#include "heap4_common.h"
#ifndef CONFIG_TOTAL_HEAP_SIZE
#define CONFIG_TOTAL_HEAP_SIZE 256U
#endif
#ifndef CONFIG_SUPPORT_DYNAMIC_ALLOCATION
#define CONFIG_SUPPORT_DYNAMIC_ALLOCATION 1
#endif
#define CONFIG_ADJUSTED_HEAP_SIZE (CONFIG_TOTAL_HEAP_SIZE - HEAP4_ALIGNMENT)
volatile size_t g_total_heap;
volatile size_t g_adjusted_heap;
volatile int g_dynamic_enabled;
volatile int g_trace_is_host_only;
int main(void)
{
g_total_heap = CONFIG_TOTAL_HEAP_SIZE;
g_adjusted_heap = CONFIG_ADJUSTED_HEAP_SIZE;
g_dynamic_enabled = CONFIG_SUPPORT_DYNAMIC_ALLOCATION;
#ifdef HOST_PRINTF
g_trace_is_host_only = 1;
#else
g_trace_is_host_only = 0;
#endif
TRACE_PRINTF("heap=%u adjusted=%u dynamic=%d host_trace=%d\n",
(unsigned)g_total_heap, (unsigned)g_adjusted_heap,
g_dynamic_enabled, g_trace_is_host_only);
return 0;
}
-175
View File
@@ -1,175 +0,0 @@
#include "heap4_common.h"
#define HEAP_BYTES 256U
#define MIN_SPLIT_SIZE ((size_t)(sizeof(HeapBlock) * 2U))
static uint8_t heap_area[HEAP_BYTES + HEAP4_ALIGNMENT];
static HeapBlock start;
static HeapBlock *end_marker;
volatile int g_allocations_ok;
volatile int g_final_free_nodes;
volatile size_t g_final_free_total;
volatile size_t g_minimum_ever_free;
static size_t free_bytes_remaining;
static size_t minimum_ever_free;
static size_t list_total(void)
{
HeapBlock *block;
size_t total;
total = 0U;
for (block = start.next; block != end_marker; block = block->next)
total += block->size;
return total;
}
static int list_count(void)
{
HeapBlock *block;
int count;
count = 0;
for (block = start.next; block != end_marker; block = block->next)
count++;
return count;
}
static void insert_block_merge(HeapBlock *block)
{
HeapBlock *iterator;
uint8_t *block_end;
for (iterator = &start; iterator->next < block; iterator = iterator->next)
;
block_end = (uint8_t *)block + block->size;
if (block_end == (uint8_t *)iterator->next) {
block->size += iterator->next->size;
block->next = iterator->next->next;
} else {
block->next = iterator->next;
}
if (iterator != &start && heap4_blocks_touch(iterator, block)) {
iterator->size += block->size;
iterator->next = block->next;
} else {
iterator->next = block;
}
}
static void heap_init(void)
{
uintptr_t raw;
uintptr_t aligned;
size_t available;
HeapBlock *first;
raw = (uintptr_t)&heap_area[0];
aligned = heap4_align_address(raw);
available = HEAP_BYTES - (size_t)(aligned - raw);
available &= ~HEAP4_ALIGNMENT_MASK;
first = (HeapBlock *)aligned;
end_marker = (HeapBlock *)(aligned + available - sizeof(HeapBlock));
end_marker->next = 0;
end_marker->size = 0U;
first->next = end_marker;
first->size = (uint8_t *)end_marker - (uint8_t *)first;
start.next = first;
start.size = 0U;
free_bytes_remaining = first->size;
minimum_ever_free = free_bytes_remaining;
}
static void *heap_malloc_model(size_t wanted)
{
HeapBlock *previous;
HeapBlock *block;
HeapBlock *remainder;
size_t total;
size_t original;
if (wanted == 0U)
return 0;
total = heap4_align_up(wanted + sizeof(HeapBlock));
previous = &start;
block = start.next;
while (block != end_marker) {
if (block->size >= total) {
original = block->size;
if (original - total > MIN_SPLIT_SIZE) {
remainder = (HeapBlock *)((uint8_t *)block + total);
remainder->size = original - total;
remainder->next = block->next;
previous->next = remainder;
block->size = total;
} else {
previous->next = block->next;
total = original;
}
block->next = 0;
block->size = heap4_mark_allocated(block->size);
free_bytes_remaining -= total;
if (free_bytes_remaining < minimum_ever_free)
minimum_ever_free = free_bytes_remaining;
return (uint8_t *)block + sizeof(HeapBlock);
}
previous = block;
block = block->next;
}
return 0;
}
static void heap_free_model(void *p)
{
HeapBlock *block;
size_t size;
if (p == 0)
return;
block = (HeapBlock *)((uint8_t *)p - sizeof(HeapBlock));
if (!heap4_is_allocated(block->size))
return;
size = heap4_clear_allocated(block->size);
block->size = size;
free_bytes_remaining += size;
insert_block_merge(block);
}
int main(void)
{
void *a;
void *b;
void *c;
heap_init();
a = heap_malloc_model(24U);
b = heap_malloc_model(40U);
heap_free_model(a);
c = heap_malloc_model(16U);
heap_free_model(b);
heap_free_model(c);
g_allocations_ok = a != 0 && b != 0 && c != 0;
g_final_free_nodes = list_count();
g_final_free_total = list_total();
g_minimum_ever_free = minimum_ever_free;
TRACE_PRINTF("ok=%d nodes=%d free=%u min=%u\n",
g_allocations_ok, g_final_free_nodes,
(unsigned)g_final_free_total, (unsigned)g_minimum_ever_free);
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
schema: 1
targets:
native:
profile: native-amd64
actions: [build, test, run, debug]
hazard3-baremetal:
profile: hazard3-sim
actions: [build, test, run, debug]
rp2350-rv:
profile: rp2350
actions: [build, test, deploy, debug]
actions:
build: [bash, tools/card-action.sh, build]
test: [bash, tools/card-action.sh, test]
run: [bash, tools/card-action.sh, run]
deploy: [bash, tools/card-action.sh, deploy]
debug: [bash, tools/card-action.sh, debug]
artifacts:
directory: .stem/artifacts
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
elf="${1:?usage: check_task03_elf.sh ELF [tool-prefix]}"
prefix="${2:-riscv64-unknown-elf-}"
nm="${prefix}nm"
objdump="${prefix}objdump"
test -s "$elf"
for symbol in pvPortMalloc vPortFree prvInsertBlockIntoFreeList ucHeap \
heap4_fragmented_checkpoint task03_debug_checkpoint g_task03_pass; do
"$nm" -a "$elf" | grep -E "[[:space:]]${symbol}$" >/dev/null
done
"$objdump" -d "$elf" | grep -E 'addi[[:space:]]+sp,sp,-' >/dev/null
printf 'PASS Task 3 links upstream heap_4 and observable checkpoints: %s\n' "$elf"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
make -C "$root" PROFILE=observe host >/dev/null
check_output() {
local program="$1" expected="$2" actual
actual="$($program)"
if [[ "$actual" != "$expected" ]]; then
printf 'FAIL %s\nexpected: %s\nactual: %s\n' "$program" "$expected" "$actual" >&2
return 1
fi
printf 'PASS %s: %s\n' "$(basename -- "$(dirname -- "$program")")" "$actual"
}
check_output "$root/host-build/task01_first_fit_split/prog" \
'header=16 wanted=32 remainder=224 payload=16 aligned=1 pass=1'
check_output "$root/host-build/task02_address_order_coalesce/prog" \
'before_nodes=2 before_total=96 before_largest=48 after_nodes=1 after_total=144 after_largest=144 pass=1'
check_output "$root/host-build/task03_freertos_heap4/prog" \
'initial=4080 after=3952 fragmented=2 final=4080 min=3952 aligned=1 oom=1 hooks=1 asserts=0 pass=1'
+358
View File
@@ -0,0 +1,358 @@
#!/usr/bin/env bash
set -euo pipefail
action="${1:?action is required}"
root="${STEM_REPO:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)}"
profile="${STEM_PROFILE:-native-amd64}"
target="${STEM_TARGET:-native}"
selector="${STEM_TASK:-task3}"
state="${STEM_STATE_DIR:-$root/.stem/instances/local}"
tasks=(
task01_first_fit_split
task02_address_order_coalesce
task03_freertos_heap4
)
resolve_task() {
local value="${1,,}" digits number stem
for stem in "${tasks[@]}"; do
if [[ "$value" == "$stem" ]]; then
printf '%s\n' "$stem"
return
fi
done
if [[ "$value" =~ ^(task|t)?[-_]?0*([1-3])($|[-_].*) ]]; then
digits="${BASH_REMATCH[2]}"
number=$((10#$digits))
printf '%s\n' "${tasks[number - 1]}"
return
fi
printf 'Unknown task selector: %s\n' "$1" >&2
exit 2
}
task_stem="$(resolve_task "$selector")"
task_digits="${task_stem#task}"
task_digits="${task_digits%%_*}"
task_number=$((10#$task_digits))
task_key="$(printf 'task%02d' "$task_number")"
source_file="$root/src/tasks/$task_stem.c"
artifact_dir="$root/.stem/artifacts/$profile/$target/$task_key"
mkdir -p "$state" "$artifact_dir"
freertos_kernel_path() {
if [[ -n "${FREERTOS_KERNEL_PATH:-}" ]]; then
printf '%s\n' "$FREERTOS_KERNEL_PATH"
elif [[ -f /opt/FreeRTOS-Kernel/portable/MemMang/heap_4.c ]]; then
printf '%s\n' /opt/FreeRTOS-Kernel
else
printf '%s\n' "$root/../lab-rv32i-freertos-task-stack-vector/vendor/FreeRTOS-Kernel"
fi
}
verify_kernel() {
local kernel="$1"
test -f "$kernel/portable/MemMang/heap_4.c"
grep -F 'FreeRTOS Kernel V11.3.0' "$kernel/include/FreeRTOS.h" >/dev/null
}
freertos_rp2350_path() {
if [[ -n "${FREERTOS_RP2350_PATH:-}" ]]; then
printf '%s\n' "$FREERTOS_RP2350_PATH"
else
printf '%s\n' /opt/FreeRTOS-RP2350
fi
}
verify_rp2350_kernel() {
local kernel="$1"
test -f "$kernel/portable/ThirdParty/GCC/RP2350_RISC-V/portASM.S"
grep -F '<DEVELOPMENT BRANCH>' "$kernel/include/FreeRTOS.h" >/dev/null
}
copy_artifact() {
local path="$1"
[[ -f "$path" ]] && cp -f "$path" "$artifact_dir/"
}
build_native() {
local mode="${1:-normal}" kernel build_dir output cc="${CC:-cc}"
local flags=(-std=c11 -O0 -g3 -fno-omit-frame-pointer -fno-inline
-fno-optimize-sibling-calls -Wall -Wextra -Wpedantic
-DHOST_PRINTF=1 -I"$root/src/tasks")
build_dir="$state/build/native/$task_stem"
output="$build_dir/prog"
if [[ "$mode" == sanitizer ]]; then
flags+=(-fsanitize=address,undefined)
fi
mkdir -p "$build_dir"
if [[ "$task_number" -eq 3 ]]; then
kernel="$(freertos_kernel_path)"
verify_kernel "$kernel"
flags+=(-DHEAP4_HOST_ADAPTER=1 -I"$root/include/host-shim")
"$cc" "${flags[@]}" "$source_file" \
"$kernel/portable/MemMang/heap_4.c" -o "$output"
else
"$cc" "${flags[@]}" "$source_file" -o "$output"
fi
objdump -dS "$output" > "$build_dir/prog.lst"
cp -f "$output" "$artifact_dir/prog"
copy_artifact "$build_dir/prog.lst"
printf '%s\n' "$output"
}
rv_env_root() {
if [[ -n "${RV_ENV_ROOT:-}" ]]; then
printf '%s\n' "$RV_ENV_ROOT"
elif [[ -d /opt/rv-env ]]; then
printf '%s\n' /opt/rv-env
else
printf '%s\n' /opt/stem
fi
}
build_hazard3() {
local env_root kernel build_dir elf
env_root="$(rv_env_root)"
kernel="$(freertos_kernel_path)"
verify_kernel "$kernel"
build_dir="$state/build/hazard3"
make -B -C "$root" "task$task_number" \
"BUILD_ROOT=$build_dir" "RV_ENV_ROOT=$env_root" \
"FREERTOS_KERNEL_PATH=$kernel" PROFILE=observe >&2 || return
elf="$build_dir/$task_stem/prog.elf"
test -s "$elf"
copy_artifact "$elf"
copy_artifact "${elf%.elf}.bin"
copy_artifact "${elf%.elf}.lst"
copy_artifact "${elf%.elf}.map"
printf '%s\n' "$elf"
}
hazard3_tb() {
local env_root tb
env_root="$(rv_env_root)"
tb="$env_root/vendor/Hazard3/test/sim/tb_verilator/tb"
if [[ ! -x "$tb" ]]; then
make -C "$(dirname -- "$tb")" tb >&2
fi
printf '%s\n' "$tb"
}
build_rp2350() {
local binary_type="${1:-no_flash}" build_kind build_dir elf kernel rp_kernel
local cmake_args
case "$binary_type" in
no_flash) build_kind=ram ;;
default) build_kind=flash ;;
*) printf 'Unsupported RP2350 binary type: %s\n' "$binary_type" >&2; return 2 ;;
esac
kernel="$(freertos_kernel_path)"
verify_kernel "$kernel"
rp_kernel="$(freertos_rp2350_path)"
verify_rp2350_kernel "$rp_kernel"
build_dir="$state/build/rp2350-rv-$build_kind"
cmake_args=(
-S "$root" -B "$build_dir"
-DPICO_PLATFORM=rp2350-riscv
-DPICO_BOARD="${PICO_BOARD:-pico2_w}"
-DPICO_GCC_TRIPLE=riscv32-pico-elf
-DHEAP4_UPSTREAM_ROOT="$kernel"
-DFREERTOS_RP2350_PATH="$rp_kernel"
-DHEAP4_PROFILE=observe
-DHEAP4_BINARY_TYPE="$binary_type"
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
)
if command -v ninja >/dev/null 2>&1; then
cmake_args+=(-G Ninja)
else
cmake_args+=(-G "Unix Makefiles")
fi
if [[ -d /opt/picotool/picotool ]]; then
cmake_args+=(-Dpicotool_DIR=/opt/picotool/picotool)
fi
cmake "${cmake_args[@]}" >&2
cmake --build "$build_dir" --target "$task_stem" >&2
elf="$build_dir/$task_stem.elf"
test -s "$elf"
riscv64-unknown-elf-objdump -dS -M no-aliases,numeric "$elf" \
> "$build_dir/$task_stem.lst"
copy_artifact "$elf"
copy_artifact "$build_dir/$task_stem.bin"
copy_artifact "$build_dir/$task_stem.hex"
copy_artifact "$build_dir/$task_stem.uf2"
copy_artifact "$build_dir/$task_stem.dis"
copy_artifact "$build_dir/$task_stem.lst"
copy_artifact "$build_dir/$task_stem.elf.map"
printf '%s\n' "$elf"
}
openocd_target="${STEM_OPENOCD_TARGET:-target/rp2350.cfg}"
gdb_port="${GDB_PORT:-3333}"
gdb_target="${GDB_TARGET:-127.0.0.1:$gdb_port}"
openocd_pid=""
stop_owned_openocd() {
if [[ -n "$openocd_pid" ]]; then
kill "$openocd_pid" 2>/dev/null || true
wait "$openocd_pid" 2>/dev/null || true
fi
}
start_openocd() {
local log="$state/openocd-rp2350.log" attempt
if nc -z 127.0.0.1 "$gdb_port" 2>/dev/null; then
return
fi
openocd -f interface/cmsis-dap.cfg \
-c "adapter speed ${STEM_ADAPTER_KHZ:-5000}" \
-c 'set USE_CORE { rv0 }' -c 'set USE_SMP 0' \
-c "set FLASHSIZE ${STEM_RP2350_FLASH_SIZE:-0x400000}" \
-f "$openocd_target" >"$log" 2>&1 &
openocd_pid=$!
for attempt in $(seq 1 80); do
if nc -z 127.0.0.1 "$gdb_port" 2>/dev/null; then
return
fi
if ! kill -0 "$openocd_pid" 2>/dev/null; then
sed -n '1,240p' "$log" >&2
return 1
fi
sleep 0.1
done
printf 'OpenOCD did not open GDB port %s\n' "$gdb_port" >&2
return 1
}
flash_rp2350() {
local elf="$1" log="$state/openocd-rp2350-flash.log"
if nc -z 127.0.0.1 "$gdb_port" 2>/dev/null; then
printf 'GDB port %s is busy; stop the current debug session before deploy.\n' "$gdb_port" >&2
return 1
fi
if ! openocd -f interface/cmsis-dap.cfg \
-c "adapter speed ${STEM_ADAPTER_KHZ:-5000}" \
-c 'set USE_CORE { cm0 rv0 }' -c 'set USE_SMP 0' \
-c "set FLASHSIZE ${STEM_RP2350_FLASH_SIZE:-0x400000}" \
-f "$openocd_target" -c init \
-c 'if {[target_present rp2350.rv0]} { targets rp2350.rv0 } else { targets rp2350.cm0 }' \
-c 'reset halt' -c "flash write_image erase $elf" \
-c "verify_image $elf" -c 'reset run' -c shutdown >"$log" 2>&1; then
sed -n '1,260p' "$log" >&2
return 1
fi
grep -E 'wrote [0-9]+ bytes|verified [0-9]+ bytes' "$log" || true
printf 'rp2350-flash-ok image=%s verify=passed architecture=RISC-V\n' "$elf"
}
hardware_test_task03() {
local elf="$1" output
start_openocd
trap stop_owned_openocd EXIT
output="$(gdb-multiarch -nx -q -batch "$elf" \
-ex 'set architecture riscv:rv32' \
-ex "target extended-remote $gdb_target" \
-ex 'monitor reset halt' \
-ex 'hbreak heap4_fragmented_checkpoint' \
-ex 'hbreak task03_debug_checkpoint' \
-ex continue \
-ex 'printf "fragmented blocks=%lu free=%lu largest=%lu\n", g_fragmented_stats.xNumberOfFreeBlocks, g_fragmented_stats.xAvailableHeapSpaceInBytes, g_fragmented_stats.xSizeOfLargestFreeBlockInBytes' \
-ex continue \
-ex 'printf "task03 initial=%lu after=%lu final=%lu min=%lu aligned=%d oom=%d hooks=%d asserts=%d pass=%d\n", g_initial_free, g_after_allocations, g_final_free, g_minimum_ever_free, g_allocations_aligned, g_oom_is_null, g_malloc_failed_hooks, g_assert_failures, g_task03_pass' \
-ex 'printf "frame sp=0x%08lx fp=0x%08lx pc=0x%08lx align=%lu\n", (unsigned long)$sp, (unsigned long)$s0, (unsigned long)$pc, ((unsigned long)$sp & 15)' \
-ex 'x/96bx ucHeap' \
-ex 'delete breakpoints' -ex 'monitor halt' -ex disconnect)"
printf '%s\n' "$output"
grep -E '^fragmented blocks=2 ' <<<"$output" >/dev/null
grep -E '^task03 .* aligned=1 oom=1 hooks=1 asserts=0 pass=1$' <<<"$output" >/dev/null
grep -E '^frame .* align=0$' <<<"$output" >/dev/null
printf 'rp2350-hardware-ok task=task03 heap_4=upstream load=flash\n'
}
configure_debug_view() {
export STEM_DEBUG_WINDOW_NAME=heap4
case "$task_number" in
1)
export STEM_DEBUG_INITIAL_BREAK=task01_debug_checkpoint
export STEM_DEBUG_MEMORY_EXPR='&g_split_arena' STEM_DEBUG_MEMORY_LENGTH=288
;;
2)
export STEM_DEBUG_INITIAL_BREAK=task02_fragmented_checkpoint
export STEM_DEBUG_MEMORY_EXPR='&g_coalesce_arena' STEM_DEBUG_MEMORY_LENGTH=160
;;
3)
export STEM_DEBUG_INITIAL_BREAK=heap4_fragmented_checkpoint
export STEM_DEBUG_MEMORY_EXPR='ucHeap' STEM_DEBUG_MEMORY_LENGTH=160
;;
esac
}
case "$profile:$target:$action" in
native-amd64:native:build)
build_native normal >/dev/null
;;
native-amd64:native:test)
output="$(build_native sanitizer)"
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=halt_on_error=1 "$output"
;;
native-amd64:native:run)
output="$(build_native normal)"
exec "$output"
;;
native-amd64:native:debug)
output="$(build_native normal)"
export RV_REPO="$root" RV_EDITOR="${STEM_EDITOR:-nvim}"
export RV_HOST_ELF="$output" RV_HOST_SRC="$source_file"
export RV_HOST_LST="$(dirname -- "$output")/prog.lst"
exec /opt/stem/scripts/host-entry.sh debug "task$task_number"
;;
hazard3-sim:hazard3-baremetal:build)
build_hazard3 >/dev/null
;;
hazard3-sim:hazard3-baremetal:test|hazard3-sim:hazard3-baremetal:run)
elf="$(build_hazard3)"
tb="$(hazard3_tb)"
exec "$tb" --bin "${elf%.elf}.bin" --cpuret
;;
hazard3-sim:hazard3-baremetal:debug)
elf="$(build_hazard3)"
configure_debug_view
export STEM_DEBUG_BACKEND=hazard3 STEM_DEBUG_ELF="$elf"
export STEM_DEBUG_BIN="${elf%.elf}.bin" STEM_DEBUG_SRC="$source_file"
export STEM_DEBUG_LST="${elf%.elf}.lst"
exec /opt/stem/scripts/card-debug-ui.sh
;;
rp2350:rp2350-rv:build)
build_rp2350 >/dev/null
;;
rp2350:rp2350-rv:test)
elf="$(build_rp2350)"
riscv64-unknown-elf-readelf -h "$elf" | grep -F 'Machine:' | grep -F 'RISC-V'
if [[ "$task_number" -eq 3 ]]; then
"$root/tests/check_task03_elf.sh" "$elf"
fi
printf 'rp2350-build-ok task=%s binary_type=no_flash\n' "$task_key"
;;
rp2350:rp2350-rv:deploy)
elf="$(build_rp2350 default)"
flash_rp2350 "$elf"
if [[ "$task_number" -eq 3 ]]; then
hardware_test_task03 "$elf"
else
printf 'rp2350-deploy-ok task=%s load=flash persistent=1\n' "$task_key"
fi
;;
rp2350:rp2350-rv:debug)
elf="$(build_rp2350)"
configure_debug_view
export STEM_DEBUG_BACKEND=rp2350 STEM_DEBUG_ELF="$elf"
export STEM_DEBUG_SRC="$source_file" STEM_DEBUG_LST="${elf%.elf}.lst"
exec /opt/stem/scripts/card-debug-ui.sh
;;
*)
printf 'Unsupported profile/target/action: %s/%s/%s\n' "$profile" "$target" "$action" >&2
exit 2
;;
esac