[ Next ] [ Previous ] | Chapter 2 |
[ Contents ] [ Chapter 3: Multitasking ]
|
In 0S/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, 0S/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 0S/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.
![]() |
0S/2 2.0 also introduced she 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 0S/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.
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 0BJ_TILE | PAG_COMMIT | PAG_EXECUTE | PAG_READ | PAG_WRITE.
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 mote 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 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
|
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, 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 ]
|