0% found this document useful (0 votes)
66 views

Digital Assignment - 2

This document contains details of a digital assignment on operating system concepts for a student named Shubh Kapil. It includes tasks to implement a kernel module, new system call, interrupts, compare overhead of system calls vs procedure calls, and use various UNIX system calls like fork, exec, getpid etc. It also describes preemptive vs non-preemptive scheduling and includes algorithms to demonstrate process control system calls like fork, execve and wait.

Uploaded by

shubh kapil
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
66 views

Digital Assignment - 2

This document contains details of a digital assignment on operating system concepts for a student named Shubh Kapil. It includes tasks to implement a kernel module, new system call, interrupts, compare overhead of system calls vs procedure calls, and use various UNIX system calls like fork, exec, getpid etc. It also describes preemptive vs non-preemptive scheduling and includes algorithms to demonstrate process control system calls like fork, execve and wait.

Uploaded by

shubh kapil
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 28

DIGITAL ASSIGNMENT -2

NAME: SHUBH KAPIL


(20BCI0265)

SUBJECT: OPERATING SYSTEM

COURSE CODE: CSE 2005


SYSTEM CALL, DEVICE DRIVERS, TIMER,
INTERRUPTS

a. Kernel space programming: Implement and add a loadable


kernel module to Linux kernel, demonstrate using insmod,
lsmod and rmmod commands. A sample kernel space
program should print the "Hello World" while loading the
kernel module and "Goodbye World" while unloading the
kernel module.

b. Implement a new system call, add this new system call in


the Linux kernel (any kernel source, any architecture and
any Linux kernel distribution) and demonstrate the use of
same.

c. [Interrupts] Create an interrupt to handle a system call and


continue the previously running process after servicing the
interrupt.
d. Compare the overhead of a system call with a procedure
call. What is the cost of a minimal system call?

A system call is expected to be significantly more expensive than a


procedure call (provided that both perform very little actual
computation). A system call involves the following actions, which do
not occur during a simple procedure call, and thus entails a high
overhead:

• A context switches
• A trap to a specific location in the interrupt vector
• Control passes to a service routine, which runs in 'monitor' mode
• The monitor determines what system call has occurred
• Monitor verifies that the parameters passed are correct and legal

For example, here's a sample code for measuring the time taken for
a simple system call and a simple function call:

#include <unistd.h>
#include <assert.h>

int foo(){
return(10);
}

long nanosec(struct timeval t){ /* Calculate nanoseconds in a timeval


structure */
return((t.tv_sec*1000000+t.tv_usec)*1000);
}
main(){
int i,j,res;
long N_iterations=1000000; /* A million iterations */
float avgTimeSysCall, avgTimeFuncCall;
struct timeval t1, t2;

/* Find average time for System call */


res=gettimeofday(&t1,NULL); assert(res==0);
for (i=0;i<N_iterations; i++){
j=getpid();
}
res=gettimeofday(&t2,NULL); assert(res==0);
avgTimeSysCall = (nanosec(t2) - nanosec(t1))/(N_iterations*1.0);

/* Find average time for Function call */


res=gettimeofday(&t1,NULL); assert(res==0);
for (i=0;i<N_iterations; i++){
j=foo();
}
res=gettimeofday(&t2,NULL); assert(res==0);
avgTimeFuncCall = (nanosec(t2) - nanosec(t1))/(N_iterations*1.0);

printf("Average time for System call getpid : %f\n",avgTimeSysCall);


printf("Average time for Function call : %f\n",avgTimeFuncCall);
}

Sample output on a linux machine :

> gcc -O0 testtime.c -o testtime


> ./testtime
Average time for System call getpid : 394.778015
Average time for Function call : 15.08000
e. Write programs using the following system calls of UNIX
operating system: fork, exec, getpid, exit, wait, close, stat,
opendir, readdir

ALGORITHM:

STEP 1: Start the program.


STEP 2: Declare the variables pid,pid1,pid2. STEP 3: Call fork()
system call to create process. STEP 4: If pid==-1, exit.
STEP 5: Ifpid!=-1 , get the process id using getpid(). STEP 6: Print
the process id.
STEP 7:Stop the program

PROGRAM:

#include<stdio.h> #include<unistd.h> main()


{
int pid,pid1,pid2; pid=fork(); if(pid==-1)
{
printf(“ERROR IN PROCESS CREATION \n”);
exit(1);
}
if(pid!=0)
{
pid1=getpid();
printf(“\n the parent process ID is %d\n”, pid1);
}
else
{
pid2=getpid();
printf(“\n the child process ID is %d\n”, pid2);
}
}
f. Write programs using the I/O system calls of UNIX
operating system (open, read, write, etc)

#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h> //needed for open
#include <sys/stat.h> //needed for open
#include <fcntl.h> //needed for open

using namespace std;

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


{
int inFile;
int n_char=0;
char buffer[10];

inFile=open(argv[1],O_RDONLY);
if (inFile==-1)
{
exit(1);
}

//Use the read system call to obtain 10 characters from


inFile
while( (n_char=read(inFile, buffer, 10))!=0)
{
//Display the characters read
n_char=write(1,buffer,n_char);
}

close (inFile);

return 0;
}
➢Process Management

A. Compare the performance of Pre-emptive scheduler (Pre-


emptive SJF – SRTN) with Non-preemptive Scheduler
(Priority). Display individual Service time, waiting time,
turnaround time and response time. Also display average
waiting time and turnaround time.

Key Differences Between Preemptive and Non-Preemptive


Scheduling:
1. In preemptive scheduling, the CPU is allocated to the processes for
a limited time whereas, in non-preemptive scheduling, the CPU is
allocated to the process till it terminates or switches to the waiting
state.
2. The executing process in preemptive scheduling is interrupted in
the middle of execution when higher priority one comes whereas, the
executing process in non-preemptive scheduling is not interrupted in
the middle of execution and waits till its execution.
3. In Preemptive Scheduling, there is the overhead of switching the
process from the ready state to running state, vise-verse and
maintaining the ready queue. Whereas in the case of non-preemptive
scheduling has no overhead of switching the process from running
state to ready state.
4. In preemptive scheduling, if a high-priority process frequently
arrives in the ready queue then the process with low priority has to
wait for a long, and it may have to starve. , in the non-preemptive
scheduling, if CPU is allocated to the process having a larger burst
time then the processes with small burst time may have to starve.
5. Preemptive scheduling attains flexibility by allowing the critical
processes to access the CPU as they arrive into the ready queue, no
matter what process is executing currently. Non-preemptive
scheduling is called rigid as even if a critical process enters the ready
queue the process running CPU is not disturbed.
6. Preemptive Scheduling has to maintain the integrity of shared data
that’s why it is cost associative which is not the case with Non-
preemptive Scheduling.

Pre-emptive scheduler (Pre-emptive SJF):


Non-preemptive Scheduler (Priority):
B. Process control system calls: The demonstration of fork, execve
and wait system calls along with zombie and orphan states.

I. Implement the C program in which main program accepts


the integers to be sorted. Main program uses the fork
system call to create a new process called a child process.
Parent process sorts the integers using merge sort and
waits for child process using wait system call to sort the
integers using quick sort. Also demonstrate zombie and
orphan states.
#include<stdio.h>
#define MAX 20
void mergeSort(int arr[],int low,int mid,int high);
void partition(int arr[],int low,int high);
void quicksort(int [10],int,int);
void main()
{
int a[10],i,n,x;
printf(“\n ————————-“);
printf(“Enter the no of element:”);
scanf(“%d”,&n);

printf(“enter the elements:”);


for(i=0;i<n;i++)
scanf(“%d”,&a[i]);

x=fork();
/* fork()
create a child process.
If fork returns value 0, then it is the child process
If it returns some positive number then it is parent process. The
positive number is the pid of a child.
*/
if(x==0) // This part of a if is executed by Child Process
{
printf(“\nchild process = %d”,getpid()); // Pid returns the Pid of the
process executing getpid() function

quicksort(a,0,n-1); // Child calls the quick sort function


printf(“\nAfter quick sorting elements are: “);

for(i=0;i<n;i++)
printf(” %d”,a[i]);
}
else // This part of a if is executed by Parent Process
{
printf(“\nparent process=%d \n”,getpid());
partition(a,0,n-1); // Parent process calls Merge Sort function
printf(“\nAfter merge sorting elements are: “);
for(i=0;i<n;i++)
printf(“%d “,a[i]);
}
}

void partition(int arr[],int low,int high)


{

int mid;
if(low<high)
{
mid=(low+high)/2;
partition(arr,low,mid);
partition(arr,mid+1,high);
mergeSort(arr,low,mid,high);
}
}

void mergeSort(int arr[],int low,int mid,int high)


{
int i,m,k,l,temp[MAX];
l=low;
i=low;
m=mid+1;
while((l<=mid)&&(m<=high))
{
if(arr[l]<=arr[m])
{
temp[i]=arr[l];
l++;
}
else
{
temp[i]=arr[m];
m++;
}
i++;
}
if(l>mid)
{
for(k=m;k<=high;k++)
{
temp[i]=arr[k];
i++;
}
}
else
{
for(k=l;k<=mid;k++)
{
temp[i]=arr[k];
i++;
}
}
for(k=low;k<=high;k++)
{
arr[k]=temp[k];
}
}

void quicksort(int x[10],int first,int last)


{
int pivot,j,temp,i;
if(first<last)
{
pivot=first;
i=first;
j=last;
while(i<j)
{
while(x[i]<=x[pivot]&&i<last)
i++;
while(x[j]>x[pivot])
j–;
if(i<j)
{
temp=x[i];
x[i]=x[j];
x[j]=temp;
}
}
temp=x[pivot];
x[pivot]=x[j];
x[j]=temp;
quicksort(x,first,j-1);
quicksort(x,j+1,last);
}
}
II. Implement the C program in which main program accepts
an integer array. Main program uses the fork system call
to create a new process called a child process. Parent
process sorts an integer array and passes the sorted array
to child process through the command line arguments of
execve system call. The child process uses execve system
call to load new program that uses this sorted array for
performing the binary search to search the particular item
in the array.
//Program 1 :

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

void quickSort(int [],int ,int );


int partition(int [],int ,int );

void mergeSort(int [],int ,int );


void merge(int [],int ,int ,int ,int );

int main()
{
int i,j,n;
int *status=NULL;
int arr[30];

printf("\nEnter the number of elements:");


scanf("%d",&n);

for(i=0;i<n;i++)
{
scanf("%d",&arr[i]);
}
pid_t pid;
pid=fork();

if(pid==0)
{
printf("\n\t This is child process. ");
printf("\n\t My process id is : %d", getpid());
printf("\n\t My Parent process id is : %d", getppid());
quickSort(arr,0,n-1);
printf("\nQuicksort");
for(i=0;i<n;i++)
printf(" %d",arr[i]);
printf("\n\n");
}
else
{

printf("\n\n\t Parent process resumed after the execution of child


process with PID %d", pid);
printf("\n\t My process id is : %d", getpid());
printf("\n\t My Parent process id is : %d", getppid());
mergeSort(arr,0,n-1);
printf("\nMergesort:");
for(i=0;i<n;i++)
printf(" %d",arr[i]);
printf("\n\n");
pid=wait(status);

void quickSort(int arr[],int low,int high)


{
int j;
if(low<high)
{
j=partition(arr,low,high);
quickSort(arr,low,j-1);
quickSort(arr,j+1,high);
}
}

int partition(int arr[],int low,int high)


{
int i,j,temp,pivot;
pivot=arr[low];
i=low;
j=high+1;

do
{
do
i++;
while(arr[i]<pivot && i<=high);
do
j--;
while(arr[j]>pivot);

if(i<j)
{
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
while(i<j);

arr[low]=arr[j];
arr[j]=pivot;

return(j);
}

void mergeSort(int arr[],int low,int high)


{
int mid;
if(low<high)
{
mid=(low+high)/2;
mergeSort(arr,low,mid);
mergeSort(arr,mid+1,high);
merge(arr,low,mid,mid+1,high);
}

}
void merge(int arr[],int i1,int j1,int i2,int j2)
{
int temp[50];
int i,j,k;
i=i1;
j=i2;
k=0;

while(i<=j1 && j<=j2)


{
if(arr[i]<arr[j])
temp[k++]=arr[i++];
else
temp[k++]=arr[j++];
}
while(i<=j1)
temp[k++]=arr[i++];
while(j<=j2)
temp[k++]=arr[j++];

for(i=i1,j=0;i<=j2;i++,j++)
arr[i]=temp[j];
}
//Program 2 :

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>

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


{

int i,j,c,ele;
int arr[argc];

for(i=0;i<argc-1;i++)
{
int n=atoi(argv[i]);
arr[i]=n;
}

ele=atoi(argv[i]);
i=0;
j=argc-1;
c=(i+j)/2;

while(ele!=arr[c] && i<=j)


{
if(arr[c]<ele)
i=c+1;
else
j=c-1;
c=(i+j)/2;

if(i<=j)
printf("Elemets found");
else
printf("Not found");
}
III. Run an experiment to determine the context switch time
from one process to another and one kernel thread to
another. Compare the findings

A Context switch is a time spent between two processes (i.e.,


bringing a waiting process into execution and sending an
executing process into a waiting for state). This happens in
multitasking. The operating system must bring the state
information if waiting for process into memory and save the state
information of the currently running process.
In order to solve this problem, we would like to record the
timestamps of the first and last instructions of the swapping
processes. The context switch time is the difference between the
two processes.
Let’s take an example: Assume there are only two processes, P1
and P2.
P1 is executing and P2 is waiting for execution. At some point,
the operating system must swap P1 and P2, let’s assume it
happens at the nth instruction of P1. If t(x, k) indicates the
timestamp in microseconds of the kth instruction of process x,
then the context switch would take t(2, 1) – t(1, n).
Another issue is that swapping is governed by the scheduling
algorithm of the operating system and there may be many kernel-
level threads that are also doing context switches. Other
processes could be contending for the CPU or the kernel handling
interrupts. The user does not have any control over these
extraneous context switches. For instance, if at time t(1, n) the
kernel decides to handle an interrupt, then the context switch time
would be overstated.

In order to avoid these obstacles, we must construct an


environment such that after P1 executes, the task scheduler
immediately selects P2 to run. This may be accomplished by
constructing a data channel, such as a pipe between P1 and P2.
That is, let’s allow P1 to be the initial sender and P2 to be the
receiver. Initially, P2 is blocked(sleeping) as it awaits the data
token. When P1 executes, it delivers the data token over the data
channel to P2 and immediately attempts to read the response
token. A context switch results and the task scheduler must select
another process to run. Since P2 is now in a ready-to-run state, it
is a desirable candidate to be selected by the task scheduler for
execution. When P2 runs, the role of P1 and P2 are swapped. P2
is now acting as the sender and P1 as the blocked receiver.

To summaries –

P2 blocks awaiting data from P1


P1 marks the starting time.
P1 sends a token to P2.
P1 attempts to read a response token from P2. This induces a
context switch.
P2 is scheduled and receives the token.
P2 sends a response token to P1.
P2 attempts to read a response token from P1. This induces a
context switch.
P1 is scheduled and receives the token.
P1 marks the end time.

The key is that the delivery of a data token induces a context


switch. Let Td and Tr be the time it takes to deliver and receive a
data token, respectively, and let Tc be the amount of time spent in
a context switch. At step 2, P1 records the timestamp of the
delivery of the token, and at step 9, it records the timestamp of
the response. The amount of time elapsed, T, between these
events, may be expressed by:

T = 2 * (Td + Tc + Tr)

This formula arises because of the following events:

P1 sends the token (3)


CPU context switches (4)
P2 receives it (5)
P2 then sends the response token (6)
CPU context switches (7)
and finally, P1 receives it (8)
IV. Compare the task creation times. Execute a process and
kernel thread, determine the time taken to create and run
the threads.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> //Header file for sleep(). man 3 sleep for
details.
#include <pthread.h>

// A normal C function that is executed as a thread


// when its name is specified in pthread_create()
void *myThreadFun(void *vargp)
{
sleep(1);
printf("Printing Chegg from Thread \n");
return NULL;
}

int main()
{
pthread_t thread_id;
printf("Before Thread\n");
pthread_create(&thread_id, NULL, myThreadFun, NULL);
pthread_join(thread_id, NULL);
printf("After Thread\n");
exit(0);
}

In main() we declare a variable called thread_id, which is of type


pthread_t, which is an integer used to identify the thread in the
system. After declaring thread_id, we call pthread_create()
function to create a thread.
pthread_create() takes 4 arguments.
The first argument is a pointer to thread_id which is set by this
function.
The second argument specifies attributes. If the value is NULL,
then default attributes shall be used.
The third argument is name of function to be executed for the
thread to be created.
The fourth argument is used to pass arguments to the function,
myThreadFun.
The pthread_join() function for threads is the equivalent of
wait() for processes. A call to pthread_join blocks the calling
thread until the thread with identifier equal to the first
argument terminates.

You might also like