460 Notes #3


               CS460 NOTES on Multi-Tasking

1. An Example Program:

============== main.c file ======================    
typedef struct proc{
        struct proc *next;
               int  ksp;               // saved sp when giving up CPU          
               int  kstack[1024];      // proc's (2KB) stack area
}PROC;

PROC mainProc, *running;
int  procSize = sizeof(PROC);          // size of PROC in bytes

printf(fmt) char *fmt;
{ YOUR printf() function }

main()
{
  running = &mainProc;
  printf("call tswitch()\n");
     tswitch();
  printf("back to main()\n");
}

int scheduler()
{ 
   printf("in scheduler\n");
}

================= s.s file ==================
.globl begtext, begdata, begbss                      ! needed by linker
.globl _tswitch, _getc, _putc                        ! EXPORT these
.globl _main,_mainProc,_running,_scheduler,_procSize ! IMPORT these

.text                                                ! these tell as:	
begtext:                                             ! text,data,bss segments
.data                                                ! are all the same.
begdata:
.bss
begbss:
.text           

start:
        mov     ax,cs                   ! establish segments 
        mov     ds,ax                   ! we know ES,CS=0x1000. Let DS=CS  
        mov     ss,ax                   ! SS = CS ===> all point to 0x1000
        mov     es,ax

        mov     sp,#_mainProc           ! sp -> mainProc
        add     sp ,_procSize           ! sp -> mainProc's HI END

        call _main                      ! call main[] in C

dead:	jmp dead

_getc:
        xorb   ah,ah 
        int    0x16  
        ret 

_putc:           
        push   bp
        mov    bp,sp
        movb   al,4[bp]
        movb   ah,#14  
        mov    bx,#0x000B 
        int    0x10    
        pop    bp
        ret
	
	
_tswitch:
SAVE:   push ax
        push bx
        push cx
        push dx
        push bp
        push si
        push di
        pushf
        mov   bx, _running
        mov   2[bx], sp

FIND:   call _scheduler

RESUME: mov   bx, _running
        mov   sp, 2[bx]
        popf
        pop  di
        pop  si
        pop  bp
        pop  dx
        pop  cx
        pop  bx
        pop  ax
        ret
=============================================
          UNDER Linux:
   as86 -o s.o s.s
   bcc  -c main.c
   ld86 -d s.o main.o /usr/lib/bcc/libc.a
   mount /dev/fd0 /fd0
   cp a.out /fd0/boot/mtx
   umount /fd0

   Dump YOUR MTX booter to FD:
   dd if=mtx_booter of=/dev/fd0

   Boot mtx from FD to run the program.

   It is informative if you trace the program execution by drawing a diagram.
   First, the beginning part of the assembly code sets CS,DS,SS to the same
   segment, 0x1000, as required by the "combined I&D spaces model", and sets
   sp to the end of mainProc, i.e. to &mainProc.kstack[1024]. Then it calls
   main() in C, in which it let running point at mainProc and calls tswitch()
   in assembly. The SAVE part of tswitch() saves CPU register ax,bx,cx,dx,bp,
   si,di and flag into stack (mainProc.kstack[ ]), and saves the current stack
   pointer, sp, into mainProc.ksp. Then it calls scheduler(), which prints a
   message and returns to the RESUME part in assembly. RESUME restores the
   stack pointer, sp, from running->ksp, (in this case it's the same as the 
   current sp) and then pop the stack contents into CPU registers, thus 
   restoring the saved registers. After the pop operations, only the return PC
   is left on the stack top. The last ret statement pops this return PC into
   CPU's IP register, causing it to return to where it called tswitch() in 
   main(). Then main() returns to assembly code to loop at the label dead.

   So, the program does practically NOTHING. 

   However, it is the basis of all multitasking systems. Note that in 
   scheduler() we may let running point at a different PROC structure. 
   Then tswitch() would "resume" the "saved context" of that PROC.

   -------------------------------------------------------------------------
   To see this, let's define a newProc structure and make it ready to "resume"
   running.

   Although newProc never existed before, we may PRETEND that it ran before and
   it gave up CPU by calling tswitch() earlier.

   When calling tswitch(), it first saved return PC on its stack.
   Then, it executed SAVE, which saved CPU registers, from ax to flag, so that
   its stack contains:
                        retPC  ax, bx, cx, dx, bp, si, di, flag;
   in newProc.kstack[]:  -1    -2  -3  -4  -5  -6  -7  -8   -9
  
   Then it saved the stack pointer so that
        newProc.ksp -> newProc.kstack[1024-9].
 
   Then, it called secheduler(), in which it gave up CPU (to our mainProc).  
 
   What should be the retPC?  It can be the entry address of any executable
   code. For example, a function    body(){ ........}
   What should be the values of the "saved" registers? They can be anything
   (except flag whose T-bit must be 0 to prevent CPU Trace traps). So, we may 
   set all of them to 0.
   -------------------------------------------------------------------------
   
   Now, in scheduler(), if we let running point to newProc,  then the RESUME
   part of tswitch() will "resume" newProc, causing it to "return" to body().

   Changing CPU's execution environment (i.e. CPU register contents, hence 
   stack area and "point of execution") from one "activity" to that of another
   is called CONTEXT SWITCHING.

2. A Simple Multi-tasking System

   Use s.s and the following main.c to generate a mtx on a FD (as shown above)
   Boot from the FD to run mtx.

/************ main.c file **********************************/
#define NTASK     4        
#define SSIZE  1024                /* kstack int size */

#define DEAD      0                /* proc status     */
#define READY     1      

typedef struct proc{
    struct proc *next;   
           int  ksp;               /* saved sp; offset = 2 */
           int  pid;
           int  status;            /* READY|DEAD, etc */
           int  kstack[SSIZE];     // kmode stack of task
}PROC;


#include "io.c" // YOUR prints(), printi() and printf() functions

PROC mainProc, proc[NTASK], *running;
int  procSize = sizeof(PROC);            // PROC struct size

/****************************************************************
 Initialize the proc's as shown:
        running ---> mainProc --> proc[0];
        the proc's as a circular list:
            proc[0] --> proc[1] ... --> proc[N-1] -->
              ^                                     |
              |<-----------------------------------<-
       Each task's kstack contains:
       PC, ax, bx, cx, dx, bp, si, di, flag;  all 2 bytes
*****************************************************************/

int body();  

int initialize()
{
  int i, j;
  PROC *p;

  running = &mainProc;
  running->next = &proc[0];
  running->pid = 0;
  running->status = READY;

  for (i=0; i < NTASK; i++){
    p = &proc[i];

    p->next= &proc[i+1];                    /* form a queue */
    p->pid = i+1;                           /* pid = 1,2,3,4 */    
    p->status = READY;

    for (j=1; j<10; j++)
      p->kstack[SSIZE - j] = 0;            // all saved registers = 0

    p->kstack[SSIZE-1]=(int)body;          // called tswitch() from body
    p->ksp = &(p->kstack[SSIZE-9]);        // ksp -> kstack top 
  }
  p->next = &proc[0];                      // a circular list 
  printf("initialization complete\n"); 
}

char *gasp[NTASK]={
     "Oh! You are killing me .......",
     "Oh! I am dying ...............", 
     "Oh! I am a goner .............", 
     "Bye! Bye! World...............",      
};

int grave(){
  printf("*****************************************\n"); 
  printf("Task %d %s %c\n", running->pid, gasp[running->pid], 007);
  printf("*****************************************\n");
  running->status = DEAD;

  tswitch();   /* journey of no return */        
}

int ps()
{
  PROC *p;

  p = running;
  printf("running = %d\n", p->pid);

  p = p->next;

  printf("readyTasks = ");
  while(p != running && p->status==READY){
    printf("%d -> " p->pid);
    p = p->next;
  }
  printf("\n");
}

int body()
{  char c;
   while(1){
       printf("Task %d running\n", running->pid);
       ps();
       printf("Input a char [s|q] : ");
       c=getc();
       switch(c){
            case 's': tswitch(); break;
            case 'q': grave();   break;
            default :            break;  
       }
   }
}


main()
{
 printf("Welcome to the 460 Multitasking System\n");
   initialize();
   tswitch();
 printf("all dead, happy ending\n");
}


int scheduler()
{
    PROC *p;
    p = running->next;

    while (p->status != READY && p != running)
      p = p->next;

    if (p == running)
       running = &mainProc;
    else
       running = p;

    printf("-----------------------------\n");
    printf("next running task = %d\n", running->pid);
    printf("-----------------------------\n");
}