TOC PREV NEXT INDEX

Put your logo here!



6.8 NUMA and Peripheral Devices

Although most of the RAM memory in a system is based on high-speed DRAM interfaced directly to the processor's bus, not all memory is connected to the CPU in this manner. Sometimes a large block of RAM is part of a peripheral device and you communicate with that device by writing data to the RAM on the peripheral. Video display cards are probably the most common example, but some network interface cards and USB controllers also work this way (as well as other peripherals). Unfortunately, the access time to the RAM on these peripheral devices is often much slower than access to normal memory. We'll call such access NUMA1 access to indicate that access to such memory isn't uniform (that is, not all memory locations have the same access times). In this section we'll use the video card as an example, although NUMA performance applies to other devices and memory technologies as well.

A typical video card interfaces to the CPU via the AGP or PCI (or much worse, ISA) bus inside the computer system. The PCI bus nominally runs at 33 MHz and is capable of transferring four bytes per bus cycle. In burst mode, a video controller card, therefore, is capable of transferring 132 megabytes per second (though few would ever come close to achieving this for technical reasons). Now compare this with main memory access. Main memory usually connects directly to the CPU's bus and modern CPUs have a 400 MHz 64-bit wide bus. Technically (if memory were fast enough), the CPU's bus could transfer 800 MBytes/sec. between memory and the CPU. This is six times faster than transferring data across the PCI bus. Game programmers long ago discovered that it's much faster to manipulate a copy of the screen data in main memory and only copy that data to the video display memory when a vertical retrace occurs (about 60 times/sec.). This mechanism is much faster than writing directly to the video memory every time you want to make a change.

Unlike caches and the virtual memory subsystem that operate in a transparent fashion, programs that write to NUMA devices must be aware of this and minimize the accesses whenever possible (e.g., by using an off-screen bitmap to hold temporary results). If you're actually storing and retrieving data on a NUMA device, like a Flash memory card, then you must explicitly cache the data yourself. Later in this text you'll learn about hash tables and searching. Those techniques will help you create your own caching system for NUMA devices.

6.9 Segmentation

Segmentation is another memory management scheme, like paging, that provides memory protection and virtual memory capabilities. Linux and Windows do not support the use of segments, nor does HLA provide any instructions that let you manipulate segment registers or use segment override prefixes on an instruction2. These 32-bit operating system employ the flat memory model that, essentially, ignore segments on the 80x86. Furthermore, the remainder of this text also ignores segmentation. What this means is that you don't really need to know anything about segmentation in order to write assembly language programs that run under modern OSes. However, it's unthinkable to write a book on 80x86 assembly language programming that doesn't at least mention segmentation. Hence this section.

The basic idea behind the segmentation model is that memory is managed using a set of segments. Each segment is, essentially, its own address space. A segment consists of two components: a base address that contains the address of some physical memory location and a length value that specifies the length of the segment. A segmented address also consists of two components: a segment selector and an offset into the segment. The segment selector specifies the segment to use (that is, the base address and length values) while the offset component specifies the offset from the base address for the actual memory access. The physical address of the actual memory location is the sum of the offset and the base address values. If the offset exceeds the length of the segment, the system generates a protection violation.

Segmentation on the 80x86 got a (deservedly) bad name back in the days of the 8086, 8088, and 80286 processors. The problem back then is that the offset into the segment was only a 16-bit value, effectively limiting segments to 64 kilobytes in length. By creating multiple segments in memory it was possible to address more than 64K within a single program; however, it was a major pain to do so, especially if a single data object exceeded 64 kilobytes in length. With the advent of the 80386, Intel solved this problem (and others) with their segmentation model. By then, however, the damage had been done; segmentation had developed a really bad name that it still bears to this day.

Segments are an especially powerful memory management system when a program needs to manipulate different variable sized objects and the program cannot determine the size of the objects before run time. For example, suppose you want to manipulate several different files using the memory mapped file scheme. Under Windows or Linux, which don't support segmentation, you have to specify the maximum size of the file before you map it into memory. If you don't do this, then the operating system can't leave sufficient space at the end of the first file in memory before the second file starts. On the other hand, if the operating system supported segmentation, it could easily return segmented pointers to these two memory mapped files, each in their own logical address space. This would allow the files to grow to the size of the maximum offset within a segment (or the maximum file size, whichever is smaller). Likewise, if two programs wanted to share some common data, a segmented system could allow the two programs to put the shared data in a segment. This would allow both programs to reference objects in the shared area using like-valued pointer (offset) values. This makes is easier to pass pointer data (within the shared segment) between the two programs, a very difficult thing to do when using a flat memory model without segmentation as Linux and Windows currently do.

One of the more interesting features of the 80386 and later processors is the fact that Intel combined both segmentation and paging in the same memory management unit. Prior to the 80386 most real-world CPUs used paging or segmentation but not both. The 80386 processor merged both of these memory management mechanisms into the same chip, offering the advantages of both systems on a single chip. Unfortunately, most 32-bit operating systems (e.g., Linux and Windows) fail to take advantage of segmentation so this feature goes wasted on the chip.

6.10 Segments and HLA

Although HLA creates programs use the flat memory model under Windows and Linux3, HLA does provide limited support for segments in your code. However, HLA's (and the operating system's) segments are not the same thing as 80x86 segments; HLA segments are a logical organization of memory that has very little to do with segmentation on the 80x86. HLA's segments provide a simple way to organize variables and other objects in memory.

Logically, a segment is a block of memory where you place related objects. By default, HLA supports five different segments: a segment that holds machine instructions, a read-only segment that holds constant objects that HLA creates, a readonly segment that holds values you declare in the READONLY section, a data segment that holds variables and other objects you declare in the STATIC section, and a "BSS" section that holds uninitialized variables you declare in the STORAGE section4.

Normally you are completely unaware of the fact that HLA creates these segments in memory. The use of these segments is automatic and generally transparent to your HLA programs. In a few cases, however, you may need access to this segment information. For example, when linking your HLA programs with high level languages like C/C++ or Delphi you may need to tell HLA to use different names for the five segments it create (as imposed by the high level language). By default, HLA uses the following segment names for its five segments under Windows:

The "_TEXT", "_DATA", "_BSS", and "CONST" segment names are quite standard under Windows. Most common compilers that generate Win32 code use these segment names for the code, data, uninitialized, and constant data sections. There does not seem to be a common segment that high level language compilers use for read-only data (other than CONST), so HLA creates a separate specifically for this purpose: the "readonly" segment where HLA puts the objects you declare in the READONLY section.

Here's the typical names under Linux:

Examples of objects HLA puts in the "CONST" segment include string literal constants for string variables, constants HLA emits for extended syntax forms of the MUL, IMUL, DIV, IDIV, BOUNDS, and other instructions, floating point constants that HLA automatically emits (e.g., for the "fld( 1.234 );" instruction) and so on. Generally, you do not explicitly declare values that wind up in this section (other than through the use of one of the aforementioned instructions).

6.10.1 Renaming Segments Under Windows

Under Windows, HLA provides special directives that let you change the default names for the default segments. Although "_TEXT", "_DATA", "_BSS" and "CONST" are very standard names, some compilers may use different names and expect HLA to put its code and data in those different segments. The "readonly" segment is definitely non-standard, some compilers may not allow you to use it (indeed, some compilers may not even allow read-only segments in memory). Should you encounter a language that wants different segment names or doesn't allow readonly segments, you can tell HLA to use a different segment name or to map the read-only segments to the static data segment. Here are the directives to achieve this:

#code( "codeSegmentName", "alignment", "class" )
 
#static( "dataSegmentName", "alignment", "class" )
 
#storage( "bssSegmentName", "alignment", "class" )
 
#readonly( "readOnlySegmentName", "alignment", "class" )
 
#const( "constSegmentName", "alignment", "class" )
 

 

The #code directive tells HLA to rename the code segment ("_TEXT") or use different alignment or classification options. The #static directive renames the data segment ("_DATA", the segment the STATIC section uses). The #storage directive renames the uninitialized data segment ("_BSS", the segment the STORAGE section uses). The #readonly directive renames the "readonly" segment (where HLA places data you declare in the READONLY section). Finally, the #const directive renames HLA's "CONST" segments (where HLA places constants that it emits internally).

Each of these directives contains three string expression operands. The first string operand specifies the name of the segment. The second string specifies the segment alignment; we'll return to a discussion of this operand in a moment. The third operand is the segment class; the linker uses this name to combine segments that have different names into a single memory block. Generally, the class name is some variant of the segment name, but this is not necessarily the case (e.g., the standard class name for the "_TEXT" segment is "CODE").

The alignment operand must be a string that contains one of the following identifiers: "byte", "word", "dword", "para", or "page". HLA will only allow a string constant containing one of these five strings. The alignment option specifies the boundary on which the linker will start a segment. This option is only meaningful if you combine two different segments by using the same string for the class parameter. The linker combines two segments by concatenating them in memory. When the linker combines the segments, it makes sure that the concatenated segments start on the boundary the alignment operand specifies. A "byte" alignment means that the segment can start at an arbitrary byte boundary. The "word" and "dword" alignment options tell the linker that the segment must start on a word or double word boundary (respectively). The "para" alignment option tells the linker to start the segment on a paragraph (16-byte) boundary. The "page" option tells the linker to align the segment on a 256-byte page boundary (this has nothing to do with 4K pages). Most systems expect paragraph alignment, so the most common option here is "para"5.

By default, the linker will start each segment in memory on a 4K MMU page boundary. Therefore, if each segment in an HLA program uses only one byte, that program will consume at least 20K because each segment in memory will start on a different 4K boundary. This is why a simple "Hello World" application consumes so much memory - the five default HLA segments each consume 4K of the memory space whether or not the segments actually have 4K of data. The program isn't really 20K long, it's just spread out over the 20K. As you add more code to the "Hello World" program, you'll notice that the executable file doesn't grow in size until you reach some magic point. Then the program jumps in size by increments of 4K (each time a segment's length crosses a 4K boundary, the program grows in length by 4K). If you want the shortest possible executable file, you can tell HLA to combine all the segments into a single segment. However, saving 8K, 12K, or even 16K of data is hardly useful on modern computer systems. Combining segments only saves a significant percentage of the program's size on very tiny programs, so it's not worth the effort for most real applications.

To combine two segments you use the same name for the third parameter in the #code, #data, #static, #readonly, and #const directives. For example, if you want to combine the "CONST" and "readonly" segments into a single memory segment, you can do so with the following two statements (this is actually the default definition):

#readonly( "readonly", "para", "CONST" )
 
#const( "CONST", "para", "CONST" )
 

 

By using the same class names but different segment names you tell the linker to combine these two segments in memory. Of course, you can also combine the two segments by giving them the same segment name, e.g.,

#readonly( "readonly", "para", "readonly" )
 
#const( "readonly", "para", "readonly" )     // This is a bad idea, see below.
 

 

If the particular language you are using doesn't support read-only segments, you should map the "readonly" and "CONST" segments to the "_TEXT" (or equivalent) segment using the "CODE" combine class parameter.

The segment renaming directives do not check the syntax of the strings you specify for the segment name and class fields. These should be legal MASM identifiers and should not be MASM keywords. Generally, legal HLA identifiers work just fine here (unless, of course, you just happen to pick a MASM reserved word). If you specify a syntactically incorrect segment name or class name, HLA will not complain until it attempts to assemble its output file with MASM.

You may only rename the HLA segments once and these directives must appear before the UNIT or PROGRAM statements in an HLA source file. HLA does not allow you to change the name of one of these segments after it has emitted any code for a specific segment. Since HLA emits segment declarations in response to a UNIT or PROGRAM statement, you must do any segment renaming prior to these statements in an HLA source file; i.e., these directives will typically be the very first statements in a source file.

Here are the default segment names, alignments, and class values that HLA uses:

#code( "_TEXT", "para", "CODE" )
 
#static( "_DATA", "para", "DATA" )
 
#storage( "_BSS", "para", "BSS" )
 
#const( "CONST", "para", "CONST" )
 
#readonly( "readonly", "para", "CONST" )
 

 

If you use the MASM-defined names "_TEXT", "_DATA", "_BSS", or "CONST" you must provide the alignment and class parameters given above or MASM will complain when it compile's HLA's output.

6.11 User Defined Segments in HLA (Windows Only)

In addition to the five standard segments, HLA lets you declare your own segments in your assembly programs. Like the five standard segments, you should not confuse HLA segments with 80x86 segments. You do not use the 80x86 segment registers to access data in user-defined segments. Instead, user segments exist as a logical entity to group a set of related objects into the same physical block of memory. In this section we'll take a look at why you would want to use segments and how you declare them in HLA.

It should come as no surprise that when you declare two variables in adjacent statements in a declaration section (e.g., STATIC) that HLA allocates those objects in adjacent memory locations. What may be surprising is that HLA will probably not allocate two variables adjacently in memory if you declare those variables in two adjacent declaration selections. E.g., HLA will allocate i and j below in adjacent memory locations, but it probably will not allocate j and k in adjacent memory locations:

static
 
	i:uns32;
 
	j:int32;
 

 
storage
 
	k:dword;
 

 

The reason k does not immediately follow j in memory is because k is in the "_BSS" segment while i and j are in the "_DATA" segment. Since segments typically start on 4K boundaries, there may be a huge gap between j and k, assuming that the "_BSS" segment follows the "_DATA" segment in memory (and it may not).

Another somewhat surprising result is that HLA (and MASM and the linker) will combine declarations from declaration sections with the same segment name, even if those declarations are not adjacent. Consider the following code:

static
 
	i:uns32;
 
	j:int32;
 

 
storage
 
	k:dword;
 

 
static
 
	m:real32;
 

 

Although j and k probably won't occupy adjacent memory locations, nor will k and m, it is quite possible for j and m to occupy adjacent memory locations since HLA places both declarations in the "_DATA" segment. There is no requirement for HLA to allocate m immediately after j, but HLA will allocate both objects in the same block of physical memory. If you need allocate two variables in adjacent memory locations, or one variable must appear at a lower address than another in memory, you must allocate both objects in the same (physical) declaration sequence. I.e., i and j (in the declarations above) will be allocated in adjacent memory locations with i at the lower address. HLA allocates m in the same segment as i and j, but there's no guarantee that m will appear at a higher or lower address than i and j.

In addition to the five standard segments, HLA lets you define your own memory segments. You use the SEGMENT declaration statement to accomplish this. The SEGMENT statement takes the following form:

segment segName( "alignment", "class" );
 
	<< Declarations >>
 

 

You would use this declaration anywhere a STATIC, READONLY, or STORAGE, declaration section is legal6. Anything legal after a STATIC keyword is legal after the SEGMENT declaration.

The segName field in the declaration above is the name you're going to give this segment. You should choose a unique name and it probably shouldn't be _TEXT, _BSS, _DATA, readonly, or CONST (HLA doesn't prevent the use of these segment names; however, there is little purpose to using most of them since you can create objects in most of these segments using the standard declaration sections). This segment name will automatically be a public name, so you should use an identifier that doesn't conflict with any MASM keywords or other global symbols.

The alignment field must be one of the following strings: "byte", "word", "dword", "para", or "page". This alignment directive has the same meaning as the corresponding string in the segment renaming directives.

The "class" operand specifies the combine class. This field has the same meaning as the combine class operand in the segment renaming directives. Note that, like those directives, this operand must be a string constant. HLA does not check the syntax of this string. It should be a legal identifier that doesn't conflict with any MASM reserved words (just like the segment renaming directives' class field).

Segment names are global, even if you define a segment within a procedure. You may use the same segment name in several different segment declaration sections throughout your program; if you do, HLA (and MASM and the linker) will physically combine all the objects you declare in such a segment.

One nice thing about using different segments for variable declarations is that you physically separate the objects in memory. This reduces the impact of errant programs on data unrelated to the task at hand. For example, if you put each procedure's static variables in their own separate segment, this will reduce the likelihood that one procedure will accidentally overwrite another procedure's data if it oversteps an array bounds by a few bytes. Of course, the procedure can still wipe out its own variables by doing this, however, keeping the values in their own segment localizes the impact and makes it easier to track down this defect in your code. One bad thing about using separate segments for each procedure is that each segment consumes a minimum of 4K of memory; so you're program's executable will contain a lot of empty data if you have a large number of these segments and you don't declare 4K of data in each procedure.

6.12 Controlling the Placement and Attributes of Segments in Memory (Windows Only)

Whenever you compile and HLA program, HLA produces several output files: in particular, HLA produces an ".ASM" file that HLA assembles via MASM, and a ".LINK" file that contains information for the linker. The ".LINK" file controls the placement of segments within memory (when the program actually executes) and it also controls other attributes of segments (such as whether they may contain executable code, whether the segment is writable, etc.). When HLA compiles a program to an executable, it first calls a program named "HLAPARSE.EXE" which is actually responsible for translating the HLA source code to a MASM-compatible ".ASM" file. Then HLA calls the LINK program to link the OBJ files that MASM produces with various library files to produce an executable file7. In addition to passing in the list of OBJ and LIB filenames, HLA also provides the linker with other useful information about segment placement. In this section we'll explore some of the linker options so you can run the linker separately should you want to exercise explicit control over the placement of segments in memory.

To get a (partial) list of the linker options, run the link.exe program with the "/?" command line option. The linker will respond with a list that looks something like the following:

Microsoft (R) Incremental Linker Version 6.00.8168
 
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.
 

 
usage: LINK [options] [files] [@commandfile]
 

 
   options:
 

 
      /ALIGN:#
 
      /BASE:{address|@filename,key}
 
      /COMMENT:comment
 
      /DEBUG
 
      /DEBUGTYPE:{CV|COFF}
 
      /DEF:filename
 
      /DEFAULTLIB:library
 
      /DELAY:{NOBIND|UNLOAD}
 
      /DELAYLOAD:dll
 
      /DLL
 
      /DRIVER[:{UPONLY|WDM}]
 
      /ENTRY:symbol
 
      /EXETYPE:DYNAMIC
 
      /EXPORT:symbol
 
      /FIXED[:NO]
 
      /FORCE[:{MULTIPLE|UNRESOLVED}]
 
      /GPSIZE:#
 
      /HEAP:reserve[,commit]
 
      /IMPLIB:filename
 
      /INCLUDE:symbol
 
      /INCREMENTAL:{YES|NO}
 
      /LARGEADDRESSAWARE[:NO]
 
      /LIBPATH:dir
 
      /LINK50COMPAT
 
      /MACHINE:{ALPHA|ARM|IX86|MIPS|MIPS16|MIPSR41XX|PPC|SH3|SH4}
 
      /MAP[:filename]
 
      /MAPINFO:{EXPORTS|FIXUPS|LINES}
 
      /MERGE:from=to
 
      /NODEFAULTLIB[:library]
 
      /NOENTRY
 
      /NOLOGO
 
      /OPT:{ICF[,iterations]|NOICF|NOREF|NOWIN98|REF|WIN98}
 
      /ORDER:@filename
 
      /OUT:filename
 
      /PDB:{filename|NONE}
 
      /PDBTYPE:{CON[SOLIDATE]|SEPT[YPES]}
 
      /PROFILE
 
      /RELEASE
 
      /SECTION:name,[E][R][W][S][D][K][L][P][X]
 
      /STACK:reserve[,commit]
 
      /STUB:filename
 
      /SUBSYSTEM:{NATIVE|WINDOWS|CONSOLE|WINDOWSCE|POSIX}[,#[.##]]
 
      /SWAPRUN:{CD|NET}
 
      /VERBOSE[:LIB]
 
      /VERSION:#[.#]
 
      /VXD
 
      /WARN[:warninglevel]
 
      /WINDOWSCE:{CONVERT|EMULATION}
 
      /WS:AGGRESSIVE
 

 

Most of these options are very advanced, or of little use to us right now. However, a good number of them are useful on occasion so we'll discuss them here.

/ALIGN:number The number value must be a decimal number and it must be a power of two8. The default (which HLA uses) is 4096. This specifies the default alignment for each segment in the program. You should normally leave this at 4K, but if you write a lot of very short assembly programs you can shrink the size of the executable image by setting this to a smaller value. Note that this number should be at least as large as the largest alignment option (byte, word, dword, para, or page) that you specify for you segments.

The /BASE:address option lets you specify the starting address of the code segment ("_TEXT"). The linker defaults this address to 0x4000000 (i.e., $400_0000). HLA typically uses a default value of 0x3000000 ($300_0000). This leaves room for a 16 Mbyte unused block, a 16 Mbyte stack segment, and a 16 Mbyte heap segment below the code segment in memory (which is where the linker normally puts the stack and heap). If you want a larger heap or stack segment, you should specify a higher starting address with the /BASE linker option.

The /ENTRY:name options specifies the name of the main program. This is the location where program execution begins when Windows first executes the program. For HLA console window applications, the name of the main program is "?HLAMain". Unless you're linking HLA code with a main program written in another language, or you completely understand the HLA start up sequence, you should always use this identifier to specify the entry point of an HLA main program. Note that if you circumvent this entry point, HLA does not properly set up the exception handling facilities and other features of the language. So change this name at your own risk.

/HEAP:reserve,commit This option specifies the amount of memory that the system reserves for the heap. The first numeric value indicates the amount of heap space to reserve, the second parameter specifies the amount of that heap space to actual map into the address space. By default, HLA supplies 0x1000000 ($100_0000, or 16 Mbytes) for both values. This sets aside room for a 16 Mbyte heap and makes all of it available to your program. This is a rather large value for the heap, especially if you write short programs that don't allocate much memory dynamically. For most small applications you may want to set this to a more reasonable (smaller) value. The Windows default is one megabyte (0x100000 or $10_0000). If you don't do much dynamic memory allocation, your code will probably coexist better with other applications if you set this value to 128K (0x20000 or $2_0000). As a general rule, you should set both operands to the same value.

The /MACHINE:IX86 option tells the linker that you're creating code for an 80x86 CPU. You should not attempt to specify a different CPU when using HLA.

/MAP and /MAP:filename. These options tell the linker to produce a map file. The first form, without the optional filename, causes the linker to produce a map file with the same base name as the output file and a suffix of ".map". The second form lets you specify the name of the map file. The map file is a text file that contains several bits of information about the object file. You should produce a map file something and view this information with a text editor to see the kind of information the linker produces. None of this information is usually essential, but it is handy to have now and then. By default, HLA does not produce a map file.

/MERGE:from=to. This option merges the segment (section) named from to to. This will cause the linker to concatenate the two segments in memory. This is roughly equivalent to using the same combine class string in the segment declaration. For example, "/MERGE:readonly=.edata" merges the readonly segment with the CONST segment by concatenating the two.

/OUT:filename. This option specifies the output (executable) filename. By default, HLA appends ".EXE" to the base name of your program and uses that as the executable name. If you would prefer a different name, then use this option to specify the executable file name that LINK produces.

/SECTION:name,options. This option lets you specify the ordering of segments in memory as well as apply attributes to those segments. The ".LINK" file that HLA produces contains a list of /SECTION commands to feed to the linker that specifies the ordering of the segments (by their appearance in the ".LINK" file) and the attributes of those segments. The name field is the segment name. This is a case sensitive field, so the case of name must exactly match the original segment declaration. The options field is a string of one or more characters that specifies the characteristics of that segment in memory. Here are some of the more common options:

Most of the other options are either very advanced, uninteresting. or not applicable to HLA programs. Most segments will have at least one of the E, R, or W options. HLA's default segments generally use the following section options:

/SECTION:.text,ER     -- Note: .text = _TEXT
 
/SECTION:.edata,R     -- Note: .edata = CONST
 
/SECTION:readonly,R
 
/SECTION:.data,RW     -- Note: .data = _DATA
 
/SECTION:.bss,RW      -- Note: .bss = _BSS
 

 

/STACK:reserve,commit. This option is similar to the /HEAP option except it reserves space for the program's stack segment rather than the heap segment. Like the HEAP segment, HLA defaults the stack size to 16 Mbytes (0x4000000 or $400_0000). If you write shorter applications that don't use a lot of local variable space or heavy recursion, you may want to consider setting this value to one megabyte or less, e.g., /STACK:0x100000,0x100000.

/SUBSYSTEM:system. You must supply a subsystem option when you create an executable program. For HLA programs you would normally use "/SUBSYSTEM:CONSOLE" when writing a standard console application. You can use HLA to write GUI applications, if you do this, then you will need to use the "/SUBSYSTEM:WINDOWS" linker option. By default, HLA links your code with the "/SUBSYSTEM:CONSOLE" option. If you use the HLA "-w" command line option, then HLA will invoke the linker with the "/SUBSYSTEM:WINDOWS" option. Of course, if you explicitly run the linker yourself, you will have to supply one of these two options.

The preceding paragraphs explain most of the command line options you'll use when linking programs written in HLA. For more information about the linker, see the Microsoft on-line documentation that accompanies the linker.

If you get tired of typing really long linker command lines every time you compile and link an HLA program, you can gather all the (non-changing) command line options into a linker command file and tell the linker to grab those options and filenames from the command file rather than from the command line. The ".LINK" file that the HLA compiler produces is a good example of a linker command file. The ".LINK" file contains the /SECTION options for the default and user-defined segments found in an HLA program. Rather than manually supplying these options on each call to the linker, you can use a command line like the following:

link @filename.link  other_options file_names
 

 

The at-sign ("@") tells the linker to read a list of commands from the specified command file. Note that you can have several different command files, so if you're compiling and linking several different HLA source files, you can specify the ".link" file for each compilation on the command line.

The filenames you specify on the linker command line should be the names of OBJ and LIB files that you wish to link together. In addition to the OBJ files you've created with HLA, you'll probably want to specify the following library files:

If you don't call any HLA Standard Library routines (unlikely, but possible) then you obviously don't need to specify the hlalib.lib file. Note that it doesn't hurt to specify the name of a library whose members you don't use. The linker will not include any object code from a library unless the program actually uses code or data from that library.

If you're manually linking code that you compile with HLA, you will probably want to create one linker command file containing all the static commands and include that and any appropriate HLA ".LINK" files on the linker command line. Here's a typical example of a static link file (i.e., a file that doesn't get rewritten each time you compile the HLA program):

/heap:0x20000, 0x20000
 
/stack:0x2000, 0x20000
 
/base:0x1000000
 
/machine:IX86
 
/entry:?HLAMain
 
/out:mypgm.exe
 
kernel32.lib
 
user32.lib
 
hlalib.lib
 

 

Generally, you'd use the /SECTION commands from the HLA ".LINK" file unless you wanted to explicitly set the segment ordering or change the attributes of the memory segments.

To run the linker manually, you'd normally tell HLA to perform a compile (and assemble) only operation. This is done using the HLA "-c" command line option. That is, a command like "hla -c myfile.hla" will compile "myfile.hla" to "myfile.asm" and then run MASM to assemble this to "myfile.obj". HLA will not run the linker when you specify the "-c" option. If you prefer, you can run MASM separately by using the "-s" command line option as follows:

hla -s myfile.hla
 
ml -c -Cp -COFF myfile.asm
 

 

However, there is very little benefit to running the assembler yourself (run "MASM /?" to see the available MASM command line options).

Once you've compiled all necessary source files, you can link them by using the Microsoft LINK.EXE program with the command line (or command file) options this section discusses. Note that this section discusses options specific to the LINK.EXE v6.0 product. These features may change in a future version of the linker. Please see the Microsoft documentation if you have any questions about how the linker operates or if you're using a different version of the linker.

6.13 Putting it All Together

CPU architects divide memory into several different types depending on cost, capacity, and speed. They call this the memory hierarchy. Many of the levels in the memory hierarchy are transparent to the programmer. That is, the system automatically moves data between levels in the memory hierarchy without intervention on the programmer's part. However, if you are aware of the effects of the memory hierarchy on program performance, you can write faster programs by organizing your data and code so that it conforms to the expectations of the caching and virtual memory subsystems in the memory hierarchy.

1Remember, NUMA stands for NonUniform Memory Access.

2Though you could easily create macros to do this.

3When this was first written, segments were not yet functional under Linux. This may have changed by the time you read this. They are, however, fully functional under Windows.

4In theory, there is also a stack and a heap segment. However, the linker, not HLA, defines and allocates these two segments. You cannot explicitly declare static objects in these two segments during compilation.

5In fact, MASM requires PARA alignment for the standard segment names. You may only change the alignment if you specify different segment names.

6Well, not really. segment declarations may not appear in classes or namespaces. See the appropriate sections later in this text for a discussion of classes and namespaces.

7If you've got any resource files, HLA will also call the resource compiler, rc.exe, to compile these files. Resource files are beyond the scope of this chapter, so we will ignore them here.

8You can use a hexadecimal value if you specify the number using C/C++ syntax, e.g., "0x123ABC".


Web Site Hits Since
Jan 1, 2000

TOC PREV NEXT INDEX