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");
}