460 Notes #5

              CS460 NOTES #5 Kernel/User Modes

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 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 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 (tasks), P1 to P8;
      Each process has a 64KB Umode image in the segment 0x1000*(i+1), i.e.
      P1 at 0x2000, P2 at 0x3000, etc. (We can remove this "FIXED SEGMENT" 
      restriction later when we introduce memory management).

Now, assume that

     P5 is running in Umode NOW, P2 is READY but not running. 

 ************************* Helpful Reviews ***********************************
 Try to answer these questions. They will help you understand the MTX Kernel:
 If P2 is not running but READY, where is its PROC? (i.e. in which Kernel data
 structure?) How can P2 run again? When P2 runs again, where does it "resume" 
 to?
 *****************************************************************************

   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 
and switches 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 number). 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.
              ------------------------------------------------


Details of the PUSH, LOAD and HANDLER actions follow.
        
(1). PUSH:   
INT # causes the CPU to push current uFlag, uCS, uPC into stack. On most other
machines, INT # causes the CPU to enter a separate Kmode, and switches stack
from ustack to kstack automatically. However, we are using the Intel CPU in 
UNPROTECTED mode, which does not have a separate Kmode. After an INT 80, all
it does is to switch execution point from (uPC,uCS) to (kPC,kCS), which changes
the code segment from UCode to KCode. All other segments (DS and SS) and CPU
registers are still those of the Umode. Thus, when CPU first enters the
INT80 handler, the stack is still ustack in the uSS segment. It contains 
uFlag, uCS, uPC at the top:
 
                -------------------------------- 
                ///////|uFlag| uCS | uPC |
                -------------------------------- 
                                      ^
                                      |
                High                 usp         Low


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. 


(2). LOAD
After an INT 80, CPU loads PC, CS from memory locations corresponding to the
vector number 80.  Naturally, the vector 80 locations MUST be initialized 
properly first, such as   

         int int80h();            /* handler function prototype */
         set_vector(80, int80h);  /* 80*4=kPC=int80h, 80*4+2=kCS=0x1000 */


(3). HANDLER:

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 task'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 task */

!         int    kstack[SSIZE]; // task'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 task's kstack HIGH END (see how below)
!      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 task'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              ! stupid CPU must mov segments this way!
        mov  es,di            
        mov  ss,di              ! SS is now KSS = 0x1000

! We are now ready to switch (running task'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 task'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 below) 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().

!============================================================================

                       6. Utility Functions:

           These functions provide Inter-space transportation.
           get_byte() and put_byte() are already given BEFOE !
       
*===========================================================================*
!*				get_byte				     *
!*===========================================================================*
! This routine is used to fetch a byte from anywhere in memory.
! The call is:
!     c = get_byte(segmet, offset) 
! where
!     'segment' is the segment value to put in es
!     'offset'  is the offset from the segment in es
_get_byte:
        push bp                 ! establish FP
        mov  bp,sp              ! 

        push es                 ! save es
        push bx                 ! save bx

        mov  es,4(bp)           ! load es with segment value
        mov  bx,6(bp)           ! load bx with offset from segment
        seg  es	                ! go get the byte
        movb al,(bx)            ! al = byte
        xorb ah,ah              ! ax = byte

        pop  bx                 ! restore bx
        pop  es	                ! restore es

        mov  bp,sp
        pop  bp                 ! return
        ret                     ! 
!=============================================================================

!*===========================================================================*
!*				put_byte				     *
!*===========================================================================*
! This routine is used to put a word to anywhere in memory.
! The call is:
!           put_byte(char,segment,offset)
! where
!     char is a byte
!     'segment' is a segment
!     'offset'  is the byte offset from the segment
_put_byte:
        push bp			! establish FP
        mov  bp,sp		! 

        push es			! save es
        push bx

        mov  es,6(bp)   	! load es with segment value
        mov  bx,8(bp)	        ! load bx with offset from segment
        movb  al,4(bp)          ! load byte in aL
        seg  es			! go put the byte to (ES, BX)
        movb  (bx),al		! there it goes

        pop  bx
        pop  es			! restore es

        mov  bp,sp
        pop  bp			! 
        ret			! return

7. Function for reading/writing disk blocks

       !------------------------------------------------------------------
       ! diskr(cyl, head, sector, buf);  MemoryAddress=(ES,buf)
       !        4     6     8     10     NOTE: (cyl,head,sector) count from 0
       !-----------------------------------------------------------------
_diskr:                             
        push  bp
        mov   bp, sp           ! setup stack frame; bp is FP

        movb  dl, #0x00        ! drive 0=fd0
        movb  dh, 6(bp)        ! head#
        movb  cl, 8(bp)        ! sector#
        incb  cl               ! inc sector by 1 to suit BIOS
        movb  ch, 4(bp)        ! cyl#
        mov   ax, #0x0202      ! READ 2 sectors; 0x0302 for WRITE 2 sectors 
        mov   bx, 10(bp)       ! put buf value in BX ==> addr=(ES,BX)

        int  0x13              ! call BIOS to read the block 
        jb   error             ! to error if CarryBit is on (read failed)

        mov  bp,sp
        pop  bp
        ret                    ! return to caller

error:  ! handle disk i/o error (reboot?)