Jolix
/joh'liks/ n.,adj. 386BSD

PORTING UNIX TO THE 386: A PRACTICAL APPROACH


William & Lynne Jolitz


We coded trap handlers and simulated read and write faults to test out page faults with our processor support code. On a 2MB machine, we knew we'd get 1,000's.




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.

Listing 11:
/* [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
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().

Listing 15:
/* [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 */
 ...
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.




<<BACK NEXT >>



Copyright 1989, 1990, 2006 TeleMuse Partners, William Jolitz and Lynne Jolitz