![]() |
![]() |
| |||||
|
For example, the following dynamic executable prog, references the symbols foo and bar, which are resolved during link-edit from the filter filter.so.1. The execution of prog results in foo being obtained from the filtee filtee.so.1, not from the filter filter.so.1. However, bar is obtained from the filter filter.so.1, as this symbol has no alternative definition in the filtee filtee.so.1.
In these examples, the filtee filtee.so.1 is uniquely associated to the filter, and is not available to satisfy symbol lookup from any other objects that might be loaded as a consequence of executing prog. Auxiliary filters provide a mechanism for defining an alternative interface of an existing shared object. This mechanism is used in the Solaris operating environment to provide optimized functionality within platform specific shared objects. See Instruction Set Specific Shared Objects, and System Specific Shared Objects for examples. Note - The environment variable LD_NOAUXFLTR can be set to disable the runtime linkers auxiliary filter processing. Because auxiliary filters are frequently employed to provide platform specific optimizations, this option can be useful in evaluating filtee use and their performance impact. Filtee ProcessingThe runtime linker's processing of a filter defers the loading of a filtee until a filter symbol is referenced. This implementation is analogous to the filter performing a dlopen(3DL), using mode RTLD_LOCAL, on each of its filtees as they are required. This implementation accounts for differences in dependency reporting that can be produced by tools such as ldd(1). The link-editor's -z loadfltr option can be used when creating a filter to cause the immediate processing of its filtees at runtime. In addition, the immediate processing of any filtees within a process can be triggered by setting the LD_LOADFLTR environment variable to any value. Performance ConsiderationsA shared object can be used by multiple applications within the same system. The performance of a shared object affects the applications that use it and the system as a whole. Although the actual code within a shared object will directly affect the performance of a running process, the performance issues focused upon here target the runtime processing of the shared object itself. The following sections investigate this processing in more detail by looking at aspects such as text size and purity, together with relocation overhead. Analyzing FilesVarious tools are available to analyze the contents of an ELF file. To display the size of a file use the size(1) command. For example:
The first example indicates the size of the shared objects text, data, and bss, a categorization used in previous releases of the SunOS operating system. The ELF format provides a finer granularity for expressing data within a file by organizing the data into sections. The second example displays the size of each of the file's loadable sections. Sections are allocated to units known as segments, some of which describe how portions of a file are mapped into memory (see the mmap(2) man page). These loadable segments can be displayed by using the dump(1) command and examining the LOAD entries. For example:
There are two loadable segments in the shared object libfoo.so.1, commonly referred to as the text and data segments. The text segment is mapped to allow reading and execution of its contents (r-x), whereas the data segment is mapped to also allow its contents to be modified (rwx). The memory size (Memsz) of the data segment differs from the file size (Filesz). This difference accounts for the .bss section, which is part of the data segment, and is dynamically created when the segment is loaded. Programmers usually think of a file in terms of the symbols that define the functions and data elements within their code. These symbols can be displayed using nm(1). For example:
The section that contains a symbol can be determined by referencing the section index (Shndx) field from the symbol table and by using dump(1) to display the sections within the file. For example:
The output from both the previous nm(1) and dump(1) examples shows the association of the functions _init, foo, and _fini to the sections .init, .text and .fini. These sections, because of their read-only nature, are part of the text segment. Similarly, the data arrays data, and bss are associated with the sections .data and .bss respectively. These sections, because of their writable nature, are part of the data segment. Note - The previous dump(1) display has been simplified for this example. Underlying SystemWhen an application is built using a shared object, the entire loadable contents of the object are mapped into the virtual address space of that process at runtime. Each process that uses a shared object starts by referencing a single copy of the shared object in memory. Relocations within the shared object are processed to bind symbolic references to their appropriate definitions. This results in the calculation of true virtual addresses that could not be derived at the time the shared object was generated by the link-editor. These relocations usually result in updates to entries within the process's data segments. The memory management scheme underlying the dynamic linking of shared objects shares memory among processes at the granularity of a page. Memory pages can be shared as long as they are not modified at runtime. If a process writes to a page of a shared object when writing a data item, or relocating a reference to a shared object, it generates a private copy of that page. This private copy will have no effect on other users of the shared object. However, this page has lost any benefit of sharing between other processes. Text pages that become modified in this manner are referred to as impure. The segments of a shared object that are mapped into memory fall into two basic categories; the text segment, which is read-only, and the data segment, which is read-write. See Analyzing Files on how to obtain this information from an ELF file. An overriding goal when developing a shared object is to maximize the text segment and minimize the data segment. This optimizes the amount of code sharing while reducing the amount of processing needed to initialize and use a shared object. The following sections present mechanisms that can help achieve this goal. Lazy Loading of Dynamic DependenciesYou can defer the loading of a shared object dependency until the dependency is first referenced by establishing the object as lazy loadable. See Lazy Loading of Dynamic Dependencies. For small applications a typical thread of execution may reference all the applications dependencies. The application loads all of its dependencies whether they are defined lazy loadable or not. However, under lazy loading, dependency processing may be deferred from process startup and spread throughout the process's execution. For applications with many dependencies, lazy loading often results in some dependencies not being loaded at all. These dependencies are those not referenced for the particular thread of execution. | |||||
| |||||