Application of the Memory Block Manager¶
The functions of this library (Memory Block Manager, CODESYS, 3.5.20.0) are largely implemented in the runtime system and should not be called directly by the end user. However, when they are well encapsulated in function blocks, they are useful for the development of function block libraries that are convenient to work with.
Pool based memory management¶
There are many advantages to working with a pool of equally sized blocks.
The calls for the request (
PoolGetBlock
) and release (PoolPutBlock
) of the blocks can be implemented in a very efficient way with a constant effort.The operating system (
Malloc
/Free
) does not have to be in use for this purpose.There is no risk of memory fragmentation.
Synchronization of the various calls in multitasking or multicore environments is guaranteed.
The functions for creating, extending (PoolCreateH
/PoolExtendH
) or destroying (PoolDelete
) a pool
require the memory management (Malloc
/Free
) of the operating system
and should therefore be called in an appropriate context (call_after_init
/ BackgroundTask
)
so that they do not interfere with the cycle time of the PLC.
The order in which the pool and its blocks are released is not easy to maintain (see: FB_Exit
).
For this reason, calling PoolDelete
does not delete the pool, it just locks it.
Calls to PoolGetBlock
and PoolExtendH
will therefore return errors.
Only after the last call to PoolPutBlock
will the resources of the pool be released.
Setup a Block Pool¶
There are two options for setting up a pool.
1.) From “static” memory with PoolCreateP
TYPE DATA : STRUCT (* ... some members ...*) END_STRUCT END_TYPE // SIZEOF(DATA) should be a multiple of SIZEOF(__XWORD) // => ((SIZEOF(DATA) + SIZEOF(__XWORD) - 1) / SIZEOF(__XWORD)) * SIZEOF(__XWORD) // n => Number of blocks // m => Number of __XWORD entries in axwPoolMem // m := MBM.PoolGetSize(n, SIZEOF(DATA)) / SIZEOF(__XWORD) + 1; axwPoolMem : ARRAY[0..(m-1)] OF __XWORD; // Initial memory area for the pool (__XWORD aligned) hPool : MBM.HANDLE; eError : MBM.ERROR; hPool := MBM.PoolCreateP( szBlockSize:=SIZEOF(DATA), szMemSize:=SIZEOF(axwPoolMem), pMemory:=ADR(axwPoolMem), peError:=ADR(eError) );
2.) From “dynamic” memory with PoolCreateH
TYPE DATA : STRUCT (* ... some members ...*) END_STRUCT END_TYPE // n => Number of blocks hPool : MBM.HANDLE; eError : MBM.ERROR; hPool := MBM.PoolCreateH( ctNumBlocks:=n, szBlockSize:=SIZEOF(DATA), peError:=ADR(eError) );
Managing memory blocks¶
1.) Requesting a block with PoolGetBlock
hPool : MBM.HANDLE; eError : MBM.ERROR; pBLock : POINTER TO DATA; pBlock := MBM.PoolGetBlock(hPool:=hPool, peError:=ADR(eError)); IF pBlock <> 0 THEN (* ... work with the new block ... *) // optional, regenerate hPool from pBlock hPool := MBM.BlockGetPool(hBlock:=pBlock, peError:=ADR(eError)); END_IF
2.) Release a block with PoolPutBlock
eError : MBM.ERROR; pBLock : POINTER TO DATA; eError := MBM.PoolPutBlock(hBlock:=pBlock); pBlock := 0;
Extending a block pool¶
hPool : MBM.HANDLE; eError : MBM.ERROR; xExtend : BOOL; IF xExtend THEN // block pool is exhausted // extend block pool from heap eError := MBM.PoolExtendH(hPool:=hPool, ctNumBlocks:=n); xExtend := FALSE; END_IF
Destroying a block pool¶
hPool : MBM.HANDLE; eError : MBM.ERROR; xExit : BOOL; IF xExit THEN eError := MBM.PoolDelete(hPool:=hPool); // The pool is now locked and PoolGetBlock will return an error // The pool memory will be released with the last PoolPutBlock hPool := MBM.gc_hINVALID; xExit := FALSE; END_IF
Query the current state of a block pool¶
hPool : MBM.HANDLE; eError : MBM.ERROR; xQuery : BOOL; udiSize, udiCapacity, udiCount : UDINT; IF xQuery THEN udiSize := TO_UDINT(MBM.PoolGetBlockSize(hPool:=hPool, peError:=ADR(eError)); udiCapacity := TO_UDINT(MBM.PoolGetCurCapacity(hPool:=hPool, peError:=ADR(eError)); udiCount := TO_UDINT(MBM.PoolGetNumBlocksLeft(hPool:=hPool, peError:=ADR(eError)); xQuery := FALSE; END_IF
Application and management of messages¶
What we said about block pools applies equally to message pools.
Messages are typically used to connect different tasks. A first task, the producer, uses a message pool and takes messages from it and sends them to the queue of a second task, the consumer. The consumer task takes the message from the queue and processes it. After processing, the message is sent back to the pool. The two tasks can operate independently of each other through this cycle, with peak loads being balanced by the queue.
A queue can handle between 1 (highest) and 64 (lowest) priority levels.
The exact number of priority levels for a specific queue is defined with the function XChgCreateP
/ XChgCreateH
.
The producer assigns a priority to each message.
The consumer receives the message with the highest priority on a first in/first out basis.
This allows a high priority message to overtake a low priority message.
A queue can process messages from different pools. For example, different types of messages can be used.
Setup a Message Pool¶
There are two options for setting up a message pool.
1.) From “static” memory with XChgCreateP
TYPE DATA : STRUCT (* ... some members ...*) END_STRUCT END_TYPE // SIZEOF(DATA) should be a multiple of SIZEOF(__XWORD) // => ((SIZEOF(DATA) + SIZEOF(__XWORD) - 1) / SIZEOF(__XWORD)) * SIZEOF(__XWORD) // n => Number of messages // m => Number of __XWORD entries in axwPoolMem // m := MBM.XChgGetSize(n, SIZEOF(DATA), 0) / SIZEOF(__XWORD) + 1; axwPoolMem : ARRAY[0..(m-1)] OF __XWORD; // Initial memory area for the pool (__XWORD aligned) hPool : MBM.HANDLE; eError : MBM.ERROR; hPool := MBM.XChgCreateP( szBlockSize:=SIZEOF(DATA), ctNumPrios:=0, (* !! *) szMemSize:=SIZEOF(axwPoolMem), pMemory:=ADR(axwPoolMem), peError:=ADR(eError) );
2.) From “dynamic” memory with XChgCreateH
TYPE DATA : STRUCT (* ... some members ...*) END_STRUCT END_TYPE // n => Number of messages hPool : MBM.HANDLE; eError : MBM.ERROR; hPool := MBM.XChgCreateH( ctNumMsg:=n, szBlockSize:=SIZEOF(DATA), ctNumPrios:=0, (* !! *) peError:=ADR(eError) );
Setup a Message Queue¶
There are two options for setting up a message queue.
1.) From “static” memory with XChgCreateP
// m => Number of __XWORD entries in axwQueueMem // p => Number of priority levels (1..64) // m := MBM.XChgGetSize(0, 0, p) / SIZEOF(__XWORD) + 1; axwQueueMem : ARRAY[0..(m-1)] OF __XWORD; // Initial memory area for the queue (__XWORD aligned) hQueue : MBM.HANDLE; eError : MBM.ERROR; hQueue := MBM.XChgCreateP( szBlockSize:=0, (* !! *) ctNumPrios:=p, szMemSize:=SIZEOF(axwQueueMem), pMemory:=ADR(axwQueueMem), peError:=ADR(eError) );
2.) From “dynamic” memory with XChgCreateH
// p => Number of priority levels (1..64) hQueue : MBM.HANDLE; eError : MBM.ERROR; hQueue := MBM.XChgCreateH( ctNumMsg:=0, (* !! *) szBlockSize:=0, (* !! *) ctNumPrios:=p, peError:=ADR(eError) );
Managing messages¶
1.) Requesting a message out of a message pool with MsgReceive
hPool : MBM.HANDLE; eError : MBM.ERROR; pMessage : POINTER TO DATA; ctMsgLeft : MBM.COUNT; (* !! *) pMessage := MBM.MsgReceive(hXChg:=hPool, pctMsgLeft:=ADR(ctMsgLeft), peError:=ADR(eError)); IF pMessage <> 0 THEN (* ... work with the new message ... *) // optional, regenerate hPool from pMessage hPool := MBM.MsgGetRXChg(hMsg:=pMessage, peError:=ADR(eError)); END_IF
2.) Release a message back to its pool with MsgRelease
eError : MBM.ERROR; pMessage : POINTER TO DATA; eError := MBM.MsgRelease(hMsg:=pMessage); pMessage := 0;
3.) Requesting a message out of a message queue with MsgReceive
hQueue : MBM.HANDLE; eError : MBM.ERROR; pMessage : POINTER TO DATA; ctMsgLeft : MBM.COUNT; (* !! *) pMessage := MBM.MsgReceive(hXChg:=hQueue, pctMsgLeft:=ADR(ctMsgLeft), peError:=ADR(eError)); IF pMessage <> 0 THEN (* ... work with the new message ... *) MBM.MsgRelease(hMsg:=pMessage); pMessage := 0; END_IF
4.) Sending a message to a message queue with MsgSend
hQueue : MBM.HANDLE; eError : MBM.ERROR; pMessage : POINTER TO DATA; usiPrio : USINT(1..64); eError := MBM.MsgSend(hMsg:=pMessage, usiPrio:=usiPrio, hXChg:=hQueue); IF eError <> MBM.ERROR.NO_ERROR THEN MBM.MsgRelease(hMsg:=pMessage); pMessage := 0; END_IF
Extending a message pool¶
hPool : MBM.HANDLE; eError : MBM.ERROR; xExtend : BOOL; IF xExtend THEN // message pool is exhausted // extend message pool from heap eError := MBM.XChgExtendH(hXChg:=hPool, ctNumMsg:=n); xExtend := FALSE; END_IF
Destroying a message pool¶
hPool : MBM.HANDLE; xExit : BOOL; IF xExit THEN MBM.XChgDelete(hXChg:=hPool); // The pool is now locked and MsgReceive will return an error // The pool memory will be released with the last MsgRelease hPool := MBM.gc_hINVALID; xExit := FALSE; END_IF
Destroying a message queue¶
hQueue : MBM.HANDLE; pMessage : POINTER TO BYTE; xExit : BOOL; IF xExit THEN // Any messages still in the queue are released MBM.XChgDelete(hXChg:=hQueue); hQueue := MBM.gc_hINVALID; xExit := FALSE; END_IF
Query the current state of a message pool¶
hPool : MBM.HANDLE; eError : MBM.ERROR; xQuery : BOOL; udiSize, udiCapacity, udiCount : UDINT; xEmpty : BOOL; IF xQuery THEN udiSize := TO_UDINT(MBM.RXChgGetMsgSize(hRXChg:=hPool, peError:=ADR(eError)); udiCapacity := TO_UDINT(MBM.RXChgGetCurCapacity(hRXChg:=hPool, peError:=ADR(eError)); udiCount := TO_UDINT(MBM.XChgMsgLeft(hXChg:=hPool, peError:=ADR(eError)); xEmpty := MBM.XChgIsEmpty(hXChg:=hPool, peError:=ADR(eError) xQuery := FALSE; END_IF
Query the current state of a message queue¶
hQueue : MBM.HANDLE; eError : MBM.ERROR; xQuery : BOOL; udiCount : UDINT; xEmpty : BOOL; IF xQuery THEN udiCount := TO_UDINT(MBM.XChgMsgLeft(hXChg:=hQueue, peError:=ADR(eError)); xEmpty := MBM.XChgIsEmpty(hXChg:=hQueue, peError:=ADR(eError) xQuery := FALSE; END_IF