• 沒有找到結果。

Memory Management

在文檔中 µC/OS-II Goals Preface (頁 165-176)

Chapter 7

Partition #1 Partition #2 Partition #3 Partition #4

Figure 7-2, Multiple memory partitions.

7.00 Memory Control Blocks

µC/OS-II keeps track of memory partitions through the use of a data structure called a memory control block as shown in listing 7.1. Each memory partition requires its own memory control block.

typedef struct { void *OSMemAddr;

void *OSMemFreeList;

INT32U OSMemBlkSize;

INT32U OSMemNBlks;

INT32U OSMemNFree;

} OS_MEM;

Listing 7.1, Memory control block data structure.

OSMemAddr

is a pointer to the begin ning (i.e. base) of the memory partition from which memory blocks will be allocated from. This field is initialized when you create a partition (see section 7.01, Creating a partition) and is not used thereafter.

OSMemFreeList

is a pointer used by µC/OS-II to point to either the next free memory control block or to the next free memory block. The use depends on whether the memory partition has been created or not (see section 7.01).

OSMemBlkSize

determines the size of each memory block in the partition and is a parameter you specify when the memory partition is created (see section 7.01).

OSMemNBlks

establishes the total number of memory blocks available from the partition. This parameter is specified when the partition is created (see section 7.01).

OSMemNFree

is used to determine how many memory blocks are available from the partition.

µC/OS-II initializes the memory manager if you configure

OS_MEM_EN

to

1

in

OS_CFG.H

. Initialization is done by

OSMemInit()

(which is automatically called by

OSInit()

) and consist of creating a linked list of memory control blocks as shown in figure 7-3. You specify the maximum number of memory partitions with the configuration constant

OS_MAX_MEM_PART

(see

OS_CFG.H

) which MUST be set to at least 2.

As can be seen, t he

OSMemFreeList

field of the control block is used to chain the free control blocks.

OSMemAddr OSMemFreeList OSMemBlkSize OSMemNBlks OSMemNFree

OSMemAddr OSMemFreeList OSMemBlkSize OSMemNBlks OSMemNFree OSMemAddr

OSMemFreeList OSMemBlkSize OSMemNBlks OSMemNFree

0 OSMemFreeList

OS_MAX_MEM_PART

Figure 7-3, List of free memory control blocks.

7.01 Creating a partition, OSMemCreate()

Your application must create each partition before they can be used. You create a memory partition by calling

OSMemCreate()

. Listing 7.2 shows how you could create a memory partition containing 100 blocks of 32 bytes each.

OS_MEM *CommTxBuf;

INT8U CommTxPart[100][32];

void main (void) {

INT8U err;

OSInit();

. .

CommTxBuf = OSMemCreate(CommTxPart, 100, 32, &err);

. .

OSStart();

}

Listing 7.2, Creating a memory partition.

The code to create a memory partition is shown in listing 7.3.

OSMemCreate()

requires four arguments: the beginning address of the memory partition., the number of blocks to be allocated from this partition, the size (in bytes) of each block and, a pointer to a variable that will contain an error code when

OSMemCreate()

returns or a

NULL

pointer if

OSMEMCreate()

fails. Upon success,

OSMemCreate()

returns a pointer to the allocated memory control block. This pointer must be used in subsequent calls to memory management services (see

OSMemGet()

,

OSMemPut()

and

OSMemQuery()

in the next sections).

Each memory partition must contain at least 2 memory blocks L7.3(1). Also, each memory block must be able to hold the size of a pointer because a pointer is used to chain all the memory blocks together L7.3(2). Next,

OSMemCreate()

obtains a memory control block from the list of free memory control blocks L7.3(3). The memory control block will contain run-time information about the memory partition.

OSMemCreate()

will not

be able to create a memory partition unless a memory control block is available L7.3(4). If a memory control block is available and we satisfied all the previous conditions, the memory blocks within the partition are linked together in a singly linked list L7.3(5). When all the blocks are linked, the memory control block is filled with information about the partition L7.3(6).

OSMemCreate()

returns the pointer to the memory control block so it can be used in subsequent calls to access the memory blocks from this partition L7.3(7).

OS_MEM *OSMemCreate (void *addr, INT32U nblks, INT32U blksize, INT8U *err) {

OS_MEM *pmem;

INT8U *pblk;

void **plink;

INT32U i;

if (nblks < 2) { (1) *err = OS_MEM_INVALID_BLKS;

return ((OS_MEM *)0);

}

if (blksize < sizeof(void *)) { (2) *err = OS_MEM_INVALID_SIZE;

return ((OS_MEM *)0);

}

OS_ENTER_CRITICAL();

pmem = OSMemFreeList; (3) if (OSMemFreeList != (OS_MEM *)0) {

OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;

}

OS_EXIT_CRITICAL();

if (pmem == (OS_MEM *)0) { (4) *err = OS_MEM_INVALID_PART;

return ((OS_MEM *)0);

}

plink = (void **)addr; (5) pblk = (INT8U *)addr + blksize;

for (i = 0; i < (nblks - 1); i++) { *plink = (void *)pblk;

plink = (void **)pblk;

pblk = pblk + blksize;

}

*plink = (void *)0;

OS_ENTER_CRITICAL();

pmem->OSMemAddr = addr; (6) pmem->OSMemFreeList = addr;

pmem->OSMemNFree = nblks;

pmem->OSMemNBlks = nblks;

pmem->OSMemBlkSize = blksize;

OS_EXIT_CRITICAL();

*err = OS_NO_ERR;

return (pmem); (7) }

Listing 7.3, OSMemCreate()

Figure 7-4 shows how the data structures look like when

OSMemCreate()

completes successfully. Note that the memory blocks are sho w nicely linked one after the other. At run -time, as you allocate and de -allocate memory blocks, the blocks will most likely not be in this order.

0

OSMemAddr = addr OSMemFreeList= addr OSMemBlkSize = blksize OSMemNBlks = nblks OSMemNFree = nblks

Contiguous memory pmem

OSMemCreate() arguments

Figure 7-4, OSMemCreate()

7.02 Obtaining a memory block, OSMemGet()

Your application can get a memory block from one of the created memory partition by calling

OSMemGet()

. You simply use the pointer returned by

OSMemCreate()

in the call to

OSMemGet()

to specify which partition the memory block will come from. Obviously, you application will need to know how big the memory block obtained is so that it doesn’t exceeds its storage capacity. In other words, you must not use more memory than what is available from the memory block. For example, if a partition contains 32 byte blocks then your application can use up to 32 bytes. When you are done using the block, you must return the block to the proper memory partition (see section 7.03, Returning a memory block, OSMemPut()).

Listing 7.4 shows the code for

OSMemGet()

. The pointer specify the partition from which you want to get a memory block from L7.4(1).

OSMemGet()

first checks to see if there are free blocks available L7.4(2). If a block is available, it is removed from the free list L7.4(3). The free list is then updated L7.4(4) so that it points to the next free memory block and, the number of blocks is decremented indicating that it has been allocated L7.4(5). The pointer to the allocated block is finally returned to your application L7.4(6).

void *OSMemGet (OS_MEM *pmem, INT8U *err) (1) {

void *pblk;

OS_ENTER_CRITICAL();

if (pmem->OSMemNFree > 0) { (2) pblk = pmem->OSMemFreeList; (3) pmem->OSMemFreeList = *(void **)pblk; (4) pmem->OSMemNFree--; (5) OS_EXIT_CRITICAL();

*err = OS_NO_ERR;

return (pblk); (6) } else {

OS_EXIT_CRITICAL();

*err = OS_MEM_NO_FREE_BLKS;

return ((void *)0);

} }

Listing 7.4, OSMemGet()

You should note that you can call this function from an ISR because if a memory block is not available, there is no waiting. The ISR would simply receive a

NULL

pointer if no memory blocks are available.

7.03 Returning a memory block, OSMemPut()

When your application is done using a memory block, it must return it to the appropriate partition. This is accomplished by calling

OSMemPut()

. You should note that

OSMemPut()

has no way of knowing whether the memory block returned to the partition belongs to that partition. In other words, if you allocated a memory block from a partition containing blocks of 32 bytes then you should not return this block to a memory partition containing blocks of 120 bytes. The next time an application request a block from the 120 bytes partition, it will actually get 32

‘valid’bytes and the remaining 88 bytes may belong to some other task(s). This could certainly make your system crash.

Listing 7.5 shows the code for

OSMemPut()

. You simply pass

OSMemPut()

the address of the memory control block for which the memory block belongs to L7.5(1). We then check to see that the memory partition is not already full L7.5(2). This situation would certainly indicate that something went wrong during the allocation/de-allocation process. If the memory partition can accept another memory block, it is inserted in the linked list of free blocks L7.5(3). Finally, the number of memory blocks in the memory partition is incremented L7.5(4).

INT8U OSMemPut (OS_MEM *pmem, void *pblk) (1) {

OS_ENTER_CRITICAL();

if (pmem->OSMemNFree >= pmem->OSMemNBlks) { (2) OS_EXIT_CRITICAL();

return (OS_MEM_FULL);

}

*(void **)pblk = pmem->OSMemFreeList; (3) pmem->OSMemFreeList = pblk;

pmem->OSMemNFree++; (4) OS_EXIT_CRITICAL();

return (OS_NO_ERR);

}

Listing 7.5, OSMemPut()

7.04 Obtaining status about memory partition, OSMemQuery()

OSMemQuery() is used to obtain information about a memory partition. Specifically, your application can determine how many memory blocks are free, how many memory blocks have been used (i.e. allocated), the size of each memory block (in bytes), etc. This information is placed in a data structure called

OS_MEM_DATA

as shown in listing 7.6.

typedef struct {

void *OSAddr; /* Points to beginning address of the memory partition */

void *OSFreeList; /* Points to beginning of the free list of memory blocks */

INT32U OSBlkSize; /* Size (in bytes) of each memory block */

INT32U OSNBlks; /* Total number of blocks in the partition */

INT32U OSNFree; /* Number of memory blocks free */

INT32U OSNUsed; /* Number of memory blocks used */

} OS_MEM_DATA;

Listing 7.6, Data structure used to obtain status from a partition.

The code for

OSMemQuery()

is shown in listing 7.7. As you can see, all the fields found in

OS_MEM

are copied to the

OS_MEM_DATA

data structure with interrupts disabled L7.7(1). This ensures that the fields will not be altered until they are all copied. You should also notice that computation of the number of blocks used is performed outside of the critical section because it’s done using the local copy of the data L7.7(2).

INT8U OSMemQuery (OS_MEM *pmem, OS_MEM_DATA *pdata) {

OS_ENTER_CRITICAL();

pdata->OSAddr = pmem->OSMemAddr; (1) pdata->OSFreeList = pmem->OSMemFreeList;

pdata->OSBlkSize = pmem->OSMemBlkSize;

pdata->OSNBlks = pmem->OSMemNBlks;

pdata->OSNFree = pmem->OSMemNFree;

OS_EXIT_CRITICAL();

pdata->OSNUsed = pdata->OSNBlks - pdata->OSNFree; (2) return (OS_NO_ERR);

}

Listing 7.7, OSMemQuery()

7.05 Using memory partitions

Figure 7-5 shows an example of how you can use the dynamic memory allocation feature of µC/OS -II as well as its message passing capability (see chapter 6). Also, refer to listing 7.8 for the pseudo-code of the two tasks shown. The numbers in parenthesis in figure 7-5 correspond to the appropriate action in listing 7.8.

The first task reads and checks the value of analog inputs (pressures, temperatures, voltages, etc.) and sends a message to the second task if any of the analog inputs exceed a threshold. The message sent contains a time stamp, information about which channel had the error, an error code, an indication of the severity of the error and any other information you can think of.

Error handling in this example is centralized. This means that other tasks or even ISRs can post error messages to the error handling task. The error handling task could be responsible for displaying error messages on a monitor (i.e. a display), logging errors to a disk, dispatching other task(s) which would take corrective action(s) based on the error, etc.

ErrMsgPart

ErrMsgQ

Error Handler AI

Task

0

OSMemGet() OSMemPut()

OSQPost() OSQPend() OSTime

OSTimeGet()

(1)

Analog Inputs

(2) (3)

(4)

(5) (6)

(7)

(8)

Figure 7-5, Using dynamic memory allocation.

AnalogInputTask() {

for (;;) {

for (all analog inputs to read) {

Read analog input; (1) if (analog input exceed threshold) {

Get memory block; (2) Get current system time (in clock ticks); (3) Store the following items in the memory block: (4) System time (i.e. a time stamp);

The channel that exceeded the threshold;

An error code;

The severity of the error;

Etc.

Post the error message to error queue; (5) (A pointer to the memory block containing the data) }

}

Delay task until it’s time to sample analog inputs again;

} }

ErrorHandlerTask() {

for (;;) {

Wait for message from error queue; (6) (Gets a pointer to a memory block containing information

about the error reported)

Read the message and take action based on error reported; (7) Return the memory block to the memory partition; (8) }

}

Listing 7.8, Scanning analog inputs and reporting errors.

7.06 Waiting for memory blocks from a partition

Sometimes it’s useful to have a task wait for a memory block in case a partition runs out of blocks. µC/OS-II doesn’t support ‘pending’on a partitions, but you can support this requirement by adding a counting semaphore (see section 6.05, Semaphores) to guard the memory partition. To obtain a memory block, you simply need to obtain a semaphore then call

OSMemGet()

. The whole process is shown in listing 7.9.

First we declared our system objects L7.9(1). You should note that I used hard -coded constants for clarity. You would certainly create

#define

constants in a real application. We initialize µC/OS-II by calling

OSInit()

L7.9(2). We then create a semaphore with an initial count corresponding to the number of blocks in the partition L7.9(3). We then create the partition L7.9(4) and one of the tasks L7.9(5) that will be accessing the partition. By now, you should be able to figure out what you need to do to add the other tasks. It would obviously not make much sense to use a semaphore if only one task is using memory blocks – there would be no need to ensure mutual exclusion! In fact, it wouldn’t event make sense to use partitions unless you intend to share memory blocks with other tasks.

Multitasking is then started by calling

OSStart()

L7.9(6). When the task gets to execute, it obtains a memory block L7.9(8) only if a semaphore is available L7.9(7). Once the semaphore is available, the memory block is obtained. There is no need to check for returned error code of

OSSemPend()

because the only way µC/OS-II will return to this task is if a memory block is released. Also, you don’t need the erro r code for

OSMemGet()

for the

same reason – you must have at least one block in the partition in order for the task to resume. When the task is done using a memory block, it simply needs to return it to the partition L7.9(9) and signal the semaphore L7.9(10).

OS_EVENT *SemaphorePtr; (1) OS_MEM *PartitionPtr;

INT8U Partition[100][32];

OS_STK TaskStk[1000];

void main (void) {

INT8U err;

OSInit(); (2) .

.

SemaphorePtr = OSSemCreate(100); (3) PartitionPtr = OSMemCreate(Partition, 100, 32, &err); (4) .

OSTaskCreate(Task, (void *)0, &TaskStk[999], &err); (5) .

OSStart(); (6) }

void Task (void *pdata) {

INT8U err;

INT8U *pblock;

for (;;) {

OSSemPend(SemaphorePtr, 0, &err); (7) pblock = OSMemGet(PartitionPtr, &err); (8) .

. /* Use the memory block */

.

OSMemPut(PartitionPtr, pblock); (9) OSSemPost(SemaphorePtr); (10) }

}

Listing 7.9, Waiting for memory blocks from a partition.

Chapter 8

在文檔中 µC/OS-II Goals Preface (頁 165-176)