Goal: Build scheduler for time-shared jobs; mix of interactive, i/o-bound, and cpu-bound jobs in workload Metric: Minimize average waiting time You write code for 5 routines (these are called by OS and pass in the PCB of the currently running process): tick(process_t *p) -- called every 10 ms block(process_t *p) -- called when p blocks (on i/o) ready(process_t *p) -- called when p is ready (i/o is done) arrive(process_t *p) -- p arrives into system terminate(process_t *p) -- p exists system You also specify what picknext(readyq_t *q) should do. You have available: gettime() - returns current time of day run(process_t *p) - has dispatcher run job p add(q, p) - adds process p to readyq q remove(q, p) - removes process p from readyq q You need to define data structure process_t. Start with: typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q } process_t; -------------------------------------------------- Code #1: -------------------------------------------------- How to minimize waiting time? --> SJF (non-preemptive) keep as simple as possible Idea: Track last CPU burst; use to predict next CPU burst typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int burst; int start; } process_t; block(process_t *p) { process_t *n; p->burst = p->start - gettime(); remove(q, p); n = picknext(q); n->start = gettime(); run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); n->start = gettime(); run(n); } arrive(process_t *p) { p->burst = getaverageburst(q); add(q, p); } ready(process_t *p) { add(q, p); } Role of picknext(q): Return p with smallest value for burst -------------------------------------------------- Code #2: -------------------------------------------------- Make so that picknext() and addq() are not O(# processes) Idea: map each cpuburst length to a priority; still not preemptive Assume 60 priority queues (higher priority --> higher number) FIFO within a queue typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int burst; int start; int priority; } process_t; block(process_t *p) { process_t *n; p->burst = p->start - gettime(); remove(q, p); n = picknext(q); n->start = gettime(); run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); n->start = gettime(); run(n); } arrive(process_t *p) { p->priorty = 30; add(q, p); } ready(process_t *p) { // This assigns range of burst lengths to different priorities p->priority = map(p->burst); add(q, p); } Role of picknext(q): Return p at head of highest priority queue -------------------------------------------------- Code #3: -------------------------------------------------- Add preemption Add RR between jobs at same level (this helps when we made a mistake in predicting the cpuburst length) typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int burst; int start; int priority; int timeleft; } process_t; tick(process_t *p) { p->timeleft--; if (p->timeleft == 0) { p->timeleft = 10; add(q, p); n = picknext(q); n->start = gettime(); run(n); } } block(process_t *p) { process_t *n; p->burst = p->start - gettime(); remove(q, p); n = picknext(q); n->start = gettime(); run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); n->start = gettime(); run(n); } arrive(process_t *p) { p->priorty = 30; p->timeleft = 10; add(q, p); } ready(process_t *p) { // This assigns range of burst lengths to different priorities p->priority = map(p->burst); p->timeleft = 10; add(q, currentjob); // this code needs to be more careful to also save state of running job n=picknext(q); run(n); } Role of picknext(q): Return p at head of highest priority queue -------------------------------------------------- Code #4: -------------------------------------------------- Simplify our rules for determining the priority of a job - if use timeslice, inc priority; - if block, dec priority; typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int priority; int timeleft; } process_t; tick(process_t *p) { p->timeleft--; if (p->timeleft == 0) { p->priority--; p->timeleft = 10; add(q, p); n = picknext(q); run(n); } } block(process_t *p) { process_t *n; remove(q, p); n = picknext(q); run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); run(n); } arrive(process_t *p) { p->priorty = 30; p->timeleft = 10; add(q, p); } ready(process_t *p) { // This assigns range of burst lengths to different priorities p->priority++; p->timeleft = 10; add(q, currentjob); // this code needs to be more careful to also save state of running job n=picknext(q); run(n); } Role of picknext(q): Return p at head of highest priority queue -------------------------------------------------- Code #5: -------------------------------------------------- Make timeslice a function of priority level - high priorities (short jobs) have short time slices - low priorities (long jobs) have long time slices typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int priority; int timeleft; } process_t; tick(process_t *p) { p->timeleft--; if (p->timeleft == 0) { p->priority--; p->timeleft = Timeslice[p->priority]; add(q, p); n = picknext(q); run(n); } } block(process_t *p) { process_t *n; remove(q, p); n = picknext(q); run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); run(n); } arrive(process_t *p) { p->priorty = 30; p->timeleft = 10; add(q, p); } ready(process_t *p) { // This assigns range of burst lengths to different priorities p->priority++; p->timeleft = Timeslice[p->priority]; add(q, currentjob); // this code needs to be more careful to also save state of running job n=picknext(q); run(n); } Role of picknext(q): Return p at head of highest priority queue -------------------------------------------------- Code #6: -------------------------------------------------- Ensure jobs cannot starve typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int priority; int timeleft; int run; } process_t; tick(process_t *p) { ticks--; if (ticks == 0) { for each p in q { if (p->run == 0) { p->priority++; p->timeleft = Timeslice[p->priority]; } p->run = 0; } ticks = 100; } p->timeleft--; if (p->timeleft == 0) { p->priority--; p->timeleft = Timeslice[p->priority]; add(q, p); n = picknext(q); n->run = 1; run(n); } } block(process_t *p) { process_t *n; remove(q, p); n = picknext(q); n->run = 1; run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); n->run = 1; run(n); } arrive(process_t *p) { p->priorty = 30; p->timeleft = 10; add(q, p); } ready(process_t *p) { // This assigns range of burst lengths to different priorities p->priority++; p->timeleft = Timeslice[p->priority]; add(q, currentjob); // this code needs to be more careful to also save state of running job n=picknext(q); n->run = 1; run(n); } Role of picknext(q): Return p at head of highest priority queue -------------------------------------------------- Code #7: -------------------------------------------------- Ensure jobs cannot game scheduler (With small I/O) typedef struct { dispatch_t state; // everything the dispatcher needs (regs) process_t *next, *prev; // things to maintain q int priority; int timeleft; int run; int iostart; } process_t; tick(process_t *p) { ticks--; if (ticks == 0) { for each p in q { if (p->run == 0) { p->priority++; p->timeleft = Timeslice[p->priority]; } p->run = 0; } ticks = 100; } p->timeleft--; if (p->timeleft == 0) { p->priority--; p->timeleft = Timeslice[p->priority]; add(q, p); n = picknext(q); n->run = 1; run(n); } } block(process_t *p) { process_t *n; p->startio = gettime(); remove(q, p); n = picknext(q); n->run = 1; run(n); } terminate(process_t *p) { process_t *n; remove(q, p); n = picknext(q); n->run = 1; run(n); } arrive(process_t *p) { p->priorty = 30; p->timeleft = 10; add(q, p); } ready(process_t *p) { // This assigns range of burst lengths to different priorities if (gettime() - p->iostart > SOMETHRESHOLD) { p->priority++; } p->timeleft = Timeslice[p->priority]; add(q, currentjob); // this code needs to be more careful to also save state of running job n=picknext(q); n->run = 1; run(n); } Role of picknext(q): Return p at head of highest priority queue