Работа с динамической памятью

При выполнении команды O7ARMv{6,7}Linker.Link в рабочий журнал будет выведено, сколько полученная программа потребляет ROM и RAM. Например, для программы из первого урока требуется 880 байт ROM и 16 байт RAM.

Доступная память RAM используется под следующие ресурсы:

Куча используется только при динамическом выделении памяти через команду NEW, и она заполняется снизу вверх. Для сборки мусора в куче надо вызывать команду MicroGC0.Collect. Количество выделенной динамической памяти доступно в переменной MicroKernel0.allocated, начало кучи — MicroKernel0.heapOrg, конец — MicroKernel0.heapLim. Следовательно общий размер легко вычислить как их разность.

Стек заполняет выделенную под него память сверху вниз. И когда этот объем полностью заполняется, он начинает расти поверх кучи. Поэтому большие переменные рекомендуется размещать либо глобально, либо в куче, выход за границы которой контролируется автоматически. При переполнении кучи указатель будет равен NIL после вызова NEW.

Размер памяти, выделяемой под стек, определяется константой StkSize в модуле System для конкретного контроллера. Например, для STM32F4 это 8192 байта, для STM32F103x{8,B} — 2048 байт. Это значение вы можете легко изменить исходя из требований вашего проекта. Если вы используете рекурсивные алгоритмы, то скорее всего вам потребуется зарезервировать больше места под стек.

Степень заполнение стека возможно вычислить, исходя из положения начала и его текущего положения:

SYSTEM.GET(SYSTEM.REG(MT), stkOrg); (* MT = 6 *)
SYSTEM.GET(SYSTEM.REG(SP), stkPos); (* SP = 13 *)
stkLen := stkOrg - stkPos;

Так возможно контролировать при отладке, не превышает ли stkLen значение StkSize

Переполнение стека возможно обнаружить и более простым путем, сравнив stkPos с MicroKernel0.heapLim. Если он меньше, значит стек уже начал писаться в область памяти, выделенной для кучи. Так возможно добавить ASSERT для аварийной остановки в случае переполнения стека. Про обработку аварийных остановок читайте заметку про отладку.

Тестирование сборщика мусора

Александр Ширяев разработал модуль для тестирования сборщика мусора:

testgc0.odc

MODULE TmpTestGC0;

	(*
		Alexander Shiryaev, 2019.10

		Dynamic memory (and garbage collection) test

		Board: Nucleo-F446RE
			PA2: UART TX
			PA3: UART RX
			PA5: LED
	*)

	IMPORT
		SYSTEM,
		ARMv7M := MicroARMv7M,
		Traps := MicroARMv7MTraps,
		Kernel := MicroKernel0,
		GC := MicroGC0,
		TPorts := MicroSTM32F4TPorts,
		Pins := MicroSTM32F4Pins,
		Sys := MicroSTM32F405System,
		SysTick0 := DplaARMv7MSTM32SysTick0,
		WWDG := MicroARMv7MSTM32F4WWDG;

	CONST
		enableLED = TRUE;

		led0PinPort = Pins.A;
		led0PinN = 5;
		led0BSRR = Pins.GPIOABSRR;

		freq0 = 128; (* Hz *)

	TYPE
		Operation = POINTER TO OperationDesc;
		OperationDesc = RECORD
		END;
		Repeat = POINTER TO RepeatDesc;
		RepeatDesc = RECORD (OperationDesc)
			value: INTEGER
		END;
		End = POINTER TO EndDesc;
		EndDesc = RECORD (OperationDesc)
		END;

	VAR
		cnt0: INTEGER;
		operations: ARRAY 100 OF Operation;
		nOp: INTEGER;
		port: TPorts.Port;

	PROCEDURE LEDOn;
	BEGIN
		SYSTEM.PUT(led0BSRR, {led0PinN})
	END LEDOn;

	PROCEDURE LEDOff;
	BEGIN
		SYSTEM.PUT(led0BSRR, {led0PinN+16})
	END LEDOff;

	PROCEDURE InitOperations;
		VAR i: INTEGER;
	BEGIN
		i := 0;
		WHILE i < LEN(operations) DO
			operations[i] := NIL;
			INC(i)
		END;
		GC.Collect;
		nOp := 0
	END InitOperations;

	PROCEDURE AddRepeat (val: INTEGER);
		VAR repeat: Repeat;
	BEGIN
		repeat := NIL;
		NEW(repeat);
		repeat.value := val;
		operations[nOp] := repeat;
		INC(nOp)
	END AddRepeat;

	PROCEDURE AddEnd;
		VAR end: Repeat;
	BEGIN
		end := NIL;
		NEW(end);
		INC(nOp)
	END AddEnd;

	PROCEDURE PopulateOperations;
		VAR i: INTEGER;
	BEGIN
		i := 0;
		WHILE i < 10 DO
			AddRepeat(i);
			INC(i)
		END;
		i := 0;
		WHILE i < 5 DO
			AddEnd;
			INC(i)
		END
	END PopulateOperations;

	PROCEDURE Receive (id: CHAR; a: ARRAY OF CHAR; len: INTEGER);
		VAR ok: BOOLEAN;
			i: INTEGER;
	BEGIN
		IF id = 20X (* communication test *) THEN
			TPorts.Send(port, 21X, SYSTEM.ADR(a), len, ok)
		ELSIF id = 22X (* clear dynamic memory *) THEN
			IF len = 0 THEN
				InitOperations;
				TPorts.Send(port, 23X, 0, 0, ok)
			END
		ELSIF id = 24X (* populate dynamic memory *) THEN
			IF len = 0 THEN
				PopulateOperations;
				TPorts.Send(port, 25X, 0, 0, ok)
			END
		ELSIF id = 26X (* Kernel.allocated query *) THEN
			IF len = 0 THEN
				TPorts.Send(port, 27X, SYSTEM.ADR(Kernel.allocated), 4, ok)
			END
		ELSIF id = 28X (* TRAP test *) THEN
			IF len = 0 THEN
				ASSERT(FALSE)
			END
		ELSIF id = 2AX (* hangup test *) THEN
			IF len = 0 THEN
				REPEAT UNTIL FALSE
			END
		ELSIF id = 2CX (* multiple clear/populate calls *) THEN
			IF len = 0 THEN
				i := 1000;
				REPEAT DEC(i);
					InitOperations;
					PopulateOperations;
					WWDG.Update
				UNTIL i = 0;
				TPorts.Send(port, 2DX, 0, 0, ok)
			END
		END
	END Receive;

	PROCEDURE OnFreq0;
	BEGIN
		IF enableLED THEN
			(* LED blink *)
				INC(cnt0);
				IF cnt0 MOD (freq0 DIV 4) = 0 THEN
					LEDOn
				ELSIF cnt0 MOD (freq0 DIV 4) = freq0 DIV 8 THEN
					LEDOff
				END
		END;

		WWDG.Update (* reset watchdog timer *)
	END OnFreq0;

	PROCEDURE MainLoop;
	BEGIN
		REPEAT
			TPorts.Receive(port);
			IF SysTick0.OnTimer() THEN
				OnFreq0
			END;
			ARMv7M.WFI
		UNTIL FALSE
	END MainLoop;

	PROCEDURE Init;
		VAR ok: BOOLEAN;
			i: INTEGER;

		PROCEDURE InitPort;
			VAR p: TPorts.InitPar;
		BEGIN
			p.n := TPorts.USART2;
			p.RXPinPort := Pins.A;
				p.RXPinN := 3;
				p.RXPinAF := Pins.AF7;
			p.TXPinPort := Pins.A;
				p.TXPinN := 2;
				p.TXPinAF := Pins.AF7;
			p.UCLK := Sys.PCLK1;
			p.baud := 115200;
			p.parity := TPorts.parityNone;
			p.receive := Receive;
			p.version1 := 1;
			TPorts.Init(port, p)
		END InitPort;

		PROCEDURE InitLED;
		BEGIN
			Pins.Configure(led0PinPort, led0PinN,
				Pins.output, Pins.pushPull, Pins.low, Pins.noPull, 0)
		END InitLED;

	BEGIN
		InitPort; (* initialize communication port *)
		IF enableLED THEN
			InitLED; LEDOff; cnt0 := 0 (* initialize LED and related logic *)
		END;
		WWDG.Init(WWDG.WDGTBMax, WWDG.WMax, WWDG.TMax); (* initialize watchdog timer *)
		SysTick0.Init(Sys.HCLK, freq0); (* initialize periodic timer *)

		IF Traps.trapFlag THEN Traps.ClearTrapFlag;
			i := 3;
			REPEAT DEC(i);
				TPorts.Send(port, 0DEX, SYSTEM.ADR(Traps.trap), 9, ok)
			UNTIL i = 0
		ELSE
			i := 3;
			REPEAT DEC(i);
				TPorts.Send(port, 0CBX, 0, 0, ok)
			UNTIL i = 0
		END
	END Init;

BEGIN
	Init;
	MainLoop
END TmpTestGC0.
O7ARMv7MLinker.Link STM32F446RE TmpTestGC0

Автор заметки: И.А. Денисов