The loader manages memory allocated from two entirely different memory pools. The loader is a component of a host process, the target server, and is able to allocate and free memory from the host heap using standard routines such as malloc( ) and free( ). However, the loader also manipulates data that it then transfers to the target system. The transfer requires the loader to allocate and free memory on the remote system as well. The loader does this through an internal library that manages a region of target system memory (the target server memory pool). The memory manager on the host also implements a host cache for portions of the target memory; this minimizes the number of data transfers to and from the target.
To understand how the target server manages the target server memory pool, refer to the online reference material under Tornado API Reference>Target Server Internal Routines. The routines listed are ANSI-compliant implementations; they have unique names to avoid conflicting with the host-system C library.
The interface to target server cache management is tgtMemCacheSet( ). This routine specifies the attributes of a block of host-mapped target memory. Blocks defined through this routine must not overlap. You must not change block boundaries once a block is created; however, you may call this routine again on a previously defined block to change its attributes.
The memory attributes of the target server memory pool are the following:
All memory obtained from the target server's target-memory manager is free from memory alignment problems because the target server memory manager always returns addresses that conform to the target alignment requirements. All tgtMemXxx( ) routines gracefully handle memory alignment.
However, alignment problems may occur when the object-module headers are processed by the loader. In general, the headers contain data that is packed (without padding) whereas the host architecture may require padding to represent the header information. For example, 16-bit quantities are often enlarged to 32-bit quantities by padding.
This can cause problems if the object module is not directly processed as a file but rather read entirely into memory first and subsequently processed. This is done to improve performance, but it can also lead to read-word operations at unaligned addresses. Some host architectures accept this, but others do not. Use the UNPACK_16 and UNPACK_32 macros (defined in host.h) to prevent portability problems. These macros offer the ability to read 16-bit (or 32-bit) data regardless of the alignment.
Another kind of alignment problem is related to the three-segment model imposed by the target server loader. In this model, sections of the same type are loaded together within a global area allocated for the coalesced segment. Each section is aligned according to the requirement specified in the object file, or, if none is specified, each is loaded at an address that fits within the target architectures alignment requirement. The requirement specified in the object file cannot be larger than the target alignment requirement. Because of alignment requirements, two coalesced sections may require a "hole" between them (depending on their sizes) to assure proper alignment. Be sure to account for this detail when computing the size of the segment sufficient to hold the coalesced sections.
The goal of the target server memory cache is to speed up object-module download and minimize the impact of module loading on target performance. It achieves this goal by performing all object-module relocations in host memory and then copying the final relocated module to the target.
In an ideal world, the target server memory cache would be the same size as the target server memory pool on the target. Then the cache could shadow the entire memory pool. In the real world, the size of the cache is limited by the size of host memory. The default for the target server cache is 1 MB. Use the -m cacheSize option to specify a larger size.
If the target server memory cache is smaller than the target server memory pool, the first object modules load quickly. However, once the cache is full, each relocation generates a memory-write transaction with the target. This slows the load process, especially if you are using a serial line.
When the target server memory pool is full and an additional module is loaded, the target server uses malloc( ) to request more memory. The target server always requests 1 KB more than it needs in order to limit the number of requests to the target for small chunks of memory.
Rules for sizing the cache and the target server memory pool:
Although the loader has memory attributes that can prevent corruption of target memory by the host (MEM_NO_WRITE), it cannot prevent the target from corrupting its own memory. Memory on the target side can be protected by the optional product VxVMI, which utilizes the CPU memory management unit (MMU) to designate regions of memory as read-only. The loader has access to this product (if it is installed) through the routine tgtMemProtect( ). Such protection can be useful when text segments are loaded; they represent program code that must not be corrupted by the execution of another program.
The target instruction cache, if present, creates problems for the loader if it is not updated when modules are downloaded. If incoherence exists between the instruction cache and the target memory, incorrect instructions can be executed from the cache. To prevent this, the loader updates the target instruction cache by calling tgtCacheTextUpdate( ) each time it loads a module.
Type abstraction is a way to avoid dependencies on the host definition of the size of various types. Tornado uses types with explicit sizes whenever information from the object modules must be manipulated. This information includes, for instance, the header-field contents or any other structural information such as relocation entries.
Type abstractions can be found in host.h and loadlib.h.
The type void * is used whenever an address is manipulated (through the more convenient type SYM_ADRS).