[Next] [Previous]      Chapter 2 
    [Contents] [ Chapter 3: Multitasking ]

Memory Management


In OS/2 1.3 the memory management scheme was designed to support the Intel segmented architecture. The 80286 could provide access to memory in segments that were limited in size to 64K. At times more than 64K was necessary. In those cases, the developer would have to create elaborate memory management schemes. This changed in OS/2 2.0. The amount of memory that developers can access is only limited by three items:

By dropping support for the 80286 and supporting only processors capable of supporting a 32-bit engine, OS/2 could have the flat, paged memory architecture of other non Intel-based chips. Both the Motorola 680x0 chips (base of the Apple Macintosh and other machines) and the RISC-base chips (base for IBM's RS-6000) use the flat, paged architecture. You cats probably see where this is leading. Designing a memory model that is portable is the first step in designing a portable operating system. A 32-bit operating system will allow addresses of up to OxFFFFFFFF, or 4GB. This also gives programmers the opportunity to allocate memory objects that are as large as the system memory allows.
OS/2 1x used the 16-bit addressing scheme of the 80286. A location in memory was represented as a 16:16 pointer, in selector-offset fashion. The upper portion of the selector maps into a descriptor table. The entry in the descriptor table maps the absolute location of the memory address.

Thirty-two-bit OS/2 has only three segments that combine to make 4GB total. This means that memory addresses are represented as a 0:32 pointer. All memory resides in these three segments. A normal program will run in die segment that starts at address 0 and covers 480Mb. Protected dynamic link libraries (DLLs see the same 480Mb region plus 32Mb above it 'This 512Mb addressability limitation is due to compatibility with 16-bit OS/2 programs.  The kernel functions see the full 4GB region. This is where the big performance boost comes in. Because all memory is in these three segments, when the operating system has to switch memory objects, the segment registers do not always have to be loaded. A flat memory management scheme has one more advantage: All pointers are near pointers, since all memory can be addressed using a 0:32 pointer. This means no more 'FAR' jumps for the operating system. This also means memory models -small. medium, large, and huge -are now obsolete.
The basis of the 32-bit OS/2 memory management functions is DosAllocMem This function allocates memory in 4,096-byte chunks called pages; however, a developer can allocate several contiguous pages in one call. While this means that you can allocate any amount of memory up to the process limit, it also means that you can waste a considerable amount of memory if you're not careful.
Consider the following code fragment:

  for (i=0; i< 1000; i++)
     DosAllocMem(&p[i],
                    1,
                    PAG_READ | PAG_WRITE | PAG_COMMIT);

The first parameter is a PPVOID,  the  second parameter is the number of bytes  allocated, and the last parameter is the memory flags. We'll see this again soon.

What you see in the code fragment is 1,000   1-byte blocks being allocated. What you don't see is the 1,000  4,095-byte blocks that are not being used because DosAllocMem allocates memory as an integral number of pages.

Committing Memory

 
OS/2 2.0 also introduced the concept or committing memory. A call to DosAllocMem will reserve an address range for the memory; however, physical memory is actually assigned to the range only if the PAG_COMMIT flag is specified. (A side note here: In 32-bit OS/2, a page is only assigned to an address really when the page is touched.)  If you try to access uncommitted memory, otherwise known as sparse memory objects TRAP-BOOM! If you choose to allocate memory without committing it, you have two ways of having it committed later - DosSetMem  or DosSubSetMem. Also, in 32-bit OS/2, memory is guaranteed to be initialized to 0. This prevents the application from having to initialize the memory, thereby touching all the memory, thereby committing  all the  memory

The following is a very simple program to allocate memory and to show a little about what happens to bad programs. Remember that we are seasoned professionals. Do not attempt this at home. Well, you may want to attempt it at home, but if you attempt this at work consistently, it may get you tired.

BADMEM.C
BADMEM.MAK
BADMEM.DEF

Now, you may look at this code and say, 'But, you're allocating only 3.000 bytes, and you're writing to 4,098.' Okay, this is bad code; however, It illustrates that no matter how much you specify as bytes allocated, the operating system will return it to you in 4,096 -byte pages, and you could use them all and never see a protection violation. You'd just end up stomping all over some data that you may need.
However, notice that when you try to write to byte 4097, TRAP ! This too can happen to you, so be very careful about writing to unallocated, uncommitted memory.

The flags used as the page attributes in the preceding example were PAG_READ | PAG_WRITE | PAG_COMMIT. Table 2.1 lists the possible page attributes.
 
Table 2.1 Page Attributes
Flag   Description
PAG_READ  Read access is the only access allowed.    A write to the memory location will generate a trap.
PAG_WRITE  Read, write, and execute access is allowed.
PAG_EXECUTE   Execute and read access to the memory is allowed. This flag sill also provide  compatibility for future versions of the operating system.
PAG_GUARD Sets a guard page after the allocated memory object If any attempt is made to write to that guard page. a guard page fault exception is raised, and the application  is given a chance to allocate more memory as needed. (See Chapter 6-  Exception Handling)
OBJ_TILE All memory objects are put into the tiled, or compatibility, region in OS/2 2.x. All objects are aligned on 64K boundaries. Provides upward compatibility when applications will be allowed by future versions of the operating system to access regions above 512MB "16-bit compatibility" barrier

Often the example programs and manuals will reference the default page attribute, fALLOC;  this is a #define for OBJ_TILE | PAG_COMMIT | PAG_EXECUTE | PAG_READ | PAG_WRITE.

Suballocating Memory

DosSubSetMem and DosSubAllocMem provide a more efficient way for developers to access chunks of memory smaller titan 4,096 bytes. An application can use DosAllocMem to allocate some number of bytes, called a memory object. DosSubSetMem is used to initialize or grow a heap within the memory object. This function has three parameters, PVOID offset, ULONG flags. and ULONG size. The flags parameter is used to provide more details about the heap. The following options are available for this parameter:
DosSubSetMem has access to all memory in the memory object. The application then calls DosSubAllocMem to allocate a smaller chunk of the heap. DosSubAllocMem can allocate all but 64 bytes of the heap. The 64 bytes is called a memory pool header. The operating system uses it to manage the suballocated portion. DosSubAllocMem has three parameters, PVOID Offset, PPVOID SmallBlock, and ULONG size. The amount actually allocated is a multiple of 8 bytes, rounded up if not a multiple of 8.
The following program shows you how to handle suballocations of memory:

SUBMEM.C
SUBMEM.MAK
SUBMEM.DEF

You'll notice when you run this program that all your pointer sizes are rounded up In increments of 8 and that DosSubAllocMem starts allocating at the 65th byte of the memory object.

Shared Memory

Shared memory is the fastest method of interprocess communication. There are two types or shared memory, named and unnamed. Shared memory is created by a call to DosAllocSharedMem.  If creating shared memory,  the second parameter to DosAllocSharedMem  is the name for the memory, in the form of \SHAREM\MemName. If using unnamed memory, a NULL is specified. There is one other difference between shared and unnamed memory-the process that allocates an unnamed memory object must declare it as giveable by using DosGiveSharedMem, and the process accessing the memory object must call DosGetSharedMem. Shared memory can be committed and decommitted just like private memory. Also, when suballocating memory from a shared memory pool, both DosSubSetMem must use the same size parameter in both processes. or an error will result.
Gotcha!

All the processes involved with the shared memory (both the getting and giving) must free the shared memory before it is available foe reuse. If only one process frees the memory, you may begin to notice an increase in your program's memory consumption over time. The system maintains a usage count of shared memory that enables is to keep track of all she processes that have access to the shared memory. The IBM products THESEUS2 and SPM/2 are the only tools available to detect memory leakage. They are two excellent tools to monitor the system performance.

The following programs are examples of allocating a named shared memory object. Notice that the memory is being allocated in a downward fashion; private memory is allocated upward from the bottom of the available space.

BATMAN.C
BATMAN.DEF
ROBIN.C
ROBIN.DEF
DYNDUO.H
DYNDUO.MAK

DosAllocMem or malloc?

DosAllocMem, DosSubSetMem, and DosSubAllocMem might seem like a bit of overkill if you would like to have only 20 bytes for a string every now and then. And they are. These functions are moss useful for large programs that allocate large quantities of memory at one time,allocate shared memory, or have special memory needs. For most smaller applications,  malloc from an ANSI C compiler will be just fine.
Also, you probably will find that malloc is much more portable to other versions of OS/2 running on top of the Power PC. The C Set++ version of malloc is the only compiler version of malloc  that will be  compared  to DosAllocMem and company. In most cases malloc will provide memory to the program just as fast as DosAllocMem. The C Set++ compiler uses a special algorithm, designed to provide the expected amount of memory in the fastest time. The following program uses mailer to allocate memory and then displays the  amount of memory allocated plus the location of  the pointer in memory. You probably will start to notice a pattern emerging, and there is one.

SPEED.C
SPEED.MAK
SPEED.DEF

By looking at the program's output, you'll notice that memory allocation starts by using 32 for values between 1 and 16. It uses 64 for values between 17 and 32, 128, 256, and finally 512. You may notice a few "bumps" in the algorithm. They occur when the C runtime is using some of the memory for its own purposes.


[Next] [Previous] Chapter 2
[Contents] [ Chapter 3: Multitasking ]