October 1999
Code for this article: Oct99Win32.exe (35KB)
Jeffrey Richter wrote Advanced Windows, Third Edition (Microsoft Press, 1998) and Windows 95: A Developer's Guide (M&T Books, 1995). Jeff is a consultant and teaches Win32 programming courses (www.solsem.com). He can be reached at www.JeffreyRichter.com. |
In my July 1999 column, I discussed how you should
create threads by calling the C runtime library's _beginthreadex function instead of the operating system's CreateThread function. That column apparently hit a hot button for many readers, since I received a lot of email about it.
The area of concern is in regard to freeing the thread's _tiddata structure. Most readers agree that if you link to the C runtime's static library, you should create your threads using _beginthreadex or the _tiddata structure will leak when the thread terminates. However, many readers indicated that the DLL version of the C runtime library traps DLL_THREAD_DETACH notifications and automatically destroys the _tiddata structure. These readers then surmise that it's fine to use CreateThread over _beginthreadex. For the most part they are correct, but there are some remaining issues. The following discussion summarizes what you need to know. Calling CreateThread in an EXE or a DLL linked to the single-threaded, static C runtime library does not create a _tiddata structure, and C runtime functions will not work correctly. If you call _beginthreadex to create your threads (as I suggest), then you would discover this bug at compile time instead of runtime. Calling the CreateThread function in an EXE linked to the multithreaded, static C runtime library does leak the _tiddata structure. Calling CreateThread in a DLL linked to the multithreaded, static C runtime library frees the _tiddata structure. However, if your DLL calls DisableThreadlibrarycalls, then your DLL will not receive the DLL_THREAD_DETACH notification and the _tiddata block will leak. Calling the CreateThread function in an EXE or a DLL linked to the multithreaded, dynamic C runtime library frees the _tiddata structure. If you always link to the multithreaded, dynamic C runtime library, then the _tiddata structure will never leak. However, _beginthreadex does more than just guarantee freeing of the _tiddata structure when the thread terminates. Calling _beginthreadex also ensures that the signal function and floating point exceptions all work correctly. I still strongly recommend that you avoid CreateThread in favor of _beginthreadex. There is absolutely no disadvantage to calling _beginthreadex, and your code is much safer. Besides, there are several advantages: compile-time checking if you link to an improper C runtime library; guaranteed freeing of the _tiddata block regardless of whether you're creating an EXE or a DLL and regardless of which multithreaded C runtime library you're using; signal support; and floating point exception support. And who knows what _beginthreadex might do for you in the future? C'mon now, it's not that hard to do a global search and replace! Address Windowing Extensions
This function allocates the number of RAM pages specified in the value pointed to by the pulRAMPages parameter and then assigns these pages to the process identified by the hProcess parameter.
Each page of RAM is assigned a page frame number by the operating system. As the system selects pages of RAM for the allocation, it populates the arraypointed to by the aRAMPages parameterwith each RAM page's page frame number. The page frame numbers themselves are not useful in any way to your application; you should not examine the contents of this array, and you most definitely should not alter any of the values in it. Note that you neither know which pages of RAM were allocated to this block, nor should you care. When the address window shows the pages in the RAM block, they appear as a contiguous block of memory. This makes the RAM easy to use and frees you from having to understand exactly what the system is doing internally. When the function returns, the value in pulRAMPages indicates the number of pages that the function allocated successfully. This will usually be the same value that you passed to the function, but it can also be a smaller value. Only the owning process can use the allocated RAM pages; AWE does not allow the RAM pages to be mapped into another process's address space. Therefore, you cannot share RAM blocks between processes. Of course, physical RAM is a very precious resource and an application can only allocate whatever RAM has not already been dedicated. You should use AWE sparingly or your process and other processes will excessively page storage to and from disk, severely hurting overall performance. In addition, less available RAM adversely affects the system's ability to create new processes, threads, and other resources. An app can use the GlobalMemoryStatusEx function to monitor physical memory use. To help protect the allocation of RAM, the AllocateUserPhysicalPages function requires the caller to have the Lock Pages in Memory user right granted and enabled or the function fails. By default, this right isn't assigned to any user or group. The right is given to the Local System account, which is typically used for services. If you want to run an interactive application that calls AllocateUserPhysicalPages, an administrator must grant you this right before you log on and run the application. The sidebar "Setting User Rights" explains how to turn this privilege on in Windows 2000. Now that I've created the address window and allocated a RAM block, I assign the block to the window by calling MapUserPhysicalPages: The first parameter, pvAddressWindow, indicates the virtual address of the address window. The second two parameters, ulRAMPages and aRAMPages, indicate how many and which pages of RAM to make visible in this address window. If the window is smaller than the number of pages you're attempting to map, the function fails. The main goal for this function is to make it execute extremely fast. Typically, MapUserPhysicalPages is able to map the RAM block in just a few microseconds.
Note that you can also call MapUserPhysicalPages to unassign the current RAM block by passing NULL for the aRAMPages parameter: Once the RAM block has been assigned to the address window, you can easily access the RAM storage simply by referencing virtual addresses relative to the address window's base address (pvWindow in my example code).
When you no longer need the RAM block, you should free it by calling FreeUserPhysicalPages: The first parameter, hProcess, indicates which process owns the RAM pages you're attempting to free. The next two parameters indicate how many pages and the page frame numbers of those pages that are to be freed. If this RAM block is currently mapped to the address window, it is unmapped and then freed.
Finally, to completely clean up, I free the address window by calling VirtualFree, passing the base virtual address of the window, 0 for the region's size, and MEM_RELEASE. My simple example creates a single address window and a single RAM block. This allows my application to access RAM that will not be swapped to or from disk. However, an application can create several address windows and can allocate several RAM blocks. These RAM blocks can be assigned to any of the address windows, but the system does not allow a single RAM block to appear in two address windows simultaneously. 64-bit Windows 2000 fully supports AWE. Porting a 32-bit application that uses AWE is easy and straightforward. AWE is less useful for a 64-bit application since a process's address space is so large, but it's still useful because it allows the application to allocate physical RAM that is not swapped to or from disk. The AWETest Sample Application
|
Figure 3: The AWETest Interface |
If you attempt to assign the same RAM block to the two address windows simultaneously, the message box shown in Figure 4 appears, since AWE doesn't support this. The source code for this sample application is clear-cut. To make working with AWE easier, I created three C++ classes contained in the AddrWindow.h file. The first class, CSystemInfo, is a very simple wrapper around the GetSystemInfo
The second C++ class, CAddrWindow, encapsulates an address window. Basically, the Create method reserves an address window, the Destroy method destroys the address window, the UnmapStorage method unmaps any RAM block currently assigned to the address window, and the PVOID cast operator method simply returns the virtual address of the address window. The third C++ class, CAddrWindowStorage, encapsulates a RAM block that may be assigned to a CAddrWindow object. The Allocate method enables the Lock Pages in Memory user right, attempts to allocate the RAM block, and then disables the user right. The Free method frees the RAM block. The HowManyPagesAllocated method returns the number of pages allocated successfully. The MapStorage and UnmapStorage methods map and unmap the RAM block to or from a CAddrWindow object. Using these C++ classes made implementing the sample application much easier. The app creates two CAddrWindow objects and two CAddrWindowStorage objects. The rest of the code is just a matter of calling the correct method for the proper object at the right time. |
Have a question about programming in Win32? Contact Jeffrey Richter at http://www.JeffreyRichter.com
|
From the October 1999 issue of Microsoft Systems Journal.
|