You are on page 1of 40

Multitasking

COMP200

Outline





Introduction to Multitasking
Pre-emption and Scheduling
Kernel structure
Tasks






WASM directives

The Handler
The Dispatcher
Setup Code

COMP200

Introduction to Multitasking


Most operating systems allow more than one


program to be running at a time.
This is achieved by sharing the CPUs time
between the tasks that are ready to be
processed.
The CPU executes each task for a short
while.
In a working system this all happens so
quickly that it appears that tasks are running
concurrently.
COMP200

Multitasking Diagram

Task 1

CPU time is shared between the three tasks.


Task 2
CPU

The CPU executes Task 1 for a while


Then Task 2
Then Task 3
Then Task 1
Then Task 2
And so on

COMP200

Task 3

Multitasking Kernel


We call the piece of code that facilitates


multitasking a Multitasking Kernel.


This code is right at the heart of a multitasking


operating system.

A multitasking kernel allows each task to be


written as if it has its own CPU to run on.
The multitasking kernel allocates the CPU to
different tasks in turn.

COMP200

How do we share the CPU?




Two main approaches:




Cooperative Multitasking


Tasks voluntarily release the CPU after doing some


processing.






But with this approach tasks can hog the CPU.

Preemptive Multitasking


Maybe when they are waiting for I/O.


Maybe when they terminate.

Tasks are given a limit on how long they can use the CPU for
at a time.
All tasks will get a go on the CPU.

We will only consider Preemptive Multitasking

COMP200

Preemptive Multitasking


The length of time a task is given is called its


Timeslice.
We can give higher priority tasks longer
timeslices.
If a task terminates, or requires a resource
then the kernel may end its timeslice early.

COMP200

Scheduling





The multitasking kernel must decide which task


will use the CPU next?
This process is called Scheduling.
A simple scheduling scheme would be to simply
go through each task in turn.
More complex scheduling schemes might take
into account things like




The assigned priority of the task


The likely time until the task will complete
The nature of the task (e.g. interactive, background)

COMP200

Saving/Restoring Context


When a task is taken off the CPU we must make


sure we can resume execution at a later time.


This involves saving information including..









Our multitasking should not interfere with this program in


any way.
The General Purpose Register contents.
The address of the instruction we were up to.

This procedure is known as Saving Context.


It generally involves saving these values to memory.
Before this task can be resumed we must restore
the context.

COMP200

Multitasking Kernel Parts




Setup code.


Handler.



Determine the cause of the exception.


If the timeslice is over then go to the Dispatcher.

Dispatcher.




Setup exceptions, as well as the tasks.

Save the context of the current task.


Determine which task should run next. (Scheduling)
Load the new tasks context and resume executing it.

Tasks.


These are the user tasks.


COMP200

10

Multitasking Kernel Parts




Our multitasking kernel system will consist of


four main parts





Setup code.
Handler.
Dispatcher.
Tasks.

COMP200

11

Kernel structure
main:
# Setup tasks.
# Setup interrupts.
# Go to the first task.
handler:
# Check which exception occurred.
# If it is the timer interrupt then we
# jump to handle_interrupt.
# Otherwise we jump to the system.
# exception handler.
handle_interrupt:
# Acknowledge the interrupt.
# Subtract 1 from the timeslice counter
# If timeslice counter is zero then goto dispatcher
# Otherwise we return from exception.
dispatcher:
# Save context for current task
# Select the next task to run
# Set the timeslice counter to an appropriate value
# Load context for next task
# Continue with next task

COMP200

12

Kernel structure (cont)


task1:
# Code for task1
task2:
# Code for task2
task3:
# Code for task3

Space for task context saving allocated here

COMP200

13

Kernel Structure Summary




A Multitasking kernel consists of




Setup code


Handler


Runs on every exception.

Dispatcher


Runs once at the start.

Called by handler on completion of timeslice.

Tasks


Run when loaded by the dispatcher.

COMP200

14

Tasks





Code
Data
Registers
Processor status






Program counter
Control registers

Stack
Stack pointer

COMP200

15

Task stacks


If we wish to have our tasks using stacks, then we must setup


a separate stack for each task.
If we do not have separate stacks, then tasks might interfere
with each other.
To setup a stack we must



Allocate some memory (probably using a .space directive)


Set the stack pointer for the task to the address of the end of that
memory.

How much memory should we allocate?




The amount has implications for the number of nested subroutine


calls we can make.
Just make sure there is enough.

COMP200

16

.equ directives
Allow us to define constants
 Much like #define in C.
 If we want to change the value then we only have to
change it in one place.
e.g.


.equ

sw

left_ssd_addr, 0x73002
$4, left_ssd_addr($0)

COMP200

17

.text, .data, and .bss




These allow us to separate the bits of our


program into segments


text segment


data segment


Instructions
Data with a specified initial value

bss segment



Uninitialised data
Any .word directives in the bss segment cannot have a
value specified.

COMP200

18

.space
Allows us to allocate a certain amount of space in
memory for a data structure.
 This saves us having to use .word repeatedly.
 Because no initial values are specified this can only
be used inside the bss segment.
e.g. reserve 18 words of space


task1_pcb:
.space

18

COMP200

19

Task stacks example


For example, allocating three stacks of size 100 words:

# Stack for task 1


.space 100
task1_stack:
# Stack for task 2
.space
100
task2_stack:
# Stack for task 3
.space
100
task3_stack:

COMP200

20

The Handler



Interrupt Handler
Use timer to generate regular (fast) interrupts





Timer is unavailable to tasks

Need to check interrupt is from timer


Every time_slice interrupts invoke dispatcher

COMP200

21

Handler Code
handler:
# Get the exception status register
movsg
$13, $estat
# Check for any other exceptions than
# the one we want (IRQ2)
andi
$13, $13, 0xffb0
# If no other exceptions have occurred then
# we branch to our handler.
beqz
$13, handle_interrupt
# Otherwise we must jump to the old
# exception handler.
lw
$13, old_vector($0)
jr
$13
handle_interrupt:
# Handle our timer interrupt

COMP200

22

Register $13


In our handler we are going to need one general


purpose register very early on.







We need to get the $estat register so we can check


the cause of the exception.

But we want to maintain the contents of the


registers so we dont affect our tasks.
The WRAMP processor provides for this by
automatically saving $13 on exception.
$13 is copied to the special register known as
$ers (Exception Register Save).
When an rfe instruction is executed then $ers
is copied back to $13.
COMP200

23

What does this mean for us?




For the programs we have been writing so far, we havent


been worrying about whether registers are saved or not no
problem.
For our multitasking kernel, we must be careful when saving
and restoring context.
Since $13 is already saved we can freely use it for the
handler and dispatcher, but we must save the value in $ers
as part of the task context.
When we restore context, we must restore the appropriate
$13 value from the saved context into $ers, so it will be
restored correctly when we return from exception.

COMP200

24

In terms of our code




The code in our handle_interrupt routine can


only safely use $13.


Remember we need to decrement the timeslice


counter, and maybe jump to the dispatcher.

Our dispatcher can use $13 as a base


address for the memory to save task context.
Other registers can be used once their value
has been saved.

COMP200

25

The Dispatcher


Roles of the Dispatcher







Save the context of the current task.


Determine which task should run next.
Load the next tasks context.
Continue executing with this next task.

COMP200

26

Saving Context


When we take a task off the CPU we need to


remember where we were up to.
This involves saving some things to memory:





All the general purpose registers (including $sp,


and $ra)
The exception address register ($ear)
The CPU control register ($cctrl)

The space in memory that we save these to


is known as a Process Control Block (PCB).
Each task must have its own PCB.
COMP200

27

Saving Context (cont)




We must be very careful in our dispatcher to


make sure that we dont lose the contents of
any registers before we save them.
Also when we are loading context, we must
ensure that we dont change the contents of
any registers after we have restored them.

COMP200

28

Process Control Block


$1
$2
$3
$4
$5
$6
$7
$8
$9
$10
$11
$12
$13
$sp
$ra
$ear
$cctrl

task1_pcb:
.space

COMP200

17

29

Managing Tasks


The Dispatcher must know which task it is


currently working on.
It must also be able to figure out which one to
work on next.
It must be able to find the PCBs for each
task so it can save and restore context.

COMP200

30

PCB version 2
link
$1
$2
$3
$4
$5
$6
$7
$8
$9
$10
$11
$12
$13
$sp
$ra
$ear
$cctrl

.equ
.equ
.equ
.equ

.equ
.equ
.equ

.bss
task1_pcb:
.space
task2_pcb:
.space
task3_pcb:
.space

COMP200

pcb_link,
pcb_reg1,
pcb_reg2,
pcb_reg3,

0
1
2
3

pcb_ra, 15
pcb_ear, 16
pcb_cctrl, 17

18
18
18

31

PCB linked list


Task 1 PCB

Task 2 PCB

Task 3 PCB

link
$1
$2
$3
$4
$5
$6
$7
$8
$9
$10
$11
$12
$13
$sp
$ra
$ear
$cctrl

link
$1
$2
$3
$4
$5
$6
$7
$8
$9
$10
$11
$12
$13
$sp
$ra
$ear
$cctrl

link
$1
$2
$3
$4
$5
$6
$7
$8
$9
$10
$11
$12
$13
$sp
$ra
$ear
$cctrl

COMP200

32

Setting up the tasks




Before we can get our system going we need


to setup some fields in each of our PCBs.


The link field




The stack pointer field




Must be set to the bottom of the stack for that task

The $ear field




Must be set to point to the next PCB in the list.

Must be set to the starting address of the task code

The $cctrl field




See the next slide

COMP200

33

Setting up the $cctrl field

31

$cctrl

Undefined (set to zero)

KU
OKU
IE
OIE

We want to unmask the timer interrupt (IRQ2).


We want to disable interrupts globally for now.
We want interrupts to become enabled when we
execute an rfe.
We want to ensure that we stay in kernel mode.

IRQ2

0 0 0 0 0 1 0 0 1 1 0 1

0x0000004d
COMP200

34

PCB setup code


# Unmask IRQ2,KU=1,OKU=1,IE=0,OIE=1
addi $5, $0, 0x4d
movgs $cctrl, $5

# Setup the pcb for task 1


la
$1, task1_pcb
# Setup the link field
la
$2, task2_pcb
sw
$2, pcb_link($1)
# Setup the stack pointer
la
$2, task1_stack
sw
$2, pcb_sp($1)
# Setup the $ear field
la
$2, task1_code
sw
$2, pcb_ear($1)
# Setup the $cctrl field
sw
$5, pcb_cctrl($1)

COMP200

We do this
for each task

35

Current task pointer




We need to maintain a pointer to the PCB of the


current task.
This will be initialised to the address of the PCB for
task 1.
When we switch task in the dispatcher this pointer
will be changed to the link field in the current PCB.
# Setup the first task
la
$1, task1_pcb
sw
$1, current_task($0)

.bss
current_task:
.word

COMP200

36

Dispatcher saving context


dispatcher:
# Get the base address of the current PCB
lw
$13, current_task($0)
# Save the registers
sw
$1, pcb_reg1($13)
sw
$2, pcb_reg2($13)

# $1 is saved now so we can use it


# Get the old value of $13
movsg $1, $ers
# and save it to the pcb
sw
$1, pcb_reg13($13)

# Save $ear
movsg $1, $ear
sw
$1, pcb_ear($13)
# Save $cctrl
movsg $1, $cctrl
sw
$1, pcb_cctrl($13)

COMP200

37

Dispatcher switching task

# Get the contents of the link field of the current PCB


lw
$13, pcb_link($13)
# Set that as our new task
sw
$13, current_task($0)

current_task

link
$1
$2
$3
$4
$5
$6
$7
$8
$9

link
$1
$2
$3
$4
$5
$6
$7
$8
$9

COMP200

38

Dispatcher restoring context

# Get the PCB value for $13 back into $ers


lw
$1, pcb_reg13($13)
movgs $ers, $1
# Restore $ear
lw
$1, pcb_ear($13)
movgs $ear, $1
# Restore $cctrl
lw
$1, pcb_cctrl($13)
movgs $cctrl, $1
# Restore the other registers
lw
$1, pcb_reg1($13)
lw
$2, pcb_reg2($13)

# Return to the new task


rfe

COMP200

39

Summary


The Handler and Dispatcher work together to


provide task switching
They must be carefully coded to save and
restore tasks exactly
The setup code provides PCBs for the
Dispatcher to use.
The Task Queue is formed using links in the
PCBs.
Try it yourself in the exercise
COMP200

40

You might also like