12
Process Sheduling Lab CSCI411 Lab Inspired by Operating System Concepts, 8 th Edition, Silberschatz, Galvin, Gagne Adapted by M.D Doman 2013 Exercise Goal: You will learn more about how process management is designed, particularly how the scheduler is implemented. The scheduler is the heart of the multiprogramming system; it is responsible for choosing a new task to run whenever the CPU is available.You will learn how implement both a First-Come-First-Served process dispatcher, and, effectively, a Round-Robin dispatcher . Contents Introduction .................................................................................................................................................. 2 Problem Statement ....................................................................................................................................... 4 Problem Statement 1: First-Come-First Serve Process Dispatcher............................................................... 4 Problem Statement 1: RoundRobin Dispatcher ............................................................................................ 5 Attacking the Problem .................................................................................................................................. 5 First-Come-First Serve Process Dispatcher ............................................................................................... 5 RoundRobin Dispatcher ............................................................................................................................ 7 Process to be dispatched: ......................................................................................................................... 7 fork() and exec() calls from shell program ................................................................................................ 8 I/O Redirection.......................................................................................................................................... 9 Submission Requirements .......................................................................................................................... 10 Appendix: Feedack Dispatcher.................................................................................................................... 11 Appendix: Fair-Share Scheduling Dispatcher .............................................................................................. 12

Process Sheduling Lab - Winthropfaculty.winthrop.edu/domanm/csci411/Projects/DispatcherLab.pdf · Process Sheduling Lab CSCI411 Lab Inspired by Operating System Concepts, 8th Edition,

  • Upload
    others

  • View
    19

  • Download
    0

Embed Size (px)

Citation preview

Process Sheduling Lab

CSCI411 Lab

Inspired by Operating System Concepts, 8th Edition, Silberschatz, Galvin, Gagne Adapted by M.D Doman 2013

Exercise Goal: You will learn more about how process management is designed, particularly

how the scheduler is implemented. The scheduler is the heart of the multiprogramming system; it

is responsible for choosing a new task to run whenever the CPU is available.You will learn how

implement both a First-Come-First-Served process dispatcher, and, effectively, a Round-Robin

dispatcher .

Contents Introduction .................................................................................................................................................. 2

Problem Statement ....................................................................................................................................... 4

Problem Statement 1: First-Come-First Serve Process Dispatcher ............................................................... 4

Problem Statement 1: RoundRobin Dispatcher ............................................................................................ 5

Attacking the Problem .................................................................................................................................. 5

First-Come-First Serve Process Dispatcher ............................................................................................... 5

RoundRobin Dispatcher ............................................................................................................................ 7

Process to be dispatched: ......................................................................................................................... 7

fork() and exec() calls from shell program ................................................................................................ 8

I/O Redirection .......................................................................................................................................... 9

Submission Requirements .......................................................................................................................... 10

Appendix: Feedack Dispatcher.................................................................................................................... 11

Appendix: Fair-Share Scheduling Dispatcher .............................................................................................. 12

Introduction Process management is the part of the OS that creates and destroys processes, provides mechanism for synchronization and

multiplexes the CPU among runnable processes. This exercised considers the last part, focusing on the scheduler.

Linux uses a different low-level view of how the OS is designed than the process model that appears at the system call

interface. In this low-level view, when the hardware is started a single hardware process begins to run. After the Linux kernel

has been loaded, the initial process is created (to use up all otherwise unused CPU cycles) and then Linux processes

are started according to how the machine is configured to operate. For example, getty (“get teletype”) processes are

started for each login port and daemons are started as specified. Thus, when the system is ready for normal

operation, several processes are in existence, most of which are blocked on various conditions. The runnable tasks

(those whose states are TASK_RUNNING) begin competing for the CPU. A fork() command defines a new kernel

process. The initial process simply multiplexes among the programs that are loaded in different address spaces as it

sees fit (and as defined by the task descriptor). That is, within the kernel, only one thread of execution exists that

jumps from one runnable task to another. At the system call interface, this behavior has the appropriate UNIX

process semantics.

Process States

The state field in the task descriptor can have any of the following values:

TASK_RUNNING

TASK_I NTERRU PTI BLE

TASK_UNINTERRUPTIBLE

TASK_ZOMBIE

TASK_STOPPED

As you go through the kernel code, you will often see statements of the form

current-e-state = TASK_".;

For example, in the sleep_on() code, state is set to TASK_UNINTERRUPTIBLE just before the process is placed on a wait queue.

In wake_up(), state is set back to TASK_RUNNING after it is removed from the wait queue. Recall that the distinction between the

TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE states relates to the sleeping task's ability to accept signals.

TASK_ZOMBIE represents a state in which a process has terminated, but its parent has not yet performed a wait() system call to

recognize that it has terminated. That is, the task does not disappear from the system until the parent is notified of its termination.

The final possible value for the state field is TASK_STOPPED, which means that the task has been halted by a signal or through

the ptrace mechanism. Thus most of the kernel code sets the state field to TASK_RUNNING, TASK_INTERRUPTIBLE, or

TASK_UNINTERRUPTIBLE.

Outside of the kernel, a different set of process states is visible to users. These states are defined by the POSIX

rnodel rather than by the Linux implementation, though they are analogous to the internal states kept in the task

descriptor. You can review these external states by inspecting the manual page for the STAT column in a ps

command. The ps has three fields.

ps FIELD 1:

The first field may be one of the following:

R: The process is running or runnable (its kernel state is TASK_RUNNING) .

S: The process is sleeping (its kernel state is TASK_INTERRUPTIBLE).

D: The process is in an uninterruptible sleep (its kernel state is

TASK_ UNNTERRUPTIBLE).

T: The process is either stopped or being traced (its kernel state is TASK_STOPPED).

Z: The process is in the zombie state (its kernel state is TASK_ZOMBIE

ps FIELD 2:

The second field of ps is either blank, or W if the process currently has no pages loaded. The third field is either

blank, or N if the process is running at a reduced priority. Most of the kernel code transitions the state of the process

among TASK_RUNNING, TASK_INTERRUPTIBLE, and TASK_UNINTER- RUPTIBLE. Roughly speaking, whenever

a process is to be blocked, it will be put on a queue (usually a wait queue) and its state will be changed to one of

TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE and then the scheduler will be called. A task/process, when

awakened, is removed from the wait queue and its state is changed to TASK_RUNNING. This leaves the scheduler

as the final critical piece of process management that this manual considers.

Scheduler Implementation

The scheduler is a kernel function, schedule(), that gets called from various other system call functions (usually via

sleep_on() ) and after every system call and slow interrupt. Each time that the scheduler is called, it does the

following.

1. Performs various periodic work (such as running device bottom halves).

2. Inspects the set of tasks in the TASK_RUNNING state.

3. Chooses one task to execute according to the scheduling policy.

4. Dispatches the task to run on the CPU until an interrupt occurs.

The scheduler contains three built-in scheduling strategies, identified by the constants SCHEDJIFO, SCHED_RR,

and SCHED_OTHER. The scheduling policy for each process can be set at runtime by using the

sched_setscheduler() system call (defined in kernel/sched.c). The task's current scheduling policy is saved in the

policy field. The SCHED_FIFO and SCHED_RR policies are intended to be sensitive to real-time requirements, so

they use scheduling priorities in the range [0:99], whereas SCHED_OTHER handles only the zero priority. Each

process also has a priority field in its task descriptor. Conceptually, the scheduler has a multilevel queue with 100

levels (corresponding to the priorities from 0 to 99). Whenever a process with a priority that is higher than the

currently running process becomes runnable, the lower-priority process is preempted and the higher-priority process

begins to run. Of course, in the normal timesharing (SCHED_OTHER) policy, processes do not preempt one another,

since they all have a priority of zero.

Any task/process that uses the SCHED_FIFO policy must have superuser permission and have a priority of 1 to 99

(that is, it has a higher priority than all SCHED_OTHER tasks). Thus a SCHED_FIFO task that becomes runnable

takes priority over every SCHED_OTHER task. The SCHED_FIFO policy does not reschedule on a timer interrupt.

Once a SCHE SCHED_FIFO task gets control of the CPU, it will retain control until either it completes, blocks by an

I/O call, calls sched_yield(), or until a higher priority task becomes runnable. If it yields or is preempted by another,

higher-priority, task, it is placed at the end of the task queue at the same priority level.

The SCHED_RR policy has the same general semantics as the SCHED_FIFO policy, except that it uses the time-

slicing mechanism to multiplex the CPU among tasks that are in the highest-priority queue. A running SCHED_RR

task that is preempted by a higher-priority task is placed back on the head of its queue so that it will resume when its

queue priority is again the highest in the system. The preempted process will then be allowed to complete its time

quantum.

The SCHED_OTHER policy is the default timesharing policy for Linux. It uses the conventional time-slicing

mechanism (rescheduling on each timer interrupt) to put an upper bound on the amount of time that a task can use

the CPU continuously if other tasks are waiting to use it. This policy ignores the static priorities used to discriminate

among tasks in the other two policies. Instead, a dynamic priority is computed on the basis of the value assigned to

the task by the nice() or setpriorty() system call and by the amount of time that a process has been waiting for the

CPU to become available.

Problem Statement

Design a dispatcher that satisfies the criteria defined below.

o The dispatcher will be created as a child process from your shell program. To do this, you will replace the system() function call with fork() and exec() calls.

o Because the shell will be the session leader it will have control and access to the terminal input.

The dispatcher must accept a file with list of the programs to ‘dispatch’.For the dispatcher process, redirect the stdin and stdout to files.

Problem Statement 1: First-Come-First Serve Process Dispatcher

Create a FCFS Dispatcher. This will be a shell program. An input file will associate an arrival time for each program so that execution of the program can not start until at least that time elapses. e.g.

<arrival time>,program name, <priority>,<cputime>

0,P1,0,3 1st Job: Arrival time 0, priority 0 (Real-Time), requiring 3 seconds of CPU time 2,P2,0,6 2st Job: Arrival time 2, priority 0 (Real-Time), requiring 6 seconds of CPU time 4,P3,0,4 6,P4,0,5 8,P5,0,2

You will need to create a queue of job structures so that you only dequeue a job when the appropriate time has elapsed and there is no longer a current job running. (There is a guarantee that the jobs presented in the input dispatch file will be in chronological order of arrival).

Problem Statement 1: RoundRobin Dispatcher

Round Robin Dispatcher (from Stallings)

Try this with the following job list with a quantum of 1 sec:

<arrival time>,program name, <priority>,<cputime>

0,P1,1,3 1st Job: Arrival time 0, priority 1, requiring 3 seconds of CPU time 2,P2,1,6 2st Job: Arrival time 2, priority 1, requiring 6 seconds of CPU time 4,P3,1,4 6,P4,1,5 8,P5,1,2

Attacking the Problem

First-Come-First Serve Process Dispatcher

The first thing to do is to define a process control block structure or class such as:

struct pcb {

pid_t pid; // system process ID

char * args[MAXARGS]; // program name and args

int arrivaltime;

int remainingcputime;

struct pcb * next; // links for PCB handlers

};

typedef struct pcb PCB;

typedef PCB * PcbPtr;

This structure/class can be used for all the queues (linked lists) needed for the project (with a few extra elements in the structure).

Dispatcher algorithm:

The basic First-Come-First-Served (FCFS) dispatcher algorithm can be reduced to the following pseudo-code. Note that the tests may appear to be out of order (shouldn't one be doing the test at 4.i after the sleep rather than before it?). With the current model it makes no difference, but there are reasons for doing it in this order as we shall see when we come to the multiple dispatcher models later on.

1. Initialize dispatcher queue; 2. Fill dispatcher queue from dispatch list file; 3. Start dispatcher timer (dispatcher timer = 0);

4. While there's anything in the queue or there is a currently running process: i. If a process is currently running;

a. Decrement process remainingcputime;

b. If times up: A. Send SIGINT to the process to terminate it;

B. Free up process structure memory ii. If no process currently running &&

dispatcher queue is not empty && arrivaltime of process at head of queue is <= dispatcher timer:

a. Dequeue process and start it (fork & exec)

b. Set it as currently running process; iii. sleep for one second;

iv. Increment dispatcher timer; v. Go back to 4.

5. Exit.

Be sure to include: #include <sys/types.h> and #include <signal.h>

There are a number of ways to terminate a job. Since we know its process ID, the easiest way to do this is to send the process a terminating signal using the kill function. The one we need to use here is SIGINT, which is the equivalent of the Ctrl-C keyboard interrupt fielded by the shell. Use waitpid will check the termination of your child process.

kill (pid, SIGINT);

process_status = PCB_TERMINATED;

RoundRobin Dispatcher

To suspend and restart processes as required by the Round Robin dispatcher will require the use of the SIGTSTP and SIGCONT signals as well as SIGINT that we used for the FCFS dispatcher.(see below).

To make the Round Robin dispatcher work (RR q=1) the logic should be:

1. Initialize dispatcher queues (input queue and RR queue); 2. Fill input queue from dispatch list file; 3. Start dispatcher timer (dispatcher timer = 0);

4. While there's anything in any of the queues or there is a currently running process: i. Unload any pending processes from the input queue:

While (head-of-input-queue.arrival-time <= dispatcher timer)

dequeue process from input queue and enqueue on RR queue; ii. If a process is currently running:

a. Decrement process remainingcputime;

b. If times up: A. Send SIGINT to the process to terminate it;

B. Free up process structure memory; c. else if other processes are waiting in RR queue:

A. Send SIGTSTP to suspend it;

B. Enqueue it back on RR queue; iii. If no process currently running && RR queue is not empty:

a. Dequeue process from RR queue b. If already started but suspended, restart it (send SIGCONT to it)

else start it (fork & exec)

c. Set it as currently running process; iv. sleep for one second;

v. Increment dispatcher timer; vi. Go back to 4.

5. Exit.

or something like that!

Try this with the following job list with a quantum of 1 sec:

<arrival time>,program name, <priority>,<cputime>

0,P1,1,3 1st Job: Arrival time 0, priority 1, requiring 3 seconds of CPU time 2,P2,1,6 2st Job: Arrival time 2, priority 1, requiring 6 seconds of CPU time 4,P3,1,4 6,P4,1,5 8,P5,1,2

Process to be dispatched:

The process to be dispatched can be as simple as the following. In fact, it can be the following. The idea is to have

the process sleep for a period to ensure the process will run for an allotted time. The sleep function causes the calling

process to be suspended until either the amount of wall clock time specified by seconds has elapsed, or a signal is

caught by the process and the signal handler returns. The return value from sleep is either 0 or the number of un-

slept seconds if the sleep has been interrupted by a signal interrupt.

#include <iostream>

#include <stdlib.h>

#include <unistd.h>

using namespace std;

int main (int arg, char *argv[])

{

int n = atoi(argv[2]);

while (0 < n)

{

printf("Process Executing: %s; PID: %lu\n”, argv[1], (unsigned long)getpid());

sleep(1);

n--;

}// end of while

}//end of main

fork() and exec() calls from shell program

Since we need to control what open files and file descriptors are passed the dispatcher, we need more control over there execution. To do this we need to use the fork and exec system calls.

1. In your Shell program, replace the use of system with fork and exec 2. You will now need to more fully parse the incoming command line so that you can set up the argument array

(char *argv[] in the above examples). 3. You will find that while a system function call only returns after the program has finished, the use of fork

means that two processes are now running in foreground. In most cases you will not want your shell to ask for the next command until the child process has finished. This can be accomplished using the wait or waitpid functions. e.g.

switch (child_pid = fork()) {

// error condition

case -1:

syserrmsg("fork",NULL);

break;

// I am the dispatcher (child) process

case 0:

execvp(args[0], args);

syserrmsg("exec failed -",args[0]);

exit(127);

// I am the shell (parent) process

default:

if (!dont_wait)

waitpid(pid, &status, WUNTRACED);

Obviously, in the above example, if you wanted to run the child process 'in background', the flag dont_wait would be set and the shell would not wait for the child process to terminate.

I/O Redirection

Your project shell must support I/O-redirection on both stdin and stdout. i.e. the command line:

programname arg1 arg2 < inputfile > outputfile

will execute the program programname with arguments arg1 and arg2, the stdin FILE stream replaced by inputfile and the stdout FILE stream replaced by outputfile.

With output redirection, if the redirection character is > then the outputfile is created if it does not exist and truncated if it does. If the redirection token is >> then outputfile is created if it does not exist and appended to if it does.

Note: you can assume that the redirection symbols, < , > & >> will be delimited from other command line arguments

by white space - one or more spaces and/or tabs. This condition and the meanings for the redirection symbols outlined above and in the project differ from that for the standard shell.

I/O redirection is accomplished in the child process immediately after the fork and before the exec command. At this

point, the child has inherited all the file-handles of its parent and still has access to a copy of the parent memory. Thus, it will know if redirection is to be performed, and, if it does change the stdin and/or stdout file streams, this will only affect the child not the parent.

The easiest way to do this is to use freopen. freopen opens a specified file on a specified stream, closing the stream

first if it is already open. This function is typically used to open a specified file as one of the predefined streams, stdin, stdout, or stderr.

Freopen (“filename”, “type”, stdin/stdout/stderr)

The type string is the standard open argument:

type Description

r or rb w or wb a or ab r+ or r+b or rb+ w+ or w+b or wb+ a+ or a+b or ab+

open for reading truncate to 0 length or create for writing append; open for writing at end of file, or create for writing open for reading and writing truncate to 0 length or create for reading and writing open or create for reading and writing at end of file

where b as part of type allows the standard I/O system to differentiate between a text file and a binary file.

Thus:

freopen("inputfile", "r", stdin);

would open the file inputfile and use it to replace the standard input stream, stdin.

. ***********************************************************************/

switch (child_pid = fork()) {

// error condition

case -1:

syserrmsg("fork",NULL);

break;

// I am the dispatcher (child) process

case 0:

// I/O redirection: stdin */

if (myINfile.txt) {

if (!freopen((myINfile.txt, "r", stdin))

errmsg((myINfile.txt,"can't be open for i/p redirect.");

}

if (myOUTfile.txt) {

if (!freopen(myOUTfile.txt, sstatus.outmode, stdout))

errmsg(myOUTfile.txt,"can't be open for o/p redirect.");

}

execvp(args[0], args);

syserrmsg("exec failed -",args[0]);

exit(127);

// I am the shell (parent) process

default:

if (!dont_wait)

waitpid(pid, &status, WUNTRACED);

Submission Requirements Submit your source code (Shell script file) and makefile if necessary.

Submit a lab report. This includes (from :

http://www.columbia.edu/cu/biology/faculty/mowshowitz/howto_guide/lab_report.html)

1. In a formal design document:

a. Describe and discuss the structures or classes used by the dispatcher for queuing, dispatching and allocating memory and other resources.

b. If your design required memory allocation: Describe and discuss what memory allocation algorithms you could have used and justify your final design choice.

c. Describe and justify the overall structure of your program, describing the various modules and major functions (descriptions of the function 'interfaces' are expected).

d. Discuss the possible use of a multilevel dispatching scheme, which would combine FCFS for real-time processes and RR or Feedback for prioritized processes.

e. Include results from your run and an explanation or interpretation of the output. f. Diagram the flow of control (Gantt diagram) that illustrates the execution of the dispatched

processes. g. Discuss turnaround time for each algorithm.

1. Determine (and show how it was calculated) the turnaround time of each process (i.e., how long was it in the system?)

2. Determine the average turnaround time. h. Include the source of our code. i. Include instructions on how to compile and run your code.

The formal design document is expected to have in-depth discussions, descriptions and arguments.

Appendix: Feedack Dispatcher

Having got the Round Robin dispatcher going, it is now a comparatively simple step to extend your code to make a multi-queue

Three Level Feedback Dispatcher (from Stallings Fig 9.10)

The logic is very similar to the previous exercise except that instead of enqueueing an incomplete process back onto the same (RR) queue that the process came from, the process is enqueued onto a lower priority process queue (if there is one to receive it). Obviously, the lowest priority queue operates in exactly the same way as a simple Round Robin queue.

To make the Feedback dispatcher work in the same way as described in Stallings Figure 9.5 (Feedback q=1) the logic should be:

1. Initialize dispatcher queues (input queue and feedback queues); 2. Fill input queue from dispatch list file; 3. Start dispatcher timer (dispatcher timer = 0);

4. While there's anything in any of the queues or there is a currently running process: i. Unload pending processes from the input queue:

While (head-of-input-queue.arrival-time <= dispatcher timer)

dequeue process from input queue and enqueue on highest priority feedback queue (assigning it the appropriate priority);

ii. If a process is currently running: a. Decrement process remainingcputime;

b. If times up: A. Send SIGINT to the process to terminate it;

B. Free up process structure memory; c. else if other processes are waiting in any of the feedback queues:

A. Send SIGTSTP to suspend it;

B. Reduce the priority of the process (if possible) and enqueue it on the appropriate feedback queue

iii. If no process currently running && feedback queues are not all empty: a. Dequeue a process from the highest priority feedback queue that is not empty b. If already started but suspended, restart it (send SIGCONT to it)

else start it (fork & exec)

c. Set it as currently running process; iv. sleep for one second;

v. Increment dispatcher timer; vi. Go back to 4.

5. Exit.

or something like that! As you can see, there is really not much difference between this and the Round Robin we completed last week.

Try this with the following job list with a quantum of 1 sec:

<arrival time>,program name, <priority>,<cputime>

0,P1,3,3 1st Job: Arrival time 0, priority 1 (Real-Time), requiring 3 seconds of CPU time 2,P2,3,6 2st Job: Arrival time 2, priority 1 (Real-Time), requiring 6 seconds of CPU time 4,P3,3,4 6,P4,3,5 8,P5,3,2

Appendix: Fair-Share Scheduling Dispatcher

Fair-share scheduling [Bach, 1986] is a more abstract strategy for scheduling than any of the three built-in strategies. The idea is that the CPU should be shared among sets of processes according to the groups that own those processes. For example, suppose that Tom, Dick, and Harry belong to different groups and are logged in to a machine that uses fair-share scheduling. Further suppose that Tom has only one runnable process, Dick has three, and Harry has six. Fair-share scheduling calls for ten runnable processes and instead of each process getting 10 of the CPU cycles, the three groups each get one-third of the CPU cycles to allocate to the processes that they own. So Tom's single process would get 33 of the available CPU cycles, each of Dick's three processes would get about 11 of the available CPU cycles, and each of Harry's six processes would get about 5.5 of the CPU cycles. If Betty belonged to Dick's group and she logged in to the machine and created two more processes, then Tom's group would have one process but still receive one-third of the CPU cycles. Dick and Betty would have five processes and would be allocated one-third of the CPU cycles, distributed equally among their five processes, with each receiving about 6.7 of the total machine cycles. Each of Harry's six processes would continue to receive about 5.5 of the available CPU cycles.