Mac OS Runtime Architectures
This article describes the Mac OS runtime architecture based upon the Code Fragment Manager (CFM) as well as the original classic 68K runtime architecture.
- The CFM-based runtime architecture was originally conceived and designed to run on PowerPC-based computers running the Mac OS. A 68K implementation, called CFM-68K, was later created to allow 68K-based machines to run CFM-based code.
- The classic 68K runtime architecture is the architecture created for the original 68K-based Macintosh computer.
A runtime architecture is a fundamental set of rules that defines how software operates. These rules define
- how to address code and data
- how to handle and keep track of programs in memory (multiple applications and so on)
- how compilers should generate code (for example, does it allow self-modifying code?)
- how to invoke certain system services
Architectures are platform-independent, although the implementation of an architecture may vary from machine to machine depending on the features (or constraints) available.
CFM-Based Runtime Architecture
The CFM-based runtime architecture relies on fragments and the Code Fragment Manager (CFM) for its operation. This architecture has been implemented as the default architecture for PowerPC-based Mac OS computers and an optional one, CFM-68K, for 68K-based machines. The key concepts are identical for both implementations, so you should read this chapter if you plan to write either PowerPC or CFM-68K code.
In the CFM-based architecture, a fragment is the basic unit of executable code and its associated data. All fragments share fundamental properties such as basic structure and method of addressing code and data. The major advantage of a fragment-based architecture is that a fragment can easily access code or data contained in another fragment. For example, a fragment can import routines or data items from another fragment or export them for another fragment's use. In addition, fragments that export items may be shared among multiple clients.
Note: The term fragment is not intended to suggest that the block of code and data is in any way either small, detached, or incomplete. Fragments can be of virtually any size, and they are complete, executable entities. The term fragment was chosen to avoid confusion with the terms already used in Inside Macintosh volumes to describe executable code (such as component and module).
The Code Fragment Manager handles fragment preparation, which involves bringing a fragment into memory and making it ready for execution. Fragments can be grouped by use into applications and shared libraries, but fundamentally the Code Fragment Manager treats them alike.
Fragment-based applications are launched from the Finder. Typically they have a user interface and use event-driven programming to control their execution.
A shared library, however, is a fragment that exports code and data for use by other fragments. Unlike a traditional static library, which the linker includes in the application during the build process, a shared library remains a separate entity. For a shared library, the linker inserts a reference to an imported function or data item into the client fragment. When the fragment is prepared, the Code Fragment Manager creates incarnations of the shared libraries required by the fragment and binds all references to imported code and data to addresses in the appropriate libraries. A shared library is stored independently of the fragment that uses it and can therefore be shared among multiple clients.
Note: Shared libraries are sometimes referred to as dynamically linked libraries (DLLs), since the application and the externally referenced code or data are linked together dynamically when the application launches.
Using a shared library offers many benefits based on the fact that its code is not directly linked into one or more fragments but exists as a separate entity that multiple fragments can address at runtime. If you are developing several CFM-based applications that have parts of their source code in common, you should consider packaging all the common code into a shared library.
Here are some ways to take advantage of shared libraries:
- An application framework can be packaged as a shared library. This potentially saves a good deal of disk space because that library resides only once on disk--where it can be addressed by multiple applications--rather than being linked physically into numerous applications.
- System functions and tools, such as OpenDoc, can be packaged as shared libraries.
- Updates and bug fixes for a single library can be released without the need to recompile and send copies of all the applications that use the library.
Shared libraries come in two basic forms:
- Import libraries. These contain code and data that your application requires to run. The Code Fragment Manager automatically prepares these libraries at runtime. Import libraries do not occupy application memory but are stored separately.
- Plug-ins. These are libraries that provide optional services, such as a spelling checker for a word processor. The application must make explicit calls to the Code Fragment Manager to prepare these libraries and must then find the symbols associated with the libraries. Plug-ins are sometimes referred to as drop-in additions or extensions.
Note: Although the terms are similar, shared library and import library are not interchangeable. An import library is a shared library, but a shared library is not necessarily an import library.
In the CFM-based runtime architecture, the Code Fragment Manager handles the manipulation of fragments. Some of its functions include
- mapping fragments into memory and releasing them when no longer needed
- resolving references to symbols imported from other fragments
- providing support for special initialization and termination routines
Fragments can be shared within a process or between two or more processes. A process defines the scope of an independently-running program. Typically each process contains a separate application and any related plug-ins.
The physical incarnation of a fragment within a process is called a connection. A fragment may have several unique connections, each local to a particular process. Each connection is assigned a connection ID.
Fragments are physically stored in containers, which can be any kind of storage area accessible by the Code Fragment Manager. For example, in System 7 the system software import library InterfaceLib is stored in the ROM of a PowerPC-based Macintosh computer. Other import libraries are typically stored in files of type 'shlb'. Fragments containing executable code are usually stored in the data fork of a file, although it is possible to store a fragment as a resource in the resource fork.
The Code Fragment Manager uses the concept of a closure when handling fragments. A closure is essentially a set of connection IDs that are grouped according to the order they are prepared. The connections represented by a closure are the root fragment, which is the initial fragment the Code Fragment Manager is called to prepare, and any import libraries the root fragment requires to resolve its symbol references.
During the fragment preparation process, the Code Fragment Manager automatically prepares all the connections required to make up a closure. This process occurs whether the Code Fragment Manager is called by the system (application launch) or programmatically from your code (for example, when preparing a plug-in).
The Structure of Fragments
Every fragment can contain separate code and data sections. A code or data section can be up to 4 GB in size. Code and data sections do not have to be contiguous in memory.
Note: Since all fragments can contain both code and data sections, any fragment can contain global variables.
A code section contains position-independent executable code (that is, code that is independent of its own memory location and the location of its associated data). Code sections are read-only, so fragments can be stored in ROM or file-mapped and paged in from disk as necessary.
A data section is typically allocated in the application heap. Each data section may be instantiated multiple times, creating a separate copy for each connection associated with the fragment. An import library's data section may also be placed into the system heap or temporary memory (when systemwide instantiation is selected).
Although a fragment's code and data sections can be located anywhere in memory, those sections cannot be moved within memory once they are prepared. The Code Fragment Manager must resolve any dependencies a fragment might have on other fragments, and this preparation involves placing pointers to imported code and data into the fragment's data section. To avoid having to prepare fragments in this way more than once, the Mac OS requires that a prepared fragment remain stationary as long as it stays in memory.
Note: Accelerated resources, which model the behavior of classic 68K resources, do not have to be fixed in memory between calls.