Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Porting Unix to the 386: the Standalone System


MAR91: PORTING UNIX TO THE 386: THE STANDALONE SYSTEM

Bill was the principal developer of 2.8 and 2.9 BSD and the chief architect of National Semiconductor's GENIX. Lynne established TeleMuse, a market research firm specializing in the telecommunications and electronics industry. They can be contacted via e-mail at william@ berkeley.edu or at uunet! william. Copyright (c) 1990 TeleMuse.


This is the third article in this series, and at this point we feel it is important to examine just what we have accomplished so far. In our first article, we arrived at e sentially a "plan of action," outlining what we understand to be the important goals of our project, as well as discussing (as always, in hindsight) some of the important technical decisions made in the process of completing our successful port of BSD to the 80386. In the second article, we wrote three programs (using Turbo C and MASM) to prepare our host for the beginnings of this port by creating the basic tools. We are now at the point of departure, where the goal itself can become all-consuming.

"Why all the drama?" one may ask. Well, what we are about to embark on may be considered, in a sense, like a mountaineering expedition to K2. We have done all the scheduling and planning and assembled the consumables and equipment. Now, here we sit at the base of the mountain, staring up at its intimidating peak and contemplating our first steps with both anticipation and dread. Projects of great complexity are always uncertain.

In this case, our mountain is an empty 386 residing in protected mode. There is not one shred of code that we can rely on. One false step can cause a spontaneous reset, or worse yet, a hang. Please believe us when we say that it takes a lot of courage to take on such projects. Now one must shrug one's shoulders of any uncertainty and begin to place one foot in front of the other and scale the foothills. We must establish our base camp from which we can explore further.

In this article, we endeavor to scale those foothills and establish our base camp by building upon our previous work; using our protected-mode program loader, we can create a minimal 80386 protected-mode standalone C programming environment for operating systems kernel development work. Next, we must write prototype code for various kernel hardware support facilities. Finally, we must use our standalone programming environment as a test bed to shake out the bugs in our first-cut implementation of kernel 386 machine-dependent code in preparation for incorporation in the BSD kernel.

At this point, the neophyte tends to ask the question (and it is a good question, mind you) "Why spend so much time on small programs, prototype code, and the like, instead of getting into the hard stuff?" Yes, it does appear on the surface that one should start shoveling this huge operating system through the compiler and onto our host. (At this very instant of writing, the BSD kernel consists of 128,332 lines, according to wc -l, and supports roughly 150 Mbytes of user-level sources -- sorry, can't wait to consult wc on that!)

Besides being a bit of a bore, just what would happen if we jumped into compiling code willy-nilly? In sum, it would be a complete disaster, as we would spread all of our latent bugs and misconceptions over a much broader body of code. Worse yet, all these different bugs would be well-distributed throughout our code and hence not easily differentiated or ordered.

A simple beginning affords us the chance to find various bugs early, when the problem still has a chance of being resolved to our satisfaction. We have plenty of land mines to avoid as it is, without adding to our troubles.

Watching for Land Mines

Porting 386BSD was definitely an eerie return to the basics; using the protected-mode loader, we bulldozed MS-DOS right off the top and were left with an empty machine that we incrementally built up to a functioning UNIX system. At this stage in the port, an unlocalizable or nondeterministic bug is a very real and costly possibility that can stall a porting project for months.

At this point, a crafty programmer can use subtle techniques which anticipate the sources of error and enable them to be identified and corrected in a predictable and orderly manner. In a famous discussion, Donald E. Knuth wrote of how he was able to greatly reduce the time it took to debug a new compiler by anticipating worst-case test conditions and "stress testing" by use of the adversary method. We employ similar techniques by testing the mechanisms used in the kernel separately from the vast body of code that is the kernel. This also has the added benefit of differentiating problems in the code from compiler and assembler bugs that are almost certain to be present. This is not a method that guarantees success, but it is much better to seek out trouble rather than wait for it to find you.

Serious thought was given to implementing a tiny debugger to facilitate this stage of the port. PC debuggers were also examined as a possible tool to ease this effort. However, it was determined that the effort to keep the tools concurrent with the rest of the project would be too costly for too little advantage. This proved, in retrospect, to be the correct strategy for 386BSD, since most of the bugs we encountered were either inherent to our naive assumptions regarding the 386 instruction set or to silent "features" of the particular version of the GNU GAS assembler used, and as such would have affected the debugger as well as the operating system kernel we were porting. However, an appropriate debugging tool might have cut our development time and would have been especially useful if we were contemplating many ports to other architectures.

In practice, an appropriate tool for kernel debugging should afford little impact on the absolute environment. It should allow for source-level debugging (now considered a bare minimum requirement) and should leverage existing development platforms as much as possible. Van Jacobsen's "kernel GDB," for example, is derived from GNU's GDB debugger. It uses a small stub routine in the kernel and a serial line to communicate with a cross-host running the debugger. Other debuggers probably exist that exhibit these qualities, but we are unaware of them.

With this article, our port is moved from the conceptual to the tangible world. This discussion, while by no means complete, addresses many of the mechanisms necessary for 386BSD kernel functionality. It is tantalizing to watch as the basic mechanisms come together, and one tends to think of what remains as mere bookkeeping. If this were true, operating systems programming techniques would more closely resemble those used by applications programmers. They do not, however. While it may appear that the end of the project is nearing, what is actually occurring is merely the first battle of a long and involved war. Again, to use our mountain climbing analogy, we clear the foothills only to be faced with the mountain range itself. In other words, we are continually challenged with a new class of obstacles.

The First Step

At this point, there is little confidence in any of our tools because we have yet to actually "shake down" the absolute loader, assembler, and link editor. Beginning with trivial programs of a few instructions and gradually expanding them, we incrementally prove our tools to the point where we can use them with some degree of confidence. The journey begins, as always, with a single step.

More Details.

The program in Listing One is the simplest protected-mode program we can write that generates output on the screen. It displays the message "hi" midscreen and then stops. A program this simple must always work. If it does not, it presents a minimum number of possibilities to determine why it fails. During our port, this program originally did not work, due to bugs in the early loader and assembler. While this may seem trite to some, this program illustrates the pathetic level at which untested software tools begin. After eliminating a handful of nuisance bugs, this simple program did work, and it proved valuable because it was able to smoke out bugs quickly.

A side note to those who may have noticed that our assembly code format seems to have changed since the previous article when we used Microsoft's MASM: For those unaware, UNIX 386 assemblers prefer the operands in the opposite order, partly because early UNIX systems appeared on PDP-11s, which preferred this ordering style. Thus, on MS-DOS with MASM:

  mov  eax,edx ;move contents of edx into eax register

corresponds to the UNIX assembler format:

  movl  %edx,%eax #move contents of edx into eax register

In other words, it is (destination, source) instead of (source, destination). This is yet another stunning "improvement" in the field of computer languages, destined to be appreciated by those simultaneously debugging a MASM-coded bootstrap loader and code generated by the GAS UNIX assembler!

As we proceed further, we add more complexity, testing span-dependent jumps, stacks, and other mechanisms. Listing Two is a more elaborate program which sends character and string output functions to the screen, thus allowing for a primitive degree of debugging. Listing Three contains a simple runtime start-off for C, with the obligatory "hello world\0" program heralding our arrival into serious programming mode. At this point, we've found most of the silly bugs and also created a primitive debugging tool. One might even claim that, through this method, our entire BSD UNIX system is derived from our original two-instruction program that we started with!

Introducing the Standalone System

The next milestone on our path was to produce, debug, and test a library of support routines written in absolute protected-mode code. These routines allow us to write the GCC programs needed to implement 386 machine-dependent code, to access devices, and to access UNIX file structures on the hard disk. For the code in Listing Three to function, a library is required to fill out all of the primitives invoked.

This library and corresponding programs constitutes a standalone system of a kind, and it affords us an opportunity to write a minimal amount of machine-dependent code outlining our basic structure before we commit to massive coding. It is a minimal C environment at best, but more than enough for us to implement and test things like exception catching, system call handling, line clock interrupts, and so forth. As we begin our climb, we are able to expand a toe hold into a foot hold.

The standalone system actually consists of assembly language programs for runtime start-off and processor support (module srt.s), as well as machine-dependent C code for device support (many modules, including kbd.c and cga.c) and machine-independent C code for language support, formatted output, and filesystem operations (modules prf.c and sys.c). With the standalone system, a file can be read or written from a BSD filesystem on a disk drive.

The BSD standalone system is not intended quite for this purpose; instead, it's used to bootstrap load the system from disk or tape as part of the process of initializing the computer to run the BSD system. Since we don't yet have an operable kernel to be loaded and we've already written a MS-DOS program loader (see DDJ, February 1991), the standalone system is not really of use to us yet. However, the standalone system also provides us with file I/O, formatted output, and a structure to hang hardware drivers on, while demanding little from the hardware for support. Thus, we can use the standalone system to prototype code for the kernel, with the added dividend of completing the bootstrap code required by the complete kernel.

To run this minimal system, only the simplest of keyboard, display, and hard disk device drivers are required. These can be enhanced later as needed.

Keyboard Driver

Listing Four outlines code for a simple driver, which extracts ASCII characters from the PC keyboard on demand by grabbing display codes from the 8042 keyboard interface, consulting a table of actions for the given key press display code, and returning the appropriate value out of the key table. It does not even bother to initialize the keyboard controller, since we know that MS-DOS already did that for us before we loaded the program with our absolute loader.

This is the first place where we are hit with variations in PC keyboard interfaces, all of which are hidden from applications programs and MS-DOS by appropriate BIOS ROM drivers. It is possible to dance back and forth between real and protected mode (thankfully made easier on the 386 than was the case on the 286), "translating" the BIOS calls into BSD driver requests. This method was intended for the PC, if one examines its real-mode ancestry, and also addresses a nest of manufacturer idiosyncrasies. However, it goes against the grain of our project in three basic ways: 1. performance degrades in getting away from direct interaction with the hardware; 2. incompatibility with previous BSD systems develops; and 3. implementation becomes a bigger project than the port itself. In addition, it perpetuates the intertwining of MS-DOS and UNIX to the point where it becomes a significant future liability. To resolve this dilemma, we must choose to support the "raw" machine in its entirety, with the result that undocumented or "secret" proprietary hardware features must be ignored. This is not as great a burden as it may first appear, because a considerable body of code already exists for this purpose, and the great bulk of 386 AT platforms already conform to de facto hardware standards.

Display Adapter

In Listing Five, we can examine the code from a trivial "glass tty" terminal emulator for the display, which in this case happens to be a CGA board. We can be content at the moment with newline, carriage return, and tab functions, since we do not intend to do anything other than line-oriented text output in the standalone system. Scrolling, by far, is the most complicated feature.

Our decision to avoid the BIOS at this point does make things more difficult, because the BIOS automatically configures in device-dependent code from ROMs onboard the display card to support the given device. Fortunately, market forces have kept the proliferation of variations down to a reasonable number, with either MDA or CGA interface standards supported by practically all boards. Up to the point where we must support X Windows, we can live with probing to determine the display type and "hard coding" for each.

Prevaricating with the Standalone System

The standalone system also provides us with a test bed for trying many different ideas which can satisfy the mechanisms used in the BSD operating system kernel, for we can then selectively test these mechanisms individually. Otherwise, we would be forced to test them all together within the operating system. Thus, as we vary our approach, we can determine whether each method satisfies our basic specification conditions and whether implementation is feasible for our project. Over the course of this project, the support strategies for device-interrupts configuration and process context-switching changed drastically as we began to notice the degree of difference between porting BSD to a 386 and porting BSD to more "conventional" architectures. In fact, we were still using the standalone system to find unintended interactions in our 386 hardware-features support code long after we had 386BSD self-compiling. Another valuable aspect of this test bed is we can benchmark competitive solutions to the same kernel support mechanism sans other interactions. This was useful in selecting appropriate context switch, interrupt control, and virtual memory system code.

Extending the Standalone System

On top of the standalone system framework (which really requires very little processor-dependent support) we can write and test portions of code for the operating system kernel (which requires quite a lot of processor-dependent support). In the following sections of this article, we will discuss some extensions to the standalone system which add kernel functionality. Processor support for the kernel reflects support for memory protection of 386 "rings," ring crossings, and address space translations among other needs (see the accompanying box "Brief Notes: 386 Rings").

These extensions are not required for the standalone system to function, but they are not only used to test the kernel code, but actually form the basis for the prototype kernel code. In essence, the standalone system can be viewed as if it were the kernel itself, or possibly even a nano-kernel!

Processor Support -- i386.c

Within the i386.c module appears the code and data structures needed to "wire-down" most of the 386's processor structures (descriptors, exceptions, task switch state). init386() is a subroutine that "fills in the blanks" and test386() tests portions of the mechanisms we will need to run our BSD UNIX system. Note that this creates a superficial test bed that does not entirely address our intended system, as user and kernel mode not only share address space, they are the same program!

We start first by initializing paging (Listing Nine). The next fragment contains code which enables paging by building a set of page tables and page directory. For this example, we map virtual addresses to correspond with physical addresses identically, and allow the first 4 Mbytes of physical memory to be referenced "read/write" by both user and supervisor (kernel) rings. It is important to remember that while the processor's instructions work through the paging MMU with virtual addresses, the addresses that the MMU uses to consult page directory and page tables are all physical addresses. These physical addresses do not always correspond to the virtual addresses that the processor uses, unlike this example where virtual addresses are mapped one for one. As a result, when modifying the page tables and page directory the kernel must explicitly convert any virtual addresses used to physical.

Another point to mention about this paging mechanism is that the page tables and page directory themselves need to be mapped to a given virtual address so that the kernel may modify them to change address translation on demand. An oddity of this paging mechanism is that it can work even if the page tables are completely inaccessible to the kernel in its virtual address space. This would be inconvenient for the kernel, however, as it spends a great deal of time modifying these structures already.

Two assembly language helper routines lcr0() and lcr3() allow us to set the 386's processor control and page directory base register, respectively. Since we are already running "protected," the lcr0() simply overwrites the already set protect-mode bit as well as the paging-mode bit, allowing the MMU to enter into paging mode.

Our page tables and directory as encoded here provide a null address mapping, so that there is, as yet, no effective difference in address translation. One might wonder why we must do this. If we don't, several subtle problems arise. For example, if the address mapping of the instructions we are executing were to differ, the 386's view of which instruction was to be executed next might no longer match the next assembled instruction the program should have executed. Both must be changed synchronously. Worse, if the 386 has an instruction queue fetching asynchronously, we may not be able to predict exactly when the transition occurred. The safest way to avoid these problems is to enable page mapping with no net translation, then modify the address mapping after the processor is running on the "identity" map. We can then arrange to flush our various processor instruction queues and MMU address translation buffers before allowing the processor to execute instructions in a "translated" portion of the address space.

Besides paging, we must reinitialize segmentation. We start by "flattening" the 386 with our descriptor tables. On the 386 (see Listing Six), our Global Descriptor Table (GDT) describes address space selectors that will have global visibility within our BSD kernel such that all processes will see them. Kernel address space requires a descriptor for instructions and data, as well as a task gate used to switch processes through, and various task state descriptors used to save and restore state on demand. The kernel has a "panic" task state reserved to be used when catching certain exceptions that require an "known good" task state.

For the address space selectors used in user processes, we have the Local Descriptor Table (LDT). We can use, potentially, one per process. These descriptors, as the name suggests, are private to each process, and describe the memory segments of that process. In addition, we have "gates." We need to use only one to call the system.

Descriptors come in many different flavors (see Listing Seven): Those that refer to memory or system data structures directly, and gates that indirectly refer to other memory segments. We use task gates to generically switch to the next consecutive task state, and call gates to allow us to enter the kernel's global code segment in a system call. Gates get their name from the controlled fashion that they regulate ring crossings, again from the MULTICS heritage.

Actually our coverage of descriptors is not yet complete. We have hidden descriptors as well that serve special functions. Interrupts and exceptions on the 386 index yet another descriptor table, the Interrupt Descriptor Table (IDT). No program code can call these gate descriptors. Instead, external interrupts and internal processor exceptions transfer through these gate descriptors. We also use a kind of "meta descriptor" called a "region descriptor," which is used to describe descriptor tables so that we can load them via appropriate instructions. So much for the cast of players in this descriptor drama.

Because the actual descriptor encoding is somewhat obscure (it was meant to be reverse-compatible with the 286), we chose to refer to the descriptor by having a subroutine shuffle our software descriptors into appropriate form when presented to the hardware for use. In Listing Six local and global tables are filled out by translating them into hardware form and loading them with a lgdt(), lidt() function. We do this, even through we are already in protected mode, to provide this newer version of the descriptor tables that we wish to use. The function lgdt() hides some characteristics of the 386 segmentation from view, because when we reload the GDT (we are running using active GDT descriptors), we need to flush instruction prefetch and reload all kernel descriptors. This insures proper code execution. We then reload the CS register by turning the normal intrasegment return into a intersegment return.

In the case of our IDT table, we use a subroutine, setgate() (see Listing Eight) to build interrupt gate descriptors that will enter the system's global code descriptor at special assembler stub routines. Each is referred to by a special naming convention hidden by the IDTVEC() macro that catches the exception or interrupt. With all of these descriptor tables loaded and in place, the 386 now has complete information describing the legitimate references to RAM memory by user programs, the operating system kernel, and hardware-accessed data structures. Exceptions, including incorrect references to memory, will also be caught and directed to appropriate code.

One virtue of this complicated scheme of descriptors and segments is that it is possible to add new microprocessor features by simply adding new descriptor types. The mechanism is now general enough to support a wide variety of data objects in a consistent way.

Initial Task State Load

Even if we don't wish to use the 386's special context switch feature, we must initialize a root task state. Why? Because once we are in a user-mode process, only the TSS (Task State Segment) contains the information on where the stack is in the supervisor (kernel) processor ring.

Interestingly, the processor will indeed go into user mode, functioning just fine until a trap, interrupt, or system call occurs. Most other processors have dedicated register sets to locate the kernel stack in these cases. But the 386 designers conceptualized ring crossings (user <-> kernel mode) like that of task switches. Thus, we include the supervisor "entry into ring" stack pointer in the TSS.

In the TSS structure (see Listing Ten), we assign a kernel/supervisor stack top well below our current stack to avoid conflicts. We select this as the current task segment, and then use a little trick to fill out the remainder of this large structure by arranging to context switch back to our task segment, using our assembly language stub jmptss. jmptss always saves the task state of the current task and then loads the state yet again. Because the new state must not already be BUSY in order to use this trick, we force it to be AVAILABLE. UNIX kernels use a function, called resume(), to provide for this mechanism. However, jmptss also provides for context switching when we wish to transfer from one process or task to the next one. The general case, when we call jmptss with a new TSS selector, not the current one, will be covered in a later article.

Trap Handling

Earlier in this article we alluded to the 386 exception/trap's assembler stubs routines. Now that we have enough 386 support in place, we can describe trap handling and the mechanism by which these stubs reflect the trap event into the C trap() handling function. Listing Twelve contains code for a sample trap, in this case a breakpoint or INT3 instruction. Assembly language stubs in module srt.s are executed by the processor in response to receiving a trap or interrupt that selects the corresponding IDT entry.

These stubs are the minimal glue that index each kind of trap with a manifest constant. This constant is always of the form T_XXXX and is obtained from the file trap.h (see Listing Fourteen). Some traps on the 386 also place an error code word on the stack, in order to transfer additional information about the cause of the trap. Because we need to ultimately remove this error code before returning from the trap, we first make all traps appear alike by pushing a dummy error code of zero on the stack for traps without error codes. Then, the common code that returns after the trap has only to remove both the trap constant and error code, regardless of which trap occurred.

After saving the processor state, all trap stubs call common code, which calls the C language trap handler; they also have code following, which restores the state and returns to whatever code was running when the trap occurred. The C language handler merely notifies us of the processor state and exception type and then returns. Since our test function will test different traps in a sequence, we prefer to bypass faults that don't move the program counter. We do this by manually incrementing the program counter, knowing that all faults we intend to encounter happen to be 1 byte in size. Obviously, this convenience doesn't hold for the BSD kernel, but it is satisfactory for the moment.

Interrupt Handling

Interrupts on the 386 are a kind of trap that function much like the exceptions we discussed. In Listing Twelve, the AT's interrupt control units and interrupt timer are initialized to allow hardware interrupts to be signaled from AT devices, such as timer to processor. As a minor point, we clear the coprocessor's exception interrupt to avoid spotting a possibly spurious interrupt from some preexisting condition formed in MS-DOS mode prior to running the standalone program.

Next, our interrupt test enables the processor and interrupt control unit for a brief period of time, allowing hardware interrupts to be processed by the 386. Any interrupts occurring during that interval will cause the 386 to extract the appropriate IDT entry from the table and cause one of the assembly stubs in Listing Twelve to be executed. These stubs, like the trap stubs, save the state, record the interrupt index on the stack, and call the common C function intr().

In intr(), the present interrupt is masked off and the interrupts are then reenabled so other interrupts can be active while the received interrupt is processed. Note that both the stubs and this function are fully reentrant, as this is not an uncommon occurrence. The example code also provides some trivial interrupt actions for our timer, keyboard, and any other device that generates an interrupt. At this point, we dispatch to a specific device driver's interrupt routine.

After responding to the interrupt, we restore our old mask in an uninterruptable "critical section," and signal the interrupt control unit that this interrupt is to be acknowledged as "finished." Our interrupt stub then unwinds the stack, restores the state as needed, and returns us to the exact state we were in prior to processing the interrupt signaled to the 386.

System Call Handling

To test system calls, we must first enter user mode by generating an outbound ring crossing. The touser() function (see Listing Thirteen) does this by switching to our previously set-up user ring address space found in the LDT and enters user mode in the function named usercode(). (The LDT, by a remarkable coincidence, exactly corresponds to our standalone system's "kernel" address space!) A special kind of stack frame is built that imitates the 386's inbound ring-crossing processing. In other words, we "fake out" the processor into believing it has just come from user mode. This done, we calmly return with an intersegment return, executing in the new mode at the beginning of the function.

usercode() does not tarry in the user ring for long; it immediately calls the system call gate previously set up in the LDT. This call gate regulates entry into the kernel at location IDTVEC (syscall), which in turn calls the C function syscall() to properly enter the kernel.

Normally, at the end of the system call assembly stub we would return to the user ring program, but since we have concluded testing the user ring transition, we instead return to the touserp() function caller via a nonlocal goto. We have carefully preserved the stack frame further up the stack, so we can test other parts of the kernel mechanism in the test386() function.

Page Fault Handling

Our final mechanism demonstrated here involves generating a page fault, a rather common occurrence in our BSD UNIX system. These faults, caught by the mechanism described earlier, are trap type T_PAGEFLT, and they end up in the trap handler trap(). In Listing Eleven, notice that this function prints the contents of the 386 special processor register cr2 on a trap. This register records the address value causing the page fault trap. Eventually, the virtual memory system will require this in order to determine which page is being requested by the program being run and if this page should be made accessible. In this case, it obviously is not accessible.

To generate a page fault, code in module i386.c (see Listing Fifteen) first reads and then writes an address outside of the range of valid page table and directory structures. If this address had been outside of the range of the current segment descriptor, it would have generated a general-protection fault before being flagged by the paging unit. (In the scheme of things, segmentation is ahead of paging.) But the address invoked is well within the range of the segment descriptor, and only the paging unit takes issue with it. Our page table mapping validates the first page of page tables (the first 4 Mbytes of address space) even though all the other page table pages are not present. However, because the page table directory entry in this case is actually invalid, the 386 MMU balks on address 0x800000 and signals trap type T_PAGE-FLT to trap().

We can determine the type of page fault from the error code of the trap, which in turn tells us whether a "read," "write," or "protection violation" condition occurred. With the 386, we can also restrict pages to "supervisor only" access, thus keeping them out of the hands of any nosy user programs. It is interesting to note that while user programs can write-protect pages of memory (typically when used for instruction segments), the kernel (running in the supervisor ring) does not have the same option since the 386 ignores the "write protect" control on paging. While this is not needed for UNIX to function, we would like to make parts of the kernel "read-only" to catch unintended modifications by undiscovered bugs in the kernel. We just can't do this on the 386.

Where Do We Go From Here?

In the first examination of our initial utilities, we discussed several items of importance in our standalone system /sys/stand, some of its utilities, and a library of support routines. Through the standalone system, we were able to use GCC programs to access devices, such as the keyboard and display, as well as UNIX file structures on the hard disk. It also provided us with a platform to examine the 386's requirements through extensions which supported features incorporated into our UNIX port, and could also be used as a test bed for some of these functions. As we stated earlier, the standalone system can be viewed at this stage as if it were the kernel itself, with the extensions the basis of our prototype kernel code. We have started up the base of the mountain.

Next time, we will proceed further with our initial utilities development, by creating a stable cross-tools environment. Kermit and NCSA telnet will be used to load files and program over Ethernet and serial lines. We will then focus on proving GCC itself valid for cross-support purposes, as well as the limitations and alternatives.

Brief Notes: 386 Rings

A "ring" is a concept developed in the early days of large-scale timesharing by those working on the MULTICS operating system (see The Multics Systems: An Examination of Its Structure, Elliot I. Organick, MIT Press: Cambridge Mass., 1972). These rings establish a hierarchy of memory protection and processor function, in which code running in lesser-valued rings has access only to all higher-valued or equally valued rings. A protection violation occurs when less "secure" code (running in a higher-valued ring) accesses more "secure" code or data (at a lower-valued ring).

Rings can be used to regulate access and determine if a protection violation occurs.

Support for the multiple ring protection model of the 386 occurs in four distinct rings of protection (0-3). Traditional UNIX supports only two rings: One for the kernel operating system, and one for the current user process. On the 386, this corresponds to the supervisor ring (0) and the user ring (3). When the 386 is running in the user ring and receives an interrupt or exception, or it needs to process a system call, it must switch rings to the supervisor ring. Once there, the kernel is run as a trusted program to handle these events. This switching of rings, or "ring crossing," is central to the UNIX memory protection model. Unlike MS-DOS, where operating system code and user application code mingle in the same address space, UNIX programs run in "hard" shells of address space. UNIX programs are not able to modify each other or the operating system by virtue of memory protection. This is enforced by memory protection hardware on the microprocessor in the general case when the applications program is running. Ring crossing, where we go from user to kernel code, needs to be carefully done to preserve the protection model in all cases. In this way, nothing that a user program does can possibly affect another user program adversely or "crash" the operating system.

--B.J., L.J.


_PORTING UNIX TO THE 386: THE STANDALONE SYSTEM_
by William Frederick Jolitz and Lynne Greer Jolitz


[LISTING ONE]
<a name="00a9_0014">


# hi.s:   Simplest protected mode program providing some kind of output.
   .text
start:   movl   $0x0e690e48, 0x0b8800   # put "hi" mid screen on display
   hlt






<a name="00a9_0015">
<a name="00a9_0016">
[LISTING TWO]
<a name="00a9_0016">

# hello.s: Minimal test of GNU GAS assembler, handles CGA & strings.
   .text
start:
   movl   $0xA0000,%esp

   pushl   $str
   call   _puts
   pop   %eax
   hlt
str:   .asciz   "\n\rHello world from GAS\r\n"
_puts:
   push   %ebx
   movl   8(%esp),%ebx
1:   cmpb   $0,(%ebx)   # until we see a null
   je   2f

   movzbl   (%ebx),%eax
   pushl   %eax
   call   _putchar   # put out characters
   popl   %eax

   incl   %ebx
   jmp   1b
2:   popl   %ebx
   ret
crtat:   .long   0xb8000      # address of CGA video RAM
row:   .long    0

_putchar:
   movzbl   4(%esp),%eax
   push   %ebx
   push   %ecx
   movl   crtat,%ebx
   cmpl   $0xb8000+80*25*2,%ebx  # continous output off screen edge & bot
   jl   1f
   movl   $0,row
   movl   $0xb8000+80*(25-1)*2,%ebx
1:   cmpb   $0xd,%al      # cr
   jne   1f
   movl   $80,%ecx      # clear rest of line
   subl   row,%ecx
   movl   %ebx,%edi
   movw   $0xfff,%ax
   cld
   rep
   stosw
   subl   row,%ebx
   subl   row,%ebx
   movl   $0,row
   jmp   9f
1:   cmpb   $0xa,%al      # nl
   jne   2f
   cmpl   $0xb8000+80*(25-1)*2,%ebx   # scroll?
   jl   1f
   movl   $0xb8000,%edi      # scroll page
   movl   $0xb8000+80*2,%esi
   movl   $80*(25-1),%ecx
   cld
   rep
   movsw
   movl   $80,%ecx      # clear new bottom line
   movl   $0xb8000+80*(25-1)*2,%edi
   movw   $0,%ax
   rep
   stosw
   sub   $80*2,%ebx      # position cursor before lf
1:   add   $80*2,%ebx
   jmp   9f
2:   orw   $0x0e00,%ax      # attribute
   movw   %ax,(%ebx)
   addl   $2,%ebx
   incl   row
9:   movl   %ebx,crtat
   pop   %ecx
   pop   %ebx
   ret






<a name="00a9_0017">
<a name="00a9_0018">
[LISTING THREE]
<a name="00a9_0018">

/* [Excerpted from srt.s] */

 ...
entry:   .globl   entry
   jmp   1f
   .space   0x500      /* skip over BIOS data area */
1:   cli         /* no interrupts yet */

   movl   $0xA0000,%esp

   movl   %esp,%edx
   movl   $_edata,%eax
   subl   %eax,%edx   /* clear stack and heap store */
   pushl   %edx
   pushl   %eax
   call   _bzero
   popl   %eax
   popl   %eax

   call   _main
 ...

/* hello.c */
main() {   printf("Hello, world!\n");   }






<a name="00a9_0019">
<a name="00a9_001a">
[LISTING FOUR]
<a name="00a9_001a">

/* kbd.c: Copyright (c) 1989, 1990 William Jolitz. All rights reserved.
 * Written by William Jolitz 9/89
 * 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.
 * Standalone driver for IBM PC keyboards.
 */

#define   L      0x001   /* locking function */
#define   SHF      0x002   /* keyboard shift */
#define   ALT      0x004   /* alternate shift -- alternate chars */
#define   NUM      0x008   /* numeric shift  cursors vs. numeric */
#define   CTL      0x010   /* control shift  -- allows ctl function */
#define   CPS      0x020   /* caps shift -- swaps case of letter */
#define   ASCII      0x040   /* ascii code for this key */
#define   STP      0x080   /* stop output */
#define   BREAK      0x100   /* key breaking contact */

typedef unsigned char u_char;

u_char inb();

u_char action[] = {
0,     ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII,    /* scan  0- 7 */
ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII,    /* scan  8-15 */
ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII,    /* scan 16-23 */
ASCII, ASCII, ASCII, ASCII, ASCII,   CTL, ASCII, ASCII,    /* scan 24-31 */
ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII,    /* scan 32-39 */
ASCII, ASCII, SHF  , ASCII, ASCII, ASCII, ASCII, ASCII,    /* scan 40-47 */
ASCII, ASCII, ASCII, ASCII, ASCII, ASCII,  SHF,  ASCII,    /* scan 48-55 */
  ALT, ASCII, CPS|L,     0,     0, ASCII,     0,     0,    /* scan 56-63 */
    0,     0,     0,     0,     0, NUM|L, STP|L, ASCII,    /* scan 64-71 */
ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII, ASCII,    /* scan 72-79 */
ASCII, ASCII, ASCII, ASCII,     0,     0,     0,     0,    /* scan 80-87 */
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,   } ;

u_char unshift[] = {   /* no shift */
0,     033  , '1'  , '2'  , '3'  , '4'  , '5'  , '6'  ,    /* scan  0- 7 */
'7'  , '8'  , '9'  , '0'  , '-'  , '='  , 0177 ,'\t'  ,    /* scan  8-15 */

'q'  , 'w'  , 'e'  , 'r'  , 't'  , 'y'  , 'u'  , 'i'  ,    /* scan 16-23 */
'o'  , 'p'  , '['  , ']'  , '\r' , CTL  , 'a'  , 's'  ,    /* scan 24-31 */

'd'  , 'f'  , 'g'  , 'h'  , 'j'  , 'k'  , 'l'  , ';'  ,  /* scan 32-39 */
'\'' , '`'  , SHF  , '\\' , 'z'  , 'x'  , 'c'  , 'v'  ,    /* scan 40-47 */

'b'  , 'n'  , 'm'  , ','  , '.'  , '/'  , SHF  ,   '*',    /* scan 48-55 */
ALT  , ' '  , CPS|L,     0,     0, ' '  ,     0,     0,    /* scan 56-63 */

    0,     0,     0,     0,     0, NUM|L, STP|L,   '7',    /* scan 64-71 */
  '8',   '9',   '-',   '4',   '5',   '6',   '+',   '1',    /* scan 72-79 */

  '2',   '3',   '0',   '.',     0,     0,     0,     0,    /* scan 80-87 */
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,   } ;

u_char shift[] = {   /* shift shift */
0,     033  , '!'  , '@'  , '#'  , '$'  , '%'  , '^'  ,    /* scan  0- 7 */
'&'  , '*'  , '('  , ')'  , '_'  , '+'  , 0177 ,'\t'  ,    /* scan  8-15 */
'Q'  , 'W'  , 'E'  , 'R'  , 'T'  , 'Y'  , 'U'  , 'I'  ,    /* scan 16-23 */
'O'  , 'P'  , '['  , ']'  , '\r' , CTL  , 'A'  , 'S'  ,    /* scan 24-31 */
'D'  , 'F'  , 'G'  , 'H'  , 'J'  , 'K'  , 'L'  , ':'  ,    /* scan 32-39 */
'"'  , '~'  , SHF  , '|'  , 'Z'  , 'X'  , 'C'  , 'V'  ,    /* scan 40-47 */
'B'  , 'N'  , 'M'  , '<'  , '>'  , '?'  , SHF  ,   '*',    /* scan 48-55 */
ALT  , ' '  , CPS|L,     0,     0, ' '  ,     0,     0,    /* scan 56-63 */
    0,     0,     0,     0,     0, NUM|L, STP|L,   '7',    /* scan 64-71 */
  '8',   '9',   '-',   '4',   '5',   '6',   '+',   '1',    /* scan 72-79 */
  '2',   '3',   '0',   '.',     0,     0,     0,     0,    /* scan 80-87 */
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,   } ;

u_char ctl[] = {   /* CTL shift */
0,     033  , '!'  , 000  , '#'  , '$'  , '%'  , 036  ,    /* scan  0- 7 */
'&'  , '*'  , '('  , ')'  , 037  , '+'  , 034  ,'\177',    /* scan  8-15 */
021  , 027  , 005  , 022  , 024  , 031  , 025  , 011  ,    /* scan 16-23 */
017  , 020  , 033  , 035  , '\r' , CTL  , 001  , 013  ,    /* scan 24-31 */
004  , 006  , 007  , 010  , 012  , 013  , 014  , ';'  ,    /* scan 32-39 */
'\'' , '`'  , SHF  , 034  , 032  , 030  , 003  , 026  ,    /* scan 40-47 */
002  , 016  , 015  , '<'  , '>'  , '?'  , SHF  ,   '*',    /* scan 48-55 */
ALT  , ' '  , CPS|L,     0,     0, ' '  ,     0,     0,    /* scan 56-63 */
CPS|L,     0,     0,     0,     0,     0,     0,     0,    /* scan 64-71 */
    0,     0,     0,     0,     0,     0,     0,     0,    /* scan 72-79 */
    0,     0,     0,     0,     0,     0,     0,     0,    /* scan 80-87 */
    0,     0,   033, '7'  , '4'  , '1'  ,     0, NUM|L,    /* scan 88-95 */
'8'  , '5'  , '2'  ,     0, STP|L, '9'  , '6'  , '3'  ,    /*scan  96-103*/
'.'  ,     0, '*'  , '-'  , '+'  ,     0,     0,     0,    /*scan 104-111*/
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,   } ;

#define   KBSTATP      0x64      /* kbd status port */
#define   KBS_RDY      0x02      /* kbd char ready */
#define   KBDATAP      0x60      /* kbd data port */
#define   KBSTATUSPORT   0x61      /* kbd status */
#define   KBD_BRK   0x80 /* key is breaking contact, not making contact */
#define   KBD_KEY(s)   ((s) & 0x7f)   /* key that has changed */

/* Return an ASCII character from the keyboard.  */
u_char kbd() {
   u_char dt, act;
   static u_char odt, shfts, ctls, alts, caps, num, stp;

   do {
      do {
         while (inb(KBSTATP)&KBS_RDY) ;
         dt = inb(KBDATAP);
      } while (dt == odt);

      odt = dt;
      dt = KBD_KEY(dt);
      act = action[dt];
      if (odt & KBD_BRK) act |= BREAK;

      /* kinds of shift keys */
      if (act&SHF) actl (act, &shfts);
      if (act&ALT) actl (act, &alts);
      if (act&NUM) actl (act, &num);
      if (act&CTL) actl (act, &ctls);
      if (act&CPS) actl (act, &caps);
      if (act&STP) actl (act, &stp);

      if (act&(ASCII|BREAK) == ASCII) {
         u_char chr;

         if (shfts)
             chr = shift[dt] ;
         else {
            if (ctls) chr = ctl[dt] ;
            else chr = unshift[dt] ;
         }
         if (caps && (chr >= 'a' && chr <= 'z'))
            chr -= 'a' - 'A' ;
         return(chr);
      }
   } while (1);
}

/* Handle shift key actions */
actl(act, v, brk) char *v; {

   /* are we locking ... */
   if (act&L) {
      if((act&BREAK) == 0) *v ^= 1;

   /* ... or single - action ? */
   } else
      if(act&BREAK) *v = 0; else *v = 1;
}





<a name="00a9_001b">
<a name="00a9_001c">
[LISTING FIVE]
<a name="00a9_001c">

/* cga.c: Copyright (c) 1989, 1990 William Jolitz. All rights reserved.
 * Written by William Jolitz 9/89
 * 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.
 * Standalone driver for IBM PC Displays like CGA.
 */

typedef unsigned short u_short;
typedef unsigned char u_char;

#define   CRT_TXTADDR   Crtat
#define   COL      80
#define   ROW      25
#define   CHR      2

u_short *Crtat = ((u_short *)0xb8000); /* 0xb0000 for monochrome */
u_short   *crtat;
u_char   color = 0xe ;
int row;

sput(c) u_char c; {

   if (crtat == 0) {
      crtat = CRT_TXTADDR; bzero (crtat,COL*ROW*CHR);
   }
   if (crtat >= (CRT_TXTADDR+COL*ROW*CHR)) {
      crtat = CRT_TXTADDR+COL*(ROW-1); row = 0;
   }
   switch(c) {

   case '\t':
      do {
         *crtat++ = (color<<8)| ' '; row++ ;
      } while (row %8);
      break;
   case '\010':
      crtat--; row--;
      break;
   case '\r':
      bzero (crtat,(COL-row)*CHR) ; crtat -= row ; row = 0;
      break;
   case '\n':
      if (crtat >= CRT_TXTADDR+COL*(ROW-1)) { /* scroll */
         bcopy(CRT_TXTADDR+COL, CRT_TXTADDR,COL*(ROW-1)*CHR);
         bzero (CRT_TXTADDR+COL*(ROW-1),COL*CHR) ;
         crtat -= COL ;
      }
      crtat += COL ;
      break;
   default:
      *crtat++ = (color<<8)| c; row++ ;
      break ;
   }
}






<a name="00a9_001d">
<a name="00a9_001e">
[LISTING SIX]
<a name="00a9_001e">

/* [excerpted from i386.c] */
 ...
/* Descriptor Tables  */

   /* Global Descriptor Table */
#define   GNULL_SEL   0   /* Null Descriptor - obligatory */
#define   GCODE_SEL   1   /* Kernel Code Descriptor */
#define   GDATA_SEL   2   /* Kernel Data Descriptor */
#define   GLDT_SEL   3   /* LDT - eventually one per process */
#define   GTGATE_SEL   4   /* Process task switch gate */
#define   GPANIC_SEL   5   /* Task state to consider panic from */
#define   GPROC0_SEL   6   /* Task state process slot zero and up */
union descriptor gdt[GPROC0_SEL+NPROC];

/* interrupt descriptor table */
struct gate_descriptor idt[NEXECPT+NINTR];

/* local descriptor table */
#define   LSYS5CALLS_SEL   0   /* SVID/BCS 386 system call gate */
#define   LSYS5SIGR_SEL   1   /* SVID/BCS 386 sigreturn() */
#define   LBSDCALLS_SEL   2   /* BSD experimental system calls */
#define   LUCODE_SEL   3   /* user process code descriptor */
#define   LUDATA_SEL   4   /* user process data descriptor */
union descriptor ldt[LUDATA_SEL+1];

/* Task State Structures (TSS) for hardware context switch */
struct   i386tss   tss[NPROC], ptss;

/* software prototypes -- in more palitable form */
struct soft_segment_descriptor gdt_segs[GPROC0_SEL+NPROC] = {
   /* Null Descriptor */
{   0x0,         /* segment base address  */
   0x0,         /* length - all address space */
   0,         /* segment type */
   0,         /* segment descriptor priority level */
   0,         /* segment descriptor present */
   0,0,
   0,         /* default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Code Descriptor for kernel */
{   0x0,         /* segment base address  */
   0xfffff,      /* length - all address space */
   SDT_MEMERA,      /* segment type */
   0,         /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   1,         /* default 32 vs 16 bit size */
   1           /* limit granularity (byte/page units)*/ },
   /* Data Descriptor for kernel */
{   0x0,         /* segment base address  */
   0xfffff,      /* length - all address space */
   SDT_MEMRWA,      /* segment type */
   0,         /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   1,         /* default 32 vs 16 bit size */
   1           /* limit granularity (byte/page units)*/ },
   /* LDT Descriptor */
{   (int) ldt,         /* segment base address  */
   sizeof(ldt)-1,      /* length - all address space */
   SDT_SYSLDT,      /* segment type */
   0,         /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   0,         /* unused - default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Null Descriptor - Placeholder */
{   0x0,         /* segment base address  */
   0x0,         /* length - all address space */
   0,         /* segment type */
   0,         /* segment descriptor priority level */
   0,         /* segment descriptor present */
   0,0,
   0,         /* default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Panic Tss Descriptor */
{   (int) &ptss,         /* segment base address  */
   sizeof(tss)-1,      /* length - all address space */
   SDT_SYS386TSS,      /* segment type */
   0,         /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   0,         /* unused - default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Process 0 Tss Descriptor */
{   (int) &tss[0],      /* segment base address  */
   sizeof(tss)-1,      /* length - all address space */
   SDT_SYS386TSS,      /* segment type */
   0,         /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   0,         /* unused - default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ } };

struct soft_segment_descriptor ldt_segs[] = {
   /* Null Descriptor - overwritten by call gate */
{   0x0,         /* segment base address  */
   0x0,         /* length - all address space */
   0,         /* segment type */
   0,         /* segment descriptor priority level */
   0,         /* segment descriptor present */
   0,0,
   0,         /* default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Null Descriptor - overwritten by call gate */
{   0x0,         /* segment base address  */
   0x0,         /* length - all address space */
   0,         /* segment type */
   0,         /* segment descriptor priority level */
   0,         /* segment descriptor present */
   0,0,
   0,         /* default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Null Descriptor - overwritten by call gate */
{   0x0,         /* segment base address  */
   0x0,         /* length - all address space */
   0,         /* segment type */
   0,         /* segment descriptor priority level */
   0,         /* segment descriptor present */
   0,0,
   0,         /* default 32 vs 16 bit size */
   0           /* limit granularity (byte/page units)*/ },
   /* Code Descriptor for user */
{   0x0,         /* segment base address  */
   0xfffff,      /* length - all address space */
   SDT_MEMERA,      /* segment type */
   SEL_UPL,      /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   1,         /* default 32 vs 16 bit size */
   1           /* limit granularity (byte/page units)*/ },
   /* Data Descriptor for user */
{   0x0,         /* segment base address  */
   0xfffff,      /* length - all address space */
   SDT_MEMRWA,      /* segment type */
   SEL_UPL,      /* segment descriptor priority level */
   1,         /* segment descriptor present */
   0,0,
   1,         /* default 32 vs 16 bit size */
   1           /* limit granularity (byte/page units)*/ } };
 ...
extern ssdtosd(), lgdt(), lidt(), lldt(), usercode(), touser();

init386() {
 ...
   /* make gdt memory segments */
   for (x=0; x < sizeof gdt / sizeof gdt[0] ; x++)
         ssdtosd(gdt_segs+x, gdt+x);
   printf("lgdt\n"); getchar();
   lgdt(gdt, sizeof(gdt)-1);

   /* make ldt memory segments */
   for (x=0; x < sizeof ldt / sizeof ldt[0] ; x++)
      ssdtosd(ldt_segs+x, ldt+x);

   /* make a call gate to reenter kernel with */
   setgate(&ldt[LSYS5CALLS_SEL].gd, &IDTVEC(syscall), SDT_SYS386CGT,
      SEL_UPL);
   printf("lldt\n"); getchar();
   lldt(GSEL(GLDT_SEL, SEL_KPL));
 ...
/* [excerpted from srt.s] */
 ...
   /* lgdt(*gdt, ngdt) */
   .globl   _lgdt
gdesc:   .word 0
   .long 0
_lgdt:
   movl   4(%esp),%eax
   movl   %eax,gdesc+2
   movl   8(%esp),%eax
   movw   %ax,gdesc
   lgdt   gdesc
   jmp   1f      /* flush instruction prefetch q */
   nop
1:   movw   $0x10,%ax   /* reload other "well known" descriptors */
   movw   %ax,%ds
   movw   %ax,%es
   movw   %ax,%ss
   movl   0(%esp),%eax
   pushl   %eax
   movl   $8,4(%esp)   /* including the ever popular CS */
   lret
 ...
   /* lldt(sel) */
   .globl   _lldt
_lldt:
   lldt   4(%esp)
   ret
 ...






<a name="00a9_001f">
<a name="00a9_0020">
[LISTING SEVEN]
<a name="00a9_0020">

/* segments.h: Copyright (c) 1989, 1990 William Jolitz. All rights reserved.
 * Written by William Jolitz 6/20/1989
 * 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.
 * 386 Segmentation Data Structures and definitions
 */

/* Selectors  */
#define   ISPL(s)   ((s)&3)      /* what is the priority level of a selector */
#define    SEL_KPL   0   /* kernel priority level */
#define    SEL_UPL   3   /* user priority level */
#define   ISLDT(s) ((s)&SEL_LDT)   /* is it local or global */
#define    SEL_LDT   4   /* local descriptor table */
#define   IDXSEL(s) (((s)>>3) & 0x1fff)   /* index of selector */
#define   LSEL(s,r) (((s)<<3) | SEL_LDT | r) /* a local selector */
#define   GSEL(s,r) (((s)<<3) | r) /* a global selector */

/* Memory and System segment descriptors  */
struct   segment_descriptor   {
   unsigned sd_lolimit:16 ;     /* segment extent (lsb) */
   unsigned sd_lobase:24 ;        /* segment base address (lsb) */
   unsigned sd_type:5 ;        /* segment type */
   unsigned sd_dpl:2 ;        /* segment descriptor priority level */
   unsigned sd_p:1 ;        /* segment descriptor present */
   unsigned sd_hilimit:4 ;        /* segment extent (msb) */
   unsigned sd_xx:2 ;        /* unused */
   unsigned sd_def32:1 ;        /* default 32 vs 16 bit size */
   unsigned sd_gran:1 ;        /* limit granularity (byte/page units)*/
   unsigned sd_hibase:8 ;        /* segment base address  (msb) */
} ;

/* Gate descriptors (e.g. indirect descriptors)  */
struct   gate_descriptor   {
   unsigned gd_looffset:16 ;   /* gate offset (lsb) */
   unsigned gd_selector:16 ;   /* gate segment selector */
   unsigned gd_stkcpy:5 ;      /* number of stack wds to cpy */
   unsigned gd_xx:3 ;      /* unused */
   unsigned gd_type:5 ;      /* segment type */
   unsigned gd_dpl:2 ;      /* segment descriptor priority level */
   unsigned gd_p:1 ;      /* segment descriptor present */
   unsigned gd_hioffset:16 ;   /* gate offset (msb) */
} ;

/* Generic descriptor  */
union   descriptor   {
   struct   segment_descriptor sd;
   struct   gate_descriptor gd;
};
#define   d_type   gd.gd_type

   /* system segments and gate types */
#define   SDT_SYSNULL    0   /* system null */
#define   SDT_SYS286TSS    1   /* system 286 TSS available */
#define   SDT_SYSLDT    2   /* system local descriptor table */
#define   SDT_SYS286BSY    3   /* system 286 TSS busy */
#define   SDT_SYS286CGT    4   /* system 286 call gate */
#define   SDT_SYSTASKGT    5   /* system task gate */
#define   SDT_SYS286IGT    6   /* system 286 interrupt gate */
#define   SDT_SYS286TGT    7   /* system 286 trap gate */
#define   SDT_SYSNULL2    8   /* system null again */
#define   SDT_SYS386TSS    9   /* system 386 TSS available */
#define   SDT_SYSNULL3   10   /* system null again */
#define   SDT_SYS386BSY   11   /* system 386 TSS busy */
#define   SDT_SYS386CGT   12   /* system 386 call gate */
#define   SDT_SYSNULL4   13   /* system null again */
#define   SDT_SYS386IGT   14   /* system 386 interrupt gate */
#define   SDT_SYS386TGT   15   /* system 386 trap gate */

   /* memory segment types */
#define   SDT_MEMRO   16   /* memory read only */
#define   SDT_MEMROA   17   /* memory read only accessed */
#define   SDT_MEMRW   18   /* memory read write */
#define   SDT_MEMRWA   19   /* memory read write accessed */
#define   SDT_MEMROD   20   /* memory read only expand dwn limit */
#define   SDT_MEMRODA   21   /* memory read only expand dwn limit accessed */
#define   SDT_MEMRWD   22   /* memory read write expand dwn limit */
#define   SDT_MEMRWDA   23   /* memory r/w expand dwn limit acessed */
#define   SDT_MEME   24   /* memory execute only */
#define   SDT_MEMEA   25   /* memory execute only accessed */
#define   SDT_MEMER   26   /* memory execute read */
#define   SDT_MEMERA   27   /* memory execute read accessed */
#define   SDT_MEMEC   28   /* memory execute only conforming */
#define   SDT_MEMEAC   29   /* memory execute only accessed conforming */
#define   SDT_MEMERC   30   /* memory execute read conforming */
#define   SDT_MEMERAC   31   /* memory execute read accessed conforming */

/* is memory segment descriptor pointer ? */
#define ISMEMSDP(s)   ((s->d_type) >= SDT_MEMRO && (s->d_type) <= SDT_MEMERAC)

/* is 286 gate descriptor pointer ? */
#define IS286GDP(s)   (((s->d_type) >= SDT_SYS286CGT \
             && (s->d_type) < SDT_SYS286TGT))
/* is 386 gate descriptor pointer ? */
#define IS386GDP(s)   (((s->d_type) >= SDT_SYS386CGT \
            && (s->d_type) < SDT_SYS386TGT))
/* is gate descriptor pointer ? */
#define ISGDP(s)   (IS286GDP(s) || IS386GDP(s))

/* is segment descriptor pointer ? */
#define ISSDP(s)   (ISMEMSDP(s) || !ISGDP(s))

/* is system segment descriptor pointer ? */
#define ISSYSSDP(s)   (!ISMEMSDP(s) && !ISGDP(s))

/* Software definitions are in this convenient format; translated into
 * inconvenient segment descriptors when needed to be used by 386 hardware  */
struct   soft_segment_descriptor   {
   unsigned ssd_base ;      /* segment base address  */
   unsigned ssd_limit ;      /* segment extent */
   unsigned ssd_type:5 ;      /* segment type */
   unsigned ssd_dpl:2 ;      /* segment descriptor priority level */
   unsigned ssd_p:1 ;      /* segment descriptor present */
   unsigned ssd_xx:4 ;      /* unused */
   unsigned ssd_xx1:2 ;      /* unused */
   unsigned ssd_def32:1 ;      /* default 32 vs 16 bit size */
   unsigned ssd_gran:1 ;          /* limit granularity (byte/page units)*/
};

extern ssdtosd() ;   /* to decode a ssd */
extern sdtossd() ;   /* to encode a sd */

/* region descriptors, used to load gdt/idt tables before segments yet exist */
struct region_descriptor {
   unsigned rd_limit:16 ;      /* segment extent */
   char *rd_base;         /* base address  */
};

/* Segment Protection Exception code bits  */
#define   SEGEX_EXT   0x01   /* recursive or externally induced */
#define   SEGEX_IDT   0x02   /* interrupt descriptor table */
#define   SEGEX_TI   0x04   /* local descriptor table */
            /* other bits are affected descriptor index */
#define SEGEX_IDX(s)   ((s)>>3)&0x1fff)






<a name="00a9_0021">
<a name="00a9_0022">
[LISTING EIGHT]
<a name="00a9_0022">

/* [excerpted from i386.c] */
 ...
/* Assemble a gate descriptor  */
setgate(gp, func, typ, dpl) char *func; struct gate_descriptor *gp; {
   gp->gd_looffset = (int)func;
   gp->gd_selector = GSEL(GCODE_SEL,SEL_KPL);
   gp->gd_stkcpy = 0;
   gp->gd_xx = 0;
   gp->gd_type = typ;
   gp->gd_dpl = dpl;
   gp->gd_p = 1;      /* definitely present */
   gp->gd_hioffset = ((int)func)>>16 ;
}

/* ASM entry points to exception/trap/interrupt entry stub code. */
#define   IDTVEC(name)   X##name
extern
   IDTVEC(div), IDTVEC(dbg), IDTVEC(nmi), IDTVEC(bpt), IDTVEC(ofl),
   IDTVEC(bnd), IDTVEC(ill), IDTVEC(dna), IDTVEC(dble), IDTVEC(fpusegm),
   IDTVEC(tss), IDTVEC(missing), IDTVEC(stk), IDTVEC(prot), IDTVEC(page),
   IDTVEC(rsvd), IDTVEC(fpu), IDTVEC(rsvd0), IDTVEC(rsvd1), IDTVEC(rsvd2),
   IDTVEC(rsvd3), IDTVEC(rsvd4), IDTVEC(rsvd5), IDTVEC(rsvd6),
   IDTVEC(rsvd7), IDTVEC(rsvd8), IDTVEC(rsvd9), IDTVEC(rsvd10),
   IDTVEC(rsvd11), IDTVEC(rsvd12), IDTVEC(rsvd13), IDTVEC(rsvd14),
   IDTVEC(rsvd14), IDTVEC(intr0), IDTVEC(intr1), IDTVEC(intr2),
   IDTVEC(intr3), IDTVEC(intr4), IDTVEC(intr5), IDTVEC(intr6),
   IDTVEC(intr7), IDTVEC(intr8), IDTVEC(intr9), IDTVEC(intr10),
   IDTVEC(intr11), IDTVEC(intr12), IDTVEC(intr13), IDTVEC(intr14),
   IDTVEC(intr15), IDTVEC(syscall);
init386() {
 ...
   /* exceptions */
   setgate(idt+0, &IDTVEC(div),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+1, &IDTVEC(dbg),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+2, &IDTVEC(nmi),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+3, &IDTVEC(bpt),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+4, &IDTVEC(ofl),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+5, &IDTVEC(bnd),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+6, &IDTVEC(ill),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+7, &IDTVEC(dna),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+8, &IDTVEC(dble),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+9, &IDTVEC(fpusegm),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+10, &IDTVEC(tss),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+11, &IDTVEC(missing),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+12, &IDTVEC(stk),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+13, &IDTVEC(prot),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+14, &IDTVEC(page),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+15, &IDTVEC(rsvd),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+16, &IDTVEC(fpu),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+17, &IDTVEC(rsvd0),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+18, &IDTVEC(rsvd1),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+19, &IDTVEC(rsvd2),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+20, &IDTVEC(rsvd3),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+21, &IDTVEC(rsvd4),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+22, &IDTVEC(rsvd5),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+23, &IDTVEC(rsvd6),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+24, &IDTVEC(rsvd7),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+25, &IDTVEC(rsvd8),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+26, &IDTVEC(rsvd9),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+27, &IDTVEC(rsvd10),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+28, &IDTVEC(rsvd11),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+29, &IDTVEC(rsvd12),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+30, &IDTVEC(rsvd13),  SDT_SYS386TGT, SEL_KPL);
   setgate(idt+31, &IDTVEC(rsvd14),  SDT_SYS386TGT, SEL_KPL);

   /* first icu */
   setgate(idt+32, &IDTVEC(intr0),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+33, &IDTVEC(intr1),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+34, &IDTVEC(intr2),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+35, &IDTVEC(intr3),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+36, &IDTVEC(intr4),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+37, &IDTVEC(intr5),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+38, &IDTVEC(intr6),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+39, &IDTVEC(intr7),  SDT_SYS386IGT, SEL_KPL);

   /* second icu */
   setgate(idt+40, &IDTVEC(intr8),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+41, &IDTVEC(intr9),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+42, &IDTVEC(intr10),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+43, &IDTVEC(intr11),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+44, &IDTVEC(intr12),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+45, &IDTVEC(intr13),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+46, &IDTVEC(intr14),  SDT_SYS386IGT, SEL_KPL);
   setgate(idt+47, &IDTVEC(intr15),  SDT_SYS386IGT, SEL_KPL);

   printf("lidt\n"); getchar();
   lidt(idt, sizeof(idt)-1);
 ...
 /* [excerpted from srt.s] */

   /* lidt(*idt, nidt) */
   .globl   _lidt
idesc:   .word   0
   .long   0
_lidt:
   movl   4(%esp),%eax
   movl   %eax,idesc+2
   movl   8(%esp),%eax
   movw   %ax,idesc
   lidt   idesc
   ret





<a name="00a9_0023">
<a name="00a9_0024">
[LISTING NINE]
<a name="00a9_0024">

/* [excerpted from i386.c] */
 ...
#define   NBPG      4096      /* number of bytes per page */
#define   PG_V      0x00000001   /* mark this page as valid */
#define   PG_UW      0x00000006   /* user and supervisor writable */

int lcr0(), lcr3();
 ...
init386() {
   /* bag of bytes to put page table, page directory in */
   static char bag[(1+1+1)*NBPG];
   int *ppte, *pptd, *cr3, x;

   /* make page table & directory aligned to NBPG */
   ppte = (int *) (((int) bag + NBPG-1) & ~(NBPG-1));
   cr3 = pptd = ppte + 1024;

   /* page table directory only has lowest 4MB entry mapped */
   *pptd++ = (int) ppte + (PG_V|PG_UW);
   for (x = 1; x < 1024 ; x++,pptd++) *pptd =  0;

   /* page table, all entrys virtual == real, user/supervisor r/w */
   for (x = 0; x < 1024 ; x++,ppte++) *ppte =  x*NBPG + (PG_V|PG_UW) ;

   /* turn on paging */
   lcr3(cr3);
   printf("paging"); getchar();
   lcr0(0x80000001);
 ...

/* [excerpted from srt.s] */

   /*
    * lcr3(cr3)
    */
   .globl   _lcr3
_lcr3:
   movl   4(%esp),%eax
   movl   %eax,%cr3
   ret

   /* lcr0(cr0) */
   .globl   _lcr0
_lcr0:
   movl   4(%esp),%eax
   movl   %eax,%cr0
   ret






<a name="00a9_0025">
<a name="00a9_0026">
[LISTING TEN]
<a name="00a9_0026">

/* [excerpted from i386.c] */
 ...
init386(){
 ...
   /* make a initial tss so 386 can get interrupt stack on syscall! */
   tss[0].tss_esp0 = (int) &x - 4096;
   tss[0].tss_ss0 = GSEL(GDATA_SEL, SEL_KPL) ;
   tss[0].tss_cr3 = (int) cr3;
   printf("ltr "); getchar();
   ltr(GSEL(GPROC0_SEL, SEL_KPL));

   printf("resume() "); getchar();
   /* set busy type to avail */
   gdt[GPROC0_SEL].sd.sd_type = SDT_SYS386TSS;
   /* jump to self to fill out tss, like BSD resume() */
   jmptss(GSEL(GPROC0_SEL, SEL_KPL));
 ...
# excerpted from srt.s
 ...
/* jmptss(sel)-- Jump to TSS so that we can load/unload context  */
   .globl _jmptss      /* similar to BSD swtch()/resume() */
_jmptss:
   ljmp   0(%esp)      /* ljmp tss */
            /* saved pc points here */
   ret







<a name="00a9_0027">
<a name="00a9_0028">
[LISTING ELEVEN]
<a name="00a9_0028">

/* [excerpted from i386.c] */
 ...
test386(){
 ...
   /* test handling exceptions */
   printf("breakpoint "); getchar();
   asm (" int $3 ");
 ...
/* Trap exception processing code  */
trap(es, ds, edi, esi, ebp, dummy, ebx, edx, ecx, eax,
   fault, ec, eip, cs, eflags, esp, ss) {

   printf("pc:%x cs:%x ds:%x eflags:%x ec %x fault %x cr0 %x cr2 %x \n",
      eip, cs, ds, eflags, ec, fault, rcr0(), rcr2());
   printf("edi %x esi %x ebp %x ebx %x edx %x ecx %x eax %x\n",
      edi, esi, ebp, ebx, edx, ecx, eax);
   eip++;   /* simple way to 'jump' over fault */
   getchar();
}

 ...
# excerpted from srt.s
 ...
#include <machine/i386/trap.h>

#define   IDTVEC(name)   .align 4; .globl _X##name; _X##name:
 ...
/* Trap and fault vector routines  */
#define   TRAP(a)   pushl $##a ; jmp alltraps

IDTVEC(div)
   pushl $0; TRAP(T_DIVIDE)
IDTVEC(dbg)
   pushl $0; TRAP(T_DEBUG)
IDTVEC(nmi)
   pushl $0; TRAP(T_NMI)
IDTVEC(bpt)
   pushl $0; TRAP(T_BPTFLT)
IDTVEC(ofl)
   pushl $0; TRAP(T_OFLOW)
IDTVEC(bnd)
   pushl $0; TRAP(T_BOUND)
IDTVEC(ill)
   pushl $0; TRAP(T_PRIVINFLT)
IDTVEC(dna)
   pushl $0; TRAP(T_DNA)
IDTVEC(dble)
   TRAP(T_DOUBLEFLT)
IDTVEC(fpusegm)
   pushl $0; TRAP(T_FPOPFLT)
IDTVEC(tss)
   TRAP(T_TSSFLT)
IDTVEC(missing)
   TRAP(T_SEGNPFLT)
IDTVEC(stk)
   TRAP(T_STKFLT)
IDTVEC(prot)
   TRAP(T_PROTFLT)
IDTVEC(page)
   TRAP(T_PAGEFLT)
IDTVEC(rsvd)
   pushl $0; TRAP(T_RESERVED)
IDTVEC(fpu)
   pushl $0; TRAP(T_ARITHTRAP)
   /* 17 - 31 reserved for future exp */
IDTVEC(rsvd0)
   pushl $0; TRAP(17)
IDTVEC(rsvd1)
   pushl $0; TRAP(18)
IDTVEC(rsvd2)
   pushl $0; TRAP(19)
IDTVEC(rsvd3)
   pushl $0; TRAP(20)
IDTVEC(rsvd4)
   pushl $0; TRAP(21)
IDTVEC(rsvd5)
   pushl $0; TRAP(22)
IDTVEC(rsvd6)
   pushl $0; TRAP(23)
IDTVEC(rsvd7)
   pushl $0; TRAP(24)
IDTVEC(rsvd8)
   pushl $0; TRAP(25)
IDTVEC(rsvd9)
   pushl $0; TRAP(26)
IDTVEC(rsvd10)
   pushl $0; TRAP(27)
IDTVEC(rsvd11)
   pushl $0; TRAP(28)
IDTVEC(rsvd12)
   pushl $0; TRAP(29)
IDTVEC(rsvd13)
   pushl $0; TRAP(30)
IDTVEC(rsvd14)
   pushl $0; TRAP(31)

alltraps:
   pushal
   push %ds         # save old selector's we will use
   push %es
   movw   $0x10,%ax      # load them with kernel global data sel
   movw   %ax,%ds
   movw   %ax,%es
   call   _trap
   pop %es
   pop %ds
   popal
   addl   $8,%esp         # pop type, code
   iret





<a name="00a9_0029">
<a name="00a9_002a">
[LISTING TWELVE]
<a name="00a9_002a">

/* [excerpted from i386.c] */
 ...
init386() {
 ...
   outb(0xf1,0);   /* clear coprocessor to cover all bases */

   /* initialize 8259 ICU's in preperation for device interrupts */

   outb(ICU1,0x11);   /* reset the unit */
   outb(ICU1+1,32);   /* start with idt 32 */
   outb(ICU1+1,4);      /* master please */
   outb(ICU1+1,1);
   outb(ICU1+1,0xff);   /* all disabled */

   outb(ICU2,0x11);
   outb(ICU2+1,40);   /* start with idt 40 */
   outb(ICU2+1,2);      /* just a slave */
   outb(ICU2+1,1);
   outb(ICU2+1,0xff);   /* all disabled */

   /* initialize 8253 timer on interrupt #0 */

   outb (0x43, 0x36);
   outb (0x40, 193182/60);
   outb (0x40, (193182/60)/256);
}
test386(){
 ...
   /* test interrupts for a while */
   printf("inton"); getchar();
   outb(ICU1+1,0);   /* unmask all interrupts */
   outb(ICU2+1,0);
   inton();

   timeout = 0x8000000;
   do nothing(); while (timeout-- );

   intoff();
 ...

# excerpted from srt.s
 ...
#define   INTR(a) \
   pushal ; \
   push %ds ; \
   push %es ; \
   movw $0x10, %ax ; \
   movw %ax, %ds ; \
   movw %ax,%es ; \
   pushl $##a ; \
   call _intr ; \
   pop %eax ; \
   pop %es ; \
   pop %ds ; \
   popal ; \
   iret

   /* hardware 32 - 47 */
IDTVEC(intr0)
   INTR(0)
IDTVEC(intr1)
   INTR(1)
IDTVEC(intr2)
   INTR(2)
IDTVEC(intr3)
   INTR(3)
IDTVEC(intr4)
   INTR(4)
IDTVEC(intr5)
   INTR(5)
IDTVEC(intr6)
   INTR(6)
IDTVEC(intr7)
   INTR(7)
IDTVEC(intr8)
   INTR(8)
IDTVEC(intr9)
   INTR(9)
IDTVEC(intr10)
   INTR(10)
IDTVEC(intr11)
   INTR(11)
IDTVEC(intr12)
   INTR(12)
IDTVEC(intr13)
   INTR(13)
IDTVEC(intr14)
   INTR(14)
IDTVEC(intr15)
   INTR(15)

   .globl   _inton
_inton:
   sti
   ret

   .globl   _intoff
_intoff:
   cli
   ret
 ...

/* back to i386.c */
 ...
/* Interrupt vector processing code  */
intr(ivec) {
   static clk;
   int omsk1, omsk2;

   /* mask off interrupt being serviced, save old mask */
   if (ivec > 7) {
      omsk2 = inb(ICU2+1);
      outb(ICU2+1, 1<<(ivec-8));
   } else {
      omsk1 = inb(ICU1+1);
      outb(ICU1+1, 1<<ivec);
   }

   /* re-enable processor's interrupts, allowing others in */
   inton();

   /* if we are the clock, count clock tick */
   if (ivec == 0) clk++;
   /* if we are the keyboard, show data incoming */
   else if (ivec == 1) printf("kbd data %x, clk %d\n", inb(0x60), clk);
   /* otherwise print message stating source and time */
   else {
      printf("intr %d, clk %d \n", ivec, clk);
      getchar();
   }

   /* turn off interrupts, re-enable old mask, do interrupt acknowledge */
   intoff();
   if (ivec > 7) {
      outb(ICU2+1,omsk2);
      outb(ICU2,0x20);
   } else
      outb(ICU1+1,omsk1);
   outb(ICU1,0x20);
   /* return to interrupt stub */
}
 ...




<a name="00a9_002b">
<a name="00a9_002c">
[LISTING THIRTEEN]
<a name="00a9_002c">

test386(){
 ...
       /* transfer to user mode to test system call */
       printf("touser "); getchar();
       touser (LSEL(LUCODE_SEL,SEL_UPL), LSEL(LUDATA_SEL, SEL_UPL), &usercode);
 ...
# [excerpted from srt.s]

   /* touser (cs,ds,func) */
   .globl   _touser
_touser:
   pushal
   movl   %esp,_myspback
   movl   32+4(%esp),%eax
   movl   32+8(%esp),%edx
   movl   32+12(%esp),%ecx
   # build outer stack frame

   pushl   %edx      # user ss
   pushl   %esp      # user esp
   pushl   %eax      # cs
   pushl   %ecx      # ip
   movw   %dx,%ds
   movw   %dx,%es
   lret   # goto user!

/* code to execute in user mode */
   .globl   _usercode
#define   LCALL(x,y)   .byte 0x9a ; .long y; .word x
_usercode:
   LCALL(0x7,0x0)   /* would be lcall $0x7,0x0 except for assembler bug */
IDTVEC(syscall)
   pushal
   movw   $0x10,%ax
   movw   %ax,%ds
   movw   %ax,%es
   call   _syscall
   movl   _myspback,%esp   /* non-local goto touser() exit */
   popal
   ret
/* back to i386.c */
 ...
/* System call processing */
syscall() {
   printf("syscall\n");
}






<a name="00a9_002d">
<a name="00a9_002e">
[LISTING FOURTEEN]
<a name="00a9_002e">

/* trap.h: i386 trap type index    [as they intersect with other BSD systems] */

#define   T_PRIVINFLT   1   /* privileged instruction */
#define   T_BPTFLT   3   /* breakpoint instruction */
#define   T_ARITHTRAP   6   /* arithmetic trap */
#define   T_PROTFLT   9   /* protection fault */
#define   T_PAGEFLT   12   /* page fault */

#define   T_DIVIDE   18   /* integer divide fault */
#define   T_NMI      19   /* non-maskable trap */
#define   T_OFLOW      20   /* overflow trap */
#define   T_BOUND      21   /* bound instruction fault */
#define   T_DNA      22   /* device not available fault */
#define   T_DOUBLEFLT   23   /* double fault */
#define   T_FPOPFLT   24   /* fp coprocessor operand fetch fault */
#define   T_TSSFLT   25   /* invalid tss fault */
#define   T_SEGNPFLT   26   /* segment not present fault */
#define   T_STKFLT   27   /* stack fault */
#define   T_RESERVED   28   /* reserved fault base */






<a name="00a9_002f">
<a name="00a9_0030">
[LISTING FIFTEEN]
<a name="00a9_0030">

/* [excerpted from i386.c] */
 ...
test386(){
   int x, *pi, timeout;
 ...
   /* generate a page fault exception */
   printf("dopagflt\n"); getchar();
   pi =  (int *) 0x800000;   /* above 4MB */
   x = *pi;      /* will fault invalid read */
   *pi = ++x ;      /* will fault invalid write */
 ...


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.