As the system creates or augments a process image, it logically copies a file's segment to a virtual memory segment. When, and if, the system physically reads the file depends on the program's execution behavior, system load, and so forth.
A process does not require a physical page unless it references the logical page during execution, and processes commonly leave many pages unreferenced. Therefore, delaying physical reads frequently obviates them, improving system performance. To obtain this efficiency in practice, executable and shared object files must have segment images whose file offsets and virtual addresses are congruent, modulo the page size.
Virtual addresses and file offsets for 32-bit segments are congruent modulo 64K (0x10000). Virtual addresses and file offsets for 64-bit segments are congruent modulo 1Mb (0x100000). By aligning segments to the maximum page size, the files are suitable for paging regardless of physical page size.
By default 64-bit SPARC programs are linked with a starting address of 0x100000000. The whole program is above 4 gigabytes, including its text, data, heap, stack, and shared object dependencies. This helps ensure that 64-bit programs are correct by making it so the program will fault in the least significant 4 gigabytes of its address space if it truncates any of its pointers. While 64-bit programs are linked above 4 gigabytes, it is still possible to link them below 4 gigabytes by using a mapfile and the -M option to the compiler or link-editor (see /usr/lib/ld/sparcv9/map.below4G).
The following example presents the SPARC version.
Member |
Text |
Data |
---|---|---|
p_type |
PT_LOAD |
PT_LOAD |
p_offset |
0x0 |
0x4000 |
p_vaddr |
0x10000 |
0x24000 |
p_paddr |
Unspecified |
Unspecified |
p_filesize |
0x3a82 |
0x4f5 |
p_memsz |
0x3a82 |
0x10a4 |
p_flags |
PF_R + PF_X |
PF_R + PF_W + PF_X |
p_align |
0x10000 |
0x10000 |
The following example presents the IA version.
Member |
Text |
Data |
---|---|---|
p_type |
PT_LOAD |
PT_LOAD |
p_offset |
0x0 |
0x4000 |
p_vaddr |
0x8050000 |
0x8064000 |
p_paddr |
Unspecified |
Unspecified |
p_filesize |
0x32fd |
0x3a0 |
p_memsz |
0x32fd |
0xdc4 |
p_flags |
PF_R + PF_X |
PF_R + PF_W + PF_X |
p_align |
0x10000 |
0x10000 |
Although the example's file offsets and virtual addresses are congruent modulo the maximum page size for both text and data, up to four file pages hold impure text or data (depending on page size and file system block size).
The first text page contains the ELF header, the program header table, and other information.
The last text page holds a copy of the beginning of data.
The first data page has a copy of the end of text.
The last data page can contain file information not relevant to the running process. Logically, the system enforces the memory permissions as if each segment were complete and separate; segments' addresses are adjusted to ensure that each logical page in the address space has a single set of permissions. In the examples above, the region of the file holding the end of text and the beginning of data will be mapped twice: at one virtual address for text and at a different virtual address for data.
The examples above reflect typical Solaris system binaries that have their text segments rounded (see Table 8-1).
The end of the data segment requires special handling for uninitialized data, which the system defines to begin with zero values. Thus, if a file's last data page includes information not in the logical memory page, the extraneous data must be set to zero, not the unknown contents of the executable file.
Impurities in the other three pages are not logically part of the process image; whether the system expunges them is unspecified. The memory image for this program follows, assuming 4 Kilobyte (0x1000) pages. For simplicity, these examples illustrate only one page size.
One aspect of segment loading differs between executable files and shared objects. Executable file segments typically contain absolute code. For the process to execute correctly, the segments must reside at the virtual addresses used to create the executable file. Thus the system uses the p_vaddr values unchanged as virtual addresses.
On the other hand, shared object segments typically contain position-independent code. (For background, see Chapter 2, Link-Editor.) This lets a segment's virtual address change from one process to another, without invalidating execution behavior.
Though the system chooses virtual addresses for individual processes, it maintains the segments' relative positions. Because position-independent code uses relative addressing between segments, the difference between virtual addresses in memory must match the difference between virtual addresses in the file.
The following tables show possible shared object virtual address assignments for several processes, illustrating constant relative positioning. The table also illustrates the base address computations.
Table 7-41 SPARC: Example Shared Object Segment Addresses
Source |
Text |
Data |
Base Address |
---|---|---|---|
File |
0x0 |
0x4000 |
0x0 |
Process 1 |
0xc0000000 |
0xc0024000 |
0xc0000000 |
Process 2 |
0xc0010000 |
0xc0034000 |
0xc0010000 |
Process 3 |
0xd0020000 |
0xd0024000 |
0xd0020000 |
Process 4 |
0xd0030000 |
0xd0034000 |
0xd0030000 |
Table 7-42 IA: Example Shared Object Segment Addresses
Source |
Text |
Data |
Base Address |
---|---|---|---|
File |
0x0 |
0x4000 |
0x0 |
Process 1 |
0x8000000 |
0x8004000 |
0x80000000 |
Process 2 |
0x80081000 |
0x80085000 |
0x80081000 |
Process 3 |
0x900c0000 |
0x900c4000 |
0x900c0000 |
Process 4 |
0x900c6000 |
0x900ca000 |
0x900c6000 |
An executable file that participates in dynamic linking can have one PT_INTERP program header element. During exec(2), the system retrieves a pathname from the PT_INTERP segment and creates the initial process image from the interpreter file's segments. That is, instead of using the original executable file's segment images, the system composes a memory image for the interpreter. It then is the interpreter's responsibility to receive control from the system and provide an environment for the application program.
The interpreter receives control in one of two ways. First, it can receive a file descriptor to read the executable file, positioned at the beginning. It can use this file descriptor to read and/or map the executable file's segments into memory. Second, depending on the executable file format, the system can load the executable file into memory instead of giving the interpreter an open file descriptor.
With the possible exception of the file descriptor, the interpreter's initial process state matches what the executable file has received. The interpreter itself can not require a second interpreter. An interpreter can be either a shared object or an executable file.
A shared object (the normal case) is loaded as position-independent, with addresses that can vary from one process to another; the system creates its segments in the dynamic segment area used by mmap(2) and related services. Consequently, a shared object interpreter typically will not conflict with the original executable file's original segment addresses.
An executable file is loaded at fixed addresses; the system creates its segments using the virtual addresses from the program header table. Consequently, an executable file interpreter's virtual addresses can collide with the first executable file; the interpreter is responsible for resolving conflicts.