Chapter 19. Portability
Linux is a portable operating system that supports a wide range of computer architectures. Portability refers to how easilyif at allcode can move from one system architecture to another. We all know that Linux is portable because it has already been ported to various systems. But this portability did not occur overnightit required many important design decisions along the way. Consequently, it is now easy (relatively speaking) to bring Linux up on a new system. This chapter discusses how to write portable codethe issues you need to keep in mind when writing core kernel code or device drivers.
Some operating systems are designed with portability as a primary feature. As little code as possible is machine specific. Assembly is kept to a minimum and interfaces and features are sufficiently general and abstract that they work on a wide range of architectures. The obvious benefit is the relative ease with which a new architecture can be supported. In some cases, highly portable and simple operating systems can be moved to a new architecture with just a few hundred lines of unique code. The downside is that architecture-specific features are not supported and code cannot be hand-tuned for a specific machine. With this design choice, optimal code is traded for portable code. Some examples of highly portable operating systems are Minix, NetBSD, and many research systems.
On the opposite side are operating systems that trade all portability for highly customized optimum code. As much as possible, code is written in assembly or otherwise designed for a specific architecture. Kernel features are designed around specific archi-tectural features. Consequently, moving the operating system to a new architecture is tantamount to rewriting the kernel from scratch. With this design decision, portable code is traded for optimal code. Such systems are often harder to maintain than more portable systems (DOS and Windows 9x). Now these systems need not be more optimal than a more portable system; their willingness to disregard portability, however, allows for a no-compromise design.
Linux takes the middle road toward portability. As much as practical, interfaces and core code are architecture-independent C code. Where performance is critical, however, kernel features are tuned for each architecture. For example, much fast-path and low-level code is architecture dependent and often written in assembly. This approach enables Linux to remain portable without forgoing optimizations. Where portability would hinder performance, performance generally wins. Otherwise, code is kept portable.
Generally, exported kernel interfaces are architecture independent. If any parts of the function need to be unique for each supported architecture (either for performance reasons or as a necessity), that code is implemented in separate functions and called as needed. Each supported architecture then implements its architecture-specific functions and links them into the kernel image.
A good example is the scheduler. The large majority of the scheduler is written in architecture-independent C and lives in kernel/sched.c. A few jobs of the scheduler, such as switching processor state or switching the address space, are very architecture dependent. Consequently, the C method context_switch(), which switches from one process to another, calls the methods switch_to() and switch_mm(), to switch processor state and switch address space, respectively.
The code for switch_to() and switch_mm() is uniquely implemented by each architecture that Linux supports. When Linux is ported to a new architecture, the new architecture must provide an implementation for these functions.
Architecture-specific files are located in arch/architecture/ and include/ asm-architecture/, where architecture is a short name representing each architecture in Linux. As an example, the Intel x86 architecture is given the short name i386. Architecture-specific files for these machines live in arch/i386 and include/asm-i386. The supported architectures in the 2.6 kernel series are alpha, arm, cris, h8300, i386, ia64, m32r, m68k, m68knommu, mips, mips64, parisc, ppc, ppc64, s390, sh, sparc, sparc64, um, v850, and x86-64. A more complete listing of these architectures is in Table 19.1, later in this chapter.