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