Memory Is Extremely Limited

Now that you have an idea of how the system hands events off to the application, it’s time to look at how memory is handled. To start off, it will help if you remember one crucial point: memory is an extremely limited resource on Palm OS devices. Because of this, Palm OS applications need to be written with careful attention to memory management.

To that end, let’s examine the memory architecture on Palm devices. RAM is divided into two areas: storage and dynamic (see Figure 4.5). The storage area of memory is managed by the Database Manager, which we discuss in Chapter 6. It is dynamic memory, which is handled by the Memory Manager, that we discuss here.

Memory map

Figure 4-5. Memory map

The dynamic memory is used for Palm OS globals, Palm OS dynamic allocations, your application’s global variables (note that C statics are a form of globals), your application’s stack space, and any dynamic memory allocations you make. As you can see in Table 4.1, the size available depends on the operating system and on the amount of total memory on the device.

Table 4-1. Dynamic Memory Usage for Various Palm OS Configurations

System Resources

OS 3.0 (>1 MB)

OS 2.0 (1 MB; has TCP/IP)

OS 2.0 (512 KB; no TCP/IP)

System Globals

6KB

2.5KB

2.5KB

System dynamic allocation

(TCP/IP, IRDA, etc.)

50KB

47KB

15KB

Application stack (call stack and local variables)

(by default)

2.5KB

2.5KB

Remainder (application globals dynamic allocation)

36KB

12KB

12KB

Total dynamic memory

96KB

64KB

32KB

The Dynamic Heap

The dynamic memory area is called the dynamic heap. You can allocate from the heap as either nonrelocatable chunks (called pointers) or relocatable chunks (called handles). It is always preferable to use handles wherever possible (if you’re going to keep something locked for its entire existence, you might as well use a pointer). This gives the memory manager the ability to move chunks around as necessary and to keep free space contiguous.

In order to read or modify the contents of a relocatable block, you temporarily lock it. When a memory allocation occurs, any unlocked relocatable block can be relocated (see Figure 4.6 for a diagram of unlocked relocatable blocks moving due to a memory allocation).

The dynamic heap before and after doing an allocation

Figure 4-6. The dynamic heap before and after doing an allocation

Memory API

Here is the API for using handles in your code. MemHandleNew lets you allocate a handle like this:

VoidHand myHandle = MemHandleNew(chunkSize)

MemHandleNew will return NULL if the allocation was unsuccessful.

Before you read from or write to a handle in your program, you need to lock it. You do so by calling MemHandleLock, which returns a pointer to the locked data. While the handle is locked, the relocatable block can’t be moved and you can do things like reading and writing of the data. In general, you should keep a handle locked for as short a time as possible (keeping in mind, however, that there is a performance cost to repetitive locking and unlocking); locked handles tend to fragment free memory when compaction takes place. Here is the code to lock and unlock a memory handle:

void *myPointer = MemHandleLock(myHandle);
// do something with myPointer
MemHandleUnlock(myHandle);

MemHandleLock and MemHandleUnlock calls can be nested, because MemHandleLock increments a lock count (you can have a maximum of 14 outstanding locks per handle). MemHandleUnlock decrements the lock count. Note that it doesn’t actually allow the chunk to move unless the lock count hits 0. If you get overeager and try to lock a handle that’s already been locked 14 times, you get a runtime “chunk overlocked” error message. Similarly, unlocking a handle that is already unlocked (whose lock count is 0) generates a “chunk underlocked” error message.

Alternatively, you can call MemPtrUnlock . This may be more convenient, especially when the unlock is in a separate routine from the lock. This way you only have to pass around the locked pointer.

To allocate a pointer, use MemPtrNew :

struct s *myS = MemPtrNew(sizeof(struct s));

To free a chunk, use MemPtrFree or MemHandleFree:

MemPtrFree(myPointer);
MemHandleFree(myHandle);

As a chunk is allocated, it is marked with an owner ID. When an application is closed, the Memory Manager deallocates all chunks with that owner ID. Other chunks (for instance, those allocated by the system with a different mark) are not deallocated.

You shouldn’t rely on this cleanup, however. Instead, you should code your application to free all its allocated memory explicitly. Just consider the system cleanup to be a crutch for those application writers who aren’t as fastidious as you. However, in the rare case that you might forget a deallocation, the system will do it for you.

This cleanup makes the lives of Palm device users much happier. They are no longer prey to every poorly written application with a memory leak. Without this behavior, there would be no cleanup of memory allocated by an application but never deallocated. Imagine an application that allocates 50 bytes every time it is run but never deallocates it. Running the application twice a day for two weeks uses 1,400 bytes of dynamic memory that could be reclaimed only by a reset. A Palm device isn’t like a desktop computer that is rebooted fairly often (at least we know our desktop computers are rebooted fairly often!). Instead, a Palm device should run months or years without a reset. The fact that handhelds need a reset button is a flaw. (Don’t get us wrong, though; given the current state of affairs, handhelds do need reset buttons.)

The Memory Manager provides other facilities, including finding the size of a chunk, resizing a chunk, and finding a handle given a locked pointer. For more information about these routines, you should see the Memory Manager documentation (or the include file MemoryMgr.h).

Last, there are two useful memory utility routines you should know about. They are MemSet and MemMove :

MemSet(void *p, ULong numBytes, Byte value)
MemMove(void *from, void *to, ULong numBytes)

MemSet sets a range of memory to the specified byte value. MemMove copies the specified number of bytes from a particular range to another range (it correctly handles the case where the two ranges overlap).

Get Palm Programming: The Developer's Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.