Porting Unix to the 386: Missing Pieces, Part 1
Completing the 386BSD kernel
William Frederick Jolitz and Lynne Greer Jolitz
Dr. Dobb's Journal
May 01, 1992
Over the next couple of months, we present the glue that lets you build an operational kernel.
Bill was the principal developer of 2.8 and 2.9BSD and was the chief architect of National Semiconductor's GENIX project, the first virtual-memory, microprocessor-based UNIX system. Prior to establishing TeleMuse, a market research firm, Lynne was vice president of marketing at Symmetric Computer Systems. They conduct seminars on BSD, ISDN, and TCP/IP. Send e-mail questions or comments to ljolitz@cardio.ucsf.edu. (c) 1992 TeleMuse.
When we began the 386BSD project in 1989, 386BSD was simply intended to be a port of BSD to the 386. Our purpose in doing 386BSD was so students, faculty, staff, and researchers could use BSD on a simple and inexpensive platform. While we did not wish to add to anyone's proprietary license revenues by folding in new encumbered code (especially pertaining to the 386), removing or redesigning new code to replace old encumbered code was out of the scope of this project. And since we were the only ones willing to work gratis on 386BSD, making an unencumbered version was impossible. After we contributed 386BSD to the University of California at Berkeley (UCB) in December 1990, the UCB staff seriously began to set their sights on releasing only unencumbered code. As you might expect, it was quite a chore to continually update 386BSD and revise it, matching the work done by UCB staff. The result was the University of California Berkeley Networking Software, Release 2 ("NET/2").
In a break with past installments, we've taken the unencumbered but incomplete NET/2 kernel and finished the missing pieces necessary to make a bootable running kernel that provides a self-supporting development environment. In order to make the complete 386BSD kernel, we had to complete some code that was not available when UCB composed the NET/2 tape. Some of these areas, such as execve(), were simply not available at the time, while others (clists, resource maps, buffer cache) were based on obsolete portions of the system due to be replaced by more modern facilities (such as streams and the page cache). In fact, we have found that many of these "new" facilities are still quite far down the road.
We needed to create these "missing pieces," so we used the NET/2 system itself to evolve the eventual replacements for these same pieces. To replace the missing clists, we chose to design ring buffers to function in their stead. In place of the missing resource maps, we invented a more flexible mechanism called a resource list that exploits the dynamic memory-allocation mechanism already present in the NET/2 kernel. For the buffer-based I/O mechanism and program-execution system call, we relied on reference materials to design a suitable substitute to serve us while we slowly work on our own, newer version of a page cache which utilizes a completely different approach.
While we still intend to emphasize innovative work, we are constrained within the confines of the present. Thus, we have chosen the shortest path to completion of the 386BSD goal, by implementing facilities that will dock to the rest of the NET/2 kernel source with minimal change. In fact, the source code presented over the next two installments--plus a small set of bug fixes and a recent copy of the NET/2 tape (available via M&T Online)--will enable you to build an operational kernel. Before you go running for the phone, please realize that besides a kernel, you need bootstraps (DDJ, February 1991), binaries of the utilities (DDJ, March, April, May 1991), a root file system (DDJ, July, August, September, and October 1991), an installation mechanism (DDJ, February and May 1991 as well as March and April 1992), and documentation (DDJ, January through November 1991, and February 1992 onward) before making 386BSD a real bootable system.
Readers who have followed this series now have enough material and information to put in the elbow grease and finish it on their own. For those with less patience, we hope to follow this up shortly with a real 386BSD binary that can be put on a PC without major travail.
386BSD Kernel Completion Methodology
The methodology we followed to complete the 386BSD NET/2 kernel was critical to our success. We began with an examination of the documentation that described how each of these missing facilities managed to function, and then reviewed the interface structure within the NET/2 kernel. From this information, we created a model of semantics for each of the missing facilities. Among the references we found useful were Maurice Bach's The Design of the UNIX Operating System, Tanenbaum's MINIX series, the BSD book, the UNIX System V Programmers Reference Manual, Knuth's The Art of Computer Programming, as well as selected readings from USENIX Technical Proceedings over the years. Comparing these references is interesting because they all contain different perspectives which "color" the puzzle piece slightly differently. Using these reference materials, we wrote the first versions of these modules from scratch. Then, by examining functional entry points that called the prototype version, we discovered weaknesses in the original assumptions--some of which required significant revision. This approach also allowed us to construct a realistic test program around the intended kernel code that facilitated developing and debugging code in a user-process "framework."
Although modern kernel debuggers and other development tools are now ubiquitous, isolated debugging is still valuable because it allows you to circumscribe the problem more efficiently. For example, a number of bugs in the NET/2 kernel which had been masked by the older missing code were discovered by this process. The independent validation that isolated, user-mode development provides is a valuable tool for pinpointing problem areas in the NET/2 code as well as in the new software.
Once a rough version was made to work in a user framework, the complex cases were tested to localize implementation problems. It's said that 90 percent of your bugs come from less than 1 percent of your source code. However, you can usually guess where the trouble will strike, and that's exactly where you target your test vectors. Unfortunately, many programmers shy away from this procedure, preferring to visually "inspect" the code instead of doing the "acid test." For example, all the bugs in the ring-buffer code were located in the boundary-crossing cases on normal and inverse functions. There were analogous cases with the contiguous GET/PUT operations, so these too needed to be examined at the boundaries. In one case, we made the ring buffers absurdly short to exacerbate the problems on the boundaries, and in doing so, we exposed other inadequacies as well.
Next, we had to contend with the environmental and interface demands of the kernel. No user program framework can hope to simulate the interrupt-driven, context-switched, race-prone environment of a basic friendly kernel (with the result that the system wedges a lot). Unlike Mach, which hopes to export this environment to user processes under the guise of kernelizing the system (the system still wedges, but the problems occur in the user environment, making it more difficult to localize the problem), we prefer that the kernel environment not be a part of the isolated test framework. When we step into the kernel, it's an all-or-nothing proposition, because the mechanisms interact greatly. This complexity of interaction is always present, whether it stays in the kernel or gets exported to a user process.
The methods through which we regulate the introduction of new code into the kernel are the key to ensuring that code's proper operation. We leverage our understanding of the entry points in order to examine and track the actual requests passed to our new code and compare them against what our user-mode test model does.
In a sense, we end up turning our debugging process inside out. Instead of producing cases to perturb the inner workings of the new code, we debug the interfaces and the outer procedures that call it. We do this methodically, testing the boundary cases of each as we go and looking for unexpected assumptions about the semantics of the new code.
In the case of the buffer cache, this method caught significant "gotchas." Much of the semantics of the NET/2 buffer cache are implied by the surrounding code. (For example, the rescaling of buffer size, buffer invalidation, and forcing to back store are all intertwined with the virtual file system and subsidiary file-system layers.) This could, in part, explain why it has appeared so difficult to replace the obsolete buffer cache. Its semantics are spread all over the map!
With our methodology in place, we began to finish off the missing pieces, arriving, ultimately, at 386BSD Unbound.
386BSD Resource Lists
One part missing from the NET/2 386BSD kernel is the facility for dense storage, or region allocation, known in Berkeley UNIX vernacular as the resource maps. Resource maps were created in 4BSD as a generalization of the "core click" physical-memory allocator found in the original Version 6 Bell Laboratories UNIX for the PDP-11. They were widely used in the older 4BSD virtual-memory system. However, in the NET/2 kernel's virtual-memory system, they are only used to allocate contiguous hunks of swap space to contain swapped-out processes. (Incidentally, the term "map" here is misleading, as this has nothing to do with the virtual-memory system's use of the term "map" to describe using the processor's address-translation hardware.)
Resource maps work by describing allocatable segments as a two-tuple (index, size). These two-tuples are stored in a contiguous array of fixed size. As allocations are made from the map, the segments fragment and take up increasing space in the array. When fragments are logically returned to the map, the free() procedure glues the fragments back together and attempts to shrink space in the array. If the array is large enough, the worst possible fragmentation cannot exceed the size of the resource map.
Resource maps, while elegant, compact, and quick for PDP-11 memory allocation, have some annoying drawbacks. To make a fast implementation, the "0th" index is not usable, because it is indistinguishable from "nothing" on the list (in other words, it's used as a sentinel), and the entry that corresponds to it is used to hold the upper-bounds limit and name of the given resource map instance. As a result, the caller either needs to relocate the range above 0 before handing it to the resource map routines or discard the first allocation unit (the "0th" index). In addition, the size and extent of the resource map is fixed at initialization time and is unalterable, so storage for the map must be reserved for the "worst-case size." If you guess wrong at worst case, you're screwed!
In many early UNIX systems, some kernels never bounds-checked these arrays at all, and would merrily scribble all over the next adjacent memory locations after the map! The system would then run for a period of time afterward, and, when the inevitable crash occurred, it appeared to come from an unrelated portion of the system. Of course, by that time, the map had become less fragmented, and its contents showed no irregularity; in other words, an almost "self-healing" bug. Those who had discovered this clever trick protected the map with a "bounds check" which caused a system panic to occur if the map fragmented outside the array. After a while, it became tedious to recompile the kernel with greater and greater map sizes, so instead of panicking, the resource map allocator would just "drop" the fragment. This approach had humorous side effects on large time-sharing systems, when the fragment dropped turned out to be something important, such as half of all available swap space, because large fragments tend to collect on the end of the list.
This static mechanism has been tolerated for so long partly because it's been used as the bottom-level storage allocator, and allowing it to use dynamic allocation was considered unwise. (For example, what if it fragmented when low on memory?)
Many design decisions in the kernel change when dynamic memory allocation becomes available, so we replaced the fixed allocation resource maps with resource lists built out of a pay-as-you-go dynamic memory-allocation scheme.
Resource Lists Defined
Resource lists (see Listing One) are arbitrary-length lists, each element of which describes a segment as an inclusive [start, end] two-tuple. The list elements are kept in a sorted order, with fragmenting entries causing spontaneous new entries to be allocated (via malloc). As segments are freed, allowing holes to be filled and fragments to be reassembled, adjacent entries are reduced to single ones, and the now superfluous list entries are freed and returned to the dynamic memory allocator. Thus, no loss of storage need occur. The price we pay is the added cost of dynamic allocation. which is generally small compared to the number of times our resource lists are used.
Other advantages of resource lists stem from their dynamic nature. There is no initialize function, only allocate and free entry calls, because initialization is just passing free space to an otherwise empty list, in any order and at any time. This allows us, for example, to add additional swap space without having to reinitialize the resource map nor reserve space ahead of time. Also, because the full dynamic range is present, segments starting and ending at any point in a 32-bit number's range can be used.
rlist_alloc(). The resource list allocate function (see Listing Two) traipses down a linked list looking for a large enough region from which to allocate. In doing this, it uses a single doubly indirect pointer to check for an unallocated entry (a null rlist pointer) as well as hold a pointer to the forward link (in case we need to restructure the list). When we find an entry of the appropriate size, we optionally pass it back it's location and reduce the size of the resource-list entry. (The caller might not want it, but this ensures it won't be allocated by others.) If we reduce the size to the point that the element is empty, we free the list-entry space and rewire the previous list's pointer to the succeeding entry (if one exists).
rlist_free(). The resource list free function (see Listing Two) is beefier, as it must glue the fragments back together and attempt to simplify them (that is, represent the fewest list entries). This routine is actually attempting to reverse the damage from the numerous unordered allocations and frees prior to being called. Like rlist_alloc(), it walks the list with a doubly indirect pointer, but it searches for a list element it can merge into or a point in the sorted list where it can be inserted. If a merge occurs, this function scans the entire list, trying to reduce adjacent entries that can be merged as the result of a hole being filled.
Program Execution Function
Another missing piece from the NET/2 kernel involves execution of a program from a file, a critical part of any system. At some point, we must load a file of binary 386 instructions and execute them. In many UNIX implementations, this is one of the most complicated system calls in the entire kernel.
Executable File Format
The executable file formats to choose from on the 386 include COEFF, ELF, ROSE, X.OUT, A.OUT, and variants. All have different advantages and adherents, and even Intel's BCS2 (Binary Compatibility Standard) does not give a single conclusive choice for an executable format. Unlike MS-DOS, where an .EXE file is the same wherever you go, UNIX compatibility is less certain.
To make a basic functional system, we implemented a single executable format. It needed to be one our GNU linkage editor already generated, so we chose the type 413 style of A.OUT, the original executable file format of UNIX, named for the octal value of the magic number leading the header in front of the executable file. (A.OUT is short for Assembler OUTput file. With no other arguments, a UNIX assembler drops its object file into "a.out" in the current directory. On the PDP 11, such files were directly executable by the system's exec(), and would work if they did not have any undefined external references that the loader/link editor would need to satisfy from other object files or libraries.)
This format was originally created in 3BSD for the VAX, specifically to allow use of paging. Its prevalence is due mainly to it being around for a long time and luckily for us, it is perpetuated in the GNU loader. The format consists of a "page-cluster" sized header, followed by pages of instruction space (to be marked for "read-only" access) and ending with pages of initialized data (to be marked for "read/write" access).
A page cluster is a group of pages logically considered to be a single page. On a VAX, with a tiny page size of 512 bytes, clusters of 1 Kbyte reduced paging traffic at the expense of increased fragmentation loss. With the 386's 4Kbyte pages, we chose a cluster size of 1, because the pages are of adequate size. Alignment isn't a bad thing, especially at a sector (or block) granularity level, so a page fault can be satisfied with an integral number of contiguous disk transfers. However, this results in some wasted space. An alternative is to put the header either at the rear of the file (where it becomes a trailer) or include the header as part of the address space mapped by the ffle (the first words of instruction space). Type 407, the original UNIX executable, worked this way because octal 400 was the jump instruction, and an offset of 7 caused it to jump over the eight-word header to the first instruction following the header!
As on the VAX (see Listing Three), we assume that instructions are to be mapped to virtual address 0 on up to the end of text (a_text), where the data pages start and continue, including so-called "BSS pages" (uninitialized data or a_bss). When the system runs the program, it assigns (somewhere) and creates a stack segment, packs it with arguments, and enters the program in user mode at a location (a_entry) designated in the header. This type 413 is the predominate executable format on Berkeley UNIX systems, as it allows for sharing of the read-only pages and protection of user instructions from accidental or deliberate modification.
For the purposes of basic operation, type 413 executable file format is sufficient. It's far from ideal, however, because the 4-Kbyte header page on the front wastes space. Another rub is that location 0 is mapped with a valid page, and at times we would like to detect access to the uninitialized pointers that frequently appear as NULL pointers. Such pointers can conceivably point to large structures, so the actual illegal reference we want to trap might not occur at 0, but "near" it (perhaps within 64 Kbytes). Therefore, we might need to avoid putting anything at the bottom of the virtual address space, which conflicts with the way type 413 defines its instruction segment to work. On the 386 not only could a user program have NULL pointers to be caught, but the kernel could as well, because they share the same virtual address space.
Additionally, our new kernel uses dynamic memory allocation, and a common problem with this occurs when an unintended reference is made to a (stale) pointer to a freed (and frequently reassigned) memory region. Such areas are often cleared (set to 0) before use! The upshot of this is that kernel NULL pointers are a common problem made more difficult to diagnose because 0 is mapped, thus masking the problem.
Executable file format also impacts the development of a method for sharing commonly referenced code among files. "Shared-object libraries" reduce the amount of disk space taken up by executable files by making one physical copy available to many programs simultaneously. The copy is stored separately from all the interlinked executables. This can result in considerable space savings, especially by the multimegabyte X libraries and toolkits. Shared libraries (and potentially dynamic linking) also provide mechanisms to manage the ever-growing complexity of modern software, by exploiting it in an object-oriented fashion.
Given these demands, it's tempting to create yet another executable file format, but this must be considered carefully, as it could affect future editions of 386B5D.
Next Month
In next month's installment, we'll implement a "bare-bones" execve() system call that allows 386BSD to provide basic operation, a block I/O buffer cache used to reduce the cost of UNIX file operations, and ring buffers that reduce the cost of tty-character buffer management.
DDJ
Listing One
/* Copyright (c) 1992 William Jolitz. All rights reserved. * Written by William Jolitz 1/92 * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: This software is a component * of "386BSD" developed by William F. Jolitz, TeleMuse. * 4. Neither the name of the developer nor the name "386BSD" may be used to * endorse or promote products derived from this software without specific * prior written permission. * THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ AND * IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS SOFTWARE SHOULD * NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT. THE DEVELOPER URGES THAT USERS * WHO REQUIRE A COMMERCIAL PRODUCT NOT MAKE USE OF THIS WORK. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE DEVELOPER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * Resource lists. Usage: * rlist_free(&swapmap, 100, 200); add space to swapmap * rlist_alloc(&swapmap, 100, &loc); obtain 100 sectors from swap */ /* A resource list element. */ struct rlist { unsigned rl_start; /* boundaries of extent - inclusive */ unsigned rl_end; /* boundaries of extent - inclusive */ struct rlist *rl_next; /* next list entry, if present */ }; /* Functions to manipulate resource lists. */ extern rlist_free __P((struct rlist **, unsigned, unsigned)); int rlist_alloc __P((struct rlist **, unsigned, unsigned *)); extern rlist_destroy __P((struct rlist **)); /* heads of lists */ struct rlist *swapmap;
Listing Two
/* Copyright (c) 1992 William Jolitz. All rights reserved. * Written by William Jolitz 1/92 * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: This software is a component * of "386BSD" developed by William F. Jolitz, TeleMuse. * 4. Neither the name of the developer nor the name "386BSD" may be used to * endorse or promote products derived from this software without specific * prior written permission. * THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ AND * IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS SOFTWARE SHOULD * NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT. THE DEVELOPER URGES THAT USERS * WHO REQUIRE A COMMERCIAL PRODUCT NOT MAKE USE OF THIS WORK. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE DEVELOPER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "sys/param.h" #include "sys/cdefs.h" #include "sys/malloc.h" #include "rlist.h" /* Resource lists. */ /* Add space to a resource list. Used to either * initialize a list or return free space to it. */ rlist_free (rlp, start, end) register struct rlist **rlp; unsigned start, end; { struct rlist *head; head = *rlp; loop: /* if nothing here, insert (tail of list) */ if (*rlp == 0) { *rlp = (struct rlist *)malloc(sizeof(**rlp), M_TEMP, M_NOWAIT); (*rlp)->rl_start = start; (*rlp)->rl_end = end; (*rlp)->rl_next = 0; return; } /* if new region overlaps something currently present, panic */ if (start >= (*rlp)->rl_start && start <= (*rlp)->rl_end) panic("overlapping rlist_free: freed twice?"); if (end >= (*rlp)->rl_start && end <= (*rlp)->rl_end) panic("overlapping rlist_free: freed twice?"); /* are we adjacent to this element? (in front) */ if (end+1 == (*rlp)->rl_start) { /* coalesce */ (*rlp)->rl_start = start; goto scan; } /* are we before this element? */ if (end < (*rlp)->rl_start) { register struct rlist *nlp; nlp = (struct rlist *)malloc(sizeof(*nlp), M_TEMP, M_NOWAIT); nlp->rl_start = start; nlp->rl_end = end; nlp->rl_next = *rlp; *rlp = nlp; return; } /* are we adjacent to this element? (at tail) */ if ((*rlp)->rl_end + 1 == start) { /* coalesce */ (*rlp)->rl_end = end; goto scan; } /* are we after this element */ if (start > (*rlp)->rl_end) { rlp = &((*rlp)->rl_next); goto loop; } else panic("rlist_free: can't happen"); scan: /* can we coalesce list now that we've filled a void? */ { register struct rlist *lp, *lpn; for (lp = head; lp->rl_next ;) { lpn = lp->rl_next; /* coalesce ? */ if (lp->rl_end + 1 == lpn->rl_start) { lp->rl_end = lpn->rl_end; lp->rl_next = lpn->rl_next; free(lpn, M_TEMP); } else lp = lp->rl_next; } } } /* Obtain a region of desired size from a resource list. If nothing available * of that size, return 0. Otherwise, return a value of 1 and set resource * start location with *loc. (Note: loc can be zero if we don't wish value) */ int rlist_alloc (rlp, size, loc) struct rlist **rlp; unsigned size, *loc; { register struct rlist *lp = *rlp, *olp = 0; /* walk list, allocating first thing that's big enough (first fit) */ for (; *rlp; rlp = &((*rlp)->rl_next)) if(size <= (*rlp)->rl_end - (*rlp)->rl_start + 1) { /* hand it to the caller */ if (loc) *loc = (*rlp)->rl_start; (*rlp)->rl_start += size; /* did we eat this element entirely? */ if ((*rlp)->rl_start > (*rlp)->rl_end) { lp = (*rlp)->rl_next; free (*rlp, M_TEMP); *rlp = lp; } return (1); } /* nothing in list that's big enough */ return (0); } /* Finished with this resource list, reclaim all space and * mark it as being empty. */ rlist_destroy (rlp) struct rlist **rlp; { struct rlist *lp, *nlp; lp = *rlp; *rlp = 0; for (; lp; lp = nlp) { nlp = lp->rl_next; free (lp, M_TEMP); } }Listing Three
/* Excerpted with permission from 4.3BSD include file * "/usr/include/sys/exec.h" * Redistribution and use in source and binary forms are freely permitted * provided that the above copyright notice and attribution and date of work * and this paragraph are duplicated in all such forms. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * Header prepended to each a.out file. */ struct exec { long a_magic; /* magic number */ unsigned long a_text; /* size of text segment */ unsigned long a_data; /* size of initialized data */ unsigned long a_bss; /* size of uninitialized data */ unsigned long a_syms; /* size of symbol table */ unsigned long a_entry; /* entry point */ unsigned long a_trsize; /* size of text relocation */ unsigned long a_drsize; /* size of data relocation */ }; #define OMAGIC 0407 /* old impure format */ #define NMAGIC 0410 /* read-only text */ #define ZMAGIC 0413 /* demand load format */
Previous installments:
April 1992 - Porting Unix to the 386: DEVICE DRIVERS - Our discussion of 386 BSD device drivers continues, focusing on interrupt routines.
March 1992 - Porting Unix to the 386: DEVICE DRIVERS - Entering, exiting, and masking various processor interrupts are covered this month.
February 1992 - Porting Unix to the 386: DEVICE DRIVERS - This month, our authors continue their discussion of support macros and functions of the 386BSD kernel that drivers use to work devices.
November 1991 - Porting Unix to the 386: THE BASIC KERNEL - It's necessary to understand UNIX device interfaces before integrating device drivers. Bill and Lynne examine BSD kernel/device driver interfaces and how BSD works the miracle of autoconfiguration.
October 1991 - Porting Unix to the 386: THE BASIC KERNEL - Bill and Lynne continue their exploration of multiprogramming and multitasking.
September 1991 - Porting Unix to the 386: THE BASIC KERNEL - Multiprogramming is the focus of this month's installment.
August 1991 - Porting Unix to the 386: THE BASIC KERNEL - The 386BSD kernel services and data structures are initialized in this month's installment.
July 1991 - Porting Unix to the 386: A STRIPPED-DOWN KERNEL - 386BSD's basic kernel incorporates a unique "recursive" paging feature that leverages resources and reduces complexity.
Copyright 1992