560 Notes #3

              CS560 NOTES #3 Kernel & User Modes

References: Back's book:Chapter 6 : System call interface

1. Process Image:
   The execution image of a process consists of 3 (logical) segments:

       -----------------------------  
       |  CODE   |  DATA   | STACK |
       -----------------------------
       |   ^     |         |    ^
       CS  |    DS        SS    |
           pc                   sp

          Figure 1. Process Image 

In theory, the 3 segments can be independent; each can be at a different memory
location, as long as they are pointed by the segment registers of CPU. 
In practice, some of the segments may coincide. For example, in the combined 
I and D space memory model, all segments are the same, i.e. CS=DS=SS all point 
at the same segment. In the separate I and D space model, CS points to the I 
(code) segment, but DS and SS point to the same Data+Stack segment. 
For simplicity, we shall use the combined I and D space memory model in MTX so 
that a process image has only ONE segment. The segment size is (up to) 64KB. 

2. Kmode and Umode Images:
   From now on we shall assume that a process may execute in (either one)
of two different modes, Kernel mode or User mode, denoted by Kmode or Umode 
for short. While in Kmode, all processes share the same Code and Data segments,
which are those of the Kernel, but each process has its own Kstack. The Umode 
images are in general all different, i.e. each process has a separate Umode 
memory area consisting of its own Ucode, Udata and Ustack. Also, for simplicity
we shall assume:
 
      MTX Kernel at (segment) 0x1000
      8 processes, P1 to P8, with Umode images;
      Each process has a 64KB Umode image in the segment (pid+1)*0x1000, i.e.
      P1 at 0x2000, P2 at 0x3000, etc. (We can remove this "FIXED SEGMENT" 
      restriction later when we implement memory management).

Now, assume that

     P5 is running in Umode NOW, P2 is READY but not running. The memory map 
     is shown below:

   -------------------------------------------------------------------------
   |Vector  | Kernel |        | UImage2 |          |         | UImage5 |
   ------------------------------------------------------------------------- 
            ^ 0x1000          ^ 0x3000                       ^ 0x6000
            |                 |                              |
       kCS,kDS,kSS                                          uCS,uDS,uSS 

At this moment, the CPU's CS,DS,SS registers all point at P5's Umode image 
at 0x6000. P5's ustack is somewhere in the upper region of UImage5.

When P5 enters Kmode, it will execute the Kernel image at 0x1000. The CPU's
CS,DS,SS registers MUST be changed to point at 0x1000, as shown by the 
kCS,kDS,kSS in the diagram. In order for P5 to execute in Kernel, the stack 
pointer (sp) must also be changed to point at the Kstack of P5. Naturally, P5
must save its Umode uCS,uDS,uSS and usp in order for it to return to UImage5 
later. 

While in Kmode, P5 may switch to P2.  P2 may return to its Umode image at
0x3000. If so, P2 must load CPU's CS,DS,SS with its own uCS,uDS,uSS, all of
which point at 0x3000. P2's ustack is in the upper region of UImage2. 

Similarly, you should be able to deduce what would happen when P2 enters Kmode 
to switch to another process, ..... etc.

3. Transition between Umode and Kmode.

A process migrates between Umode and Kmode MANY times during its life time.
Although every process begins (comes into existence) in Kmode, we shall assume
that a process is already executing in Umode. This sounds like another 
chicken-egg problem, but we can handle it easily.

A process in Umode will enter Kmode if any of these events occurs:

   ===================================================================== 
   (1). traps      : divideByZero, IllegalInstruction, InvalidAddress,...
   (2). Interrupts : timer interrupts, device I/O completion, etc.
   (3). syscalls   : INT # (or equivalent instructions on other CPUs).
   =====================================================================

Traps and Interrupts will be covered later in the course. Here, we shall 
consider only syscalls. 

syscall is a mechanism that allows a Umode process to enter Kmode to execute 
Kernel functions. syscalls are not ordinary function calls because they involve
CPU operating in different modes (U to K) and accessing different address 
spaces. Assume that there are N Kernel functions, each corresponds to a 
Type = 0,1,2, ..., N-1, e.g.

         Type   KernelFunction 
         -----  --------------  
           0       getpid
           1       fork
           2       wait
         ...................
          99       exit

A Umode process may call

        int syscall(type, arg1, arg2, .... )  int type, arg1, arg2, ....;

which acts as an interface between U mode "calls" to the corresponding 
Kernel functions. The implementation of syscall(), assuming 4 arguments,
is shown below:

|========================================================================
|    syscall(a,b,c,d) int a,b,c,d;     issue INT 80 to enter Kerenl
|========================================================================
_syscall:
        int   80            <==== This is the magic wand!
        ret

On the Intel CPU, syscalls are implemented as INT #, where # is a (byte) value.
Although we may use different INT numbers to implement different syscall 
fucntions, it suffices to use one number: INT 80 (because the parameter a 
indicates the syscall type). The choice of 80 is quite arbitrary. You may 
choose any number, as long as it is not used as IRQ or by BIOS. 

When CPU executes the instruction  INT 80,  it performs the following actions.
              -------------------- 
PUSH:            push uFlag   
                       clear T-bit,I-bit of flag register to 0 
                             (trace off, mask off interrupts)
                 push uCS
                 push uPC
              ------------------------------------------------
LOAD:            load (PC, CS) with CONTENTs of (4*80, 4*80+2);
              ------------------------------------------------
HANDLER:      continue execution from the loaded (PC, CS) ===>
              CPU enters INT80 handler in kernel's Code segment.
              ------------------------------------------------

Corresponding to INT #, the instruction  IRET  pops 3 items off the current
stack into CPU's PC,CS,Flag registers, in that order. It is used to return from
interrupt handler (in Kmode) to Umode. 

The INT80 handler is shown below:
|=======================================================================
|  int80h() is the entry point for INT 80 interrupts
|=======================================================================
_int80h:
          
! (1). SAVE Umode registers in ustack, save uSS,uSP in running proc's PROC, 
!      Accordingly, we modify the proc structure as follows.
    
!      typedef struct proc{
!         struct proc *next;
!         int    *ksp;           /* offset =  2 bytes */

!         int    uss, usp;       /* ADD 2 items at offset 4,6 */ 

!         int    pid; 
!         int    ppid;
!         int    status;
!         int    pri;           /* scheduling priority */

!         int    event;         /* sleep event */
!         char   name[32];      /* name string of proc */
!         int    exitValue;      // ADD this for exit(exitValue);  

!         int    kstack[SSIZE]; // proc's Kernel mode stack
      }PROC;        

!     int procSzie = sizeof(PROC):     // a global imported in assembly 

!    The added fields (uss, usp) are for saving (uSS, uSP). 

! (2). Change to running proc's kstack HIGH END (see below for how)
!      Then, call handler function in C

! (3). RETURN to Umode

! Details of these steps follow.

! **************** SAVE U mode registers *********************************
        push ax                 ! save all Umode registers in ustack
        push bx
        push cx
        push dx
        push bp
        push si
        push di
        push es
        push ds
! ustack contains : flag,uCS,uPC | ax,bx,cx,dx,bp,si,di,ues,uds
!                                                            |
                                                            uSP

! **************  Change DS,SS,sp to K space *****************

! change DS to KDS in order to access data in K space
        push cs            ! push kCS (0x1000) 
        pop  ds            ! let DS=CS = 0x1000

USS = 4
USP = 6 

! NOTE: All variables are now relative to DS=0x1000 of Kernel space
! save running proc's U mode uSS and uSP into its PROC 
        mov si,_running  	! ready to access running PROC in K space

        mov USS(si),ss          ! save uSS  in proc.uss
        mov USP(si),sp          ! save uSP  in proc.usp

! change ES SS to K space segment 0x1000 
        mov  di,ds              ! must mov segments this way!
        mov  es,di            
        mov  ss,di              ! SS is now KSS = 0x1000

! We are now ready to switch (running proc's) stack from U space to K space.
        mov  sp,si	        ! sp points at running proc
        add  sp,_procSize       ! sp -> HIGH END of running's kstack[]

! We are now completely in K space, and stack is running proc's (empty) kstack

! *************   CALL handler in C ******************************
        call  _kcinth           ! call kcinth() in C; 

! *************   RETURN TO U Mode ********************************
_goUmode:
        cli                     ! mask out interrupts
        mov bx,_running 	! bx -> proc
        mov cx,USS(bx)
        mov ss,cx               ! restore uSS
        mov sp,USP(bx)          ! restore uSP
       
        pop ds
        pop es
        pop di
        pop si
        pop bp
        pop dx
        pop cx
        pop bx
        pop ax                  ! NOTE: return value must be in AX in ustack  

        iret
=============================================================================
 The assembly function goUmode() restores Umode stack, then restores Umode
 registers, followed by an IRET, causing the process to return to Umode.

 These assembly functions are keys to understanding Umode/Kmode transitions. 
 STUDY THEM CAREFULLY and make sure you know what they do.
*****************************************************************************

4. Handler Function in C
           kcinth(){.............}
   is the syscall handler function in C. The parameters used in  
           syscall(a,b,c,d)   
   are still in ustack, which contains

   LOW                                                                 High    
   | uds,ues,udi,usi,ubp,udx,ucx,ubx,uax |upc,ucs,uflag|retPC, a, b, c, d |...
      0   1   2   3   4   5   6   7   8    9   10  11    12    13 14 15 16
     usp

   The segment of ustack is saved in proc.uss, and usp is saved in proc.usp

   Use the get_byte()/put_byte() functions (given earlier) to implement YOUR 
   own get_word()/put_word() functions, where

         int get_word(segment, offset) ushort segment, offset;
   
   returns a 2-byte word from (segment, offset), and

         int put_word(word, segment, offset) ushort word, segment, offset;
   
   writes a 2-byte word to (segment, offset).

   In both cases, (segment, offset) MUST be an even address

   With these functions, you can easily copy bytes/words from/to U space.
   In particular, you can get the syscall parameter from ustack AND modify
   the ustack contents, e.g. the saved AX register for return value to U
   mode.

5. Action Functions:
   In syscall(a,b,c,d), the parameter a is the syscall number. Based on the 
   value of a, you can execute the desired action function, passing any needed
   parameters b,c,d to it.

                           RETURN VALUE:
   Each action function returns a value back to Umode. The return value is 
   carried in the AX register. Since goUmode() pops the saved AX from ustack, 
   you must fix the saved AX in ustack before goUmode().