Thursday, 15 November 2012

Miscellaneous Programs

In this post, I added some interesting programs written in C to implement some commands .


Our very first program is to implement a Command Interpreter. The idea to implement this is simple. Just take the input command from the user and call exec in a forked child and print the output. The code is as follows:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#define MAX_ARGS 5
void runcmd()
{
 int c=0, take=0, give=0;
 char *i[MAX_ARGS],g;
 char *input_file=(char*)malloc(12*sizeof(char));
 char *output_file=(char*)malloc(12*sizeof(char));
 
 for(int j=0; j<MAX_ARGS; j++) i[j]=(char*)malloc(9*sizeof(char));
 do
 {
        /*read all the input into a char double pointer*/    
  scanf("%s",i[c++]);
  if(!strcmp(i[c-1], ">") || !strcmp(i[c-1], "<"))
  {
   if(!strcmp(i[c-1], ">"))
   {
                /*if the symbol is '>' then read the output
                 * file-name to which the data must  be sent
                 */
    scanf("%s",output_file);
    give=1;
   }
   else
   {
                /*if the symbol is '<', then we are gonna 
                 *have an input file to take data from
                 */
    scanf("%s", input_file);
    take=1;
   }
    
  c--;
  }
  /*read the nxt char of every string if its '\n'
         * then terminate the loop..
         */
  g=getc(stdin);
 }while(g!='\n');

 i[c]=NULL;

 pid_t h;

 if((h=fork()) < 0) printf("fork error\n");
 else if(h==0)
 {
  if(give==1)
  {
   int fd1 = open(output_file, O_RDWR | O_CREAT,  S_IRUSR | S_IWUSR|S_IRGRP);
   dup2(fd1,1);
  }
  else if(take==1)
  {
   int fd = open(input_file, O_RDWR ,  S_IRUSR | S_IWUSR|S_IRGRP);
   if(fd==-1)
   {
    printf("unsupported file or file doesn't exists\n");
    return;
   }
   dup2(fd,STDIN_FILENO);
  }
  execvp(i[0], i);
  /*if the control transfers here, it means 
   *execvp has not executed successfully
   */
  printf("unknown command\n");
 }
 else
 {
  waitpid(h, NULL, NULL);
        /*wait for the child to terminate*/ 
 }
} 
int main(int argc, char* argv[])
{
 while(1)
 {
 printf("-->"); 
 /*keep on calling this function*/
 runcmd();
    }
 exit(0);
}

The above program works for most of the commands and I also provide the stream re-direction facility, to transfer the data into/from files.


The second program is to implement the ps command. For those who don't know what this does, it simply displays the list of the processes running from the current terminal. If you append an argument '-A', it gives the list of all the processes running on the system. Our task is to implement the later one.

The idea is to open the proc directory. This contains numbered directories and some other directories. Each numbered directory denotes a running process and the number on the directory represents its process-ID.

Inside every numbered directory lies a file called "stat". This contains the information that is needed to display the necessary contents. For example, the stat file of init process is as follows:

nitish@nitish:~$ cat /proc/1/stat
1 (init) S 0 1 1 0 -1 4202752 14453 339956 23 20 19 35 257 129 20 0 1
 0 3 25030656 613 18446744073709551615 1 1 0 0 0 0 0 4096 536962595
 18446744073709551615 0 0 17 0 0 0 41 0 0 0 0 0 0 0 0 0 0

The first of these is the init's process ID, second is the name of the process, third is the process status, fourth is the parent process ID(scheduler in this case).

These strings will be displayed for each numbered directory and then we are done. The C code is as follows:

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


int main(int argc, char *argv[])
{
 DIR *d;
 char *t;
 int N=0,pid;
 int cnt=0;
 int curr[10], curr_cnt=0;
 struct dirent *fl;
 d=opendir("/proc");
 int M=getppid();
 printf("Process ID of the terminal : %d\n", M);
 printf("Format: PID  /  PNAME  /  STATE  /  PPID \n");
 while((fl=readdir(d))!=NULL)
 {
  int num=atoi(fl->d_name);
  N=0;
  if(num!=0)
  {
   cnt++;
   t= (char*)malloc(15*sizeof(char));
   strcpy(t,"/proc/");
   strncat(t,fl->d_name,10);
   strncat(t,"/stat",6);  
   FILE *ptr=fopen(t,"r");
   if(ptr==NULL)
   printf("error\n");
   char tt[20];
   char g;
   /**process id**/
   while((g=getc(ptr))!=' ')
   N=N*10 + (g-48);
   pid=N;
   printf("%d  /  ", N);
   g=getc(ptr);
   /**process name**/
   while((g=getc(ptr))!=')' )
   {
    printf("%c", g);
   }
   printf("  /  ");
   g=getc(ptr);
   g=getc(ptr);
   if(g=='S')
    printf("Interruptible sleep  /  ");
   else if(g=='D')
    printf("Uninterruptible sleep  /  ");
   else if(g=='R')
    printf("Runnable  /  ");
   else if(g=='T')
    printf("Stopped  /  ");
   else if(g=='X')
    printf("Dead  /  ");
   else if(g=='Z')
    printf("Zombie  /  ");
   else if(g=='W')
    printf("Paging  /  ");
   g=getc(ptr);
   N=0;
   while((g=getc(ptr))!=' ')
    N = N*10 + (g-48);
   printf("%d" , N);
   if(N==M)
   {
    printf("**\n");
    curr[curr_cnt++]=pid;
   }
   fclose(ptr);
   printf("\n");
  }
 }
 printf("**\nTOTAL PROCESSES:%d\n**\n", cnt);
 printf("**********\n");
 printf("Processes inside the terminal:%d\n",curr_cnt );
 for(int i=0; i<curr_cnt; i++)
  printf("%d ", curr[i]);
 printf("\n");
exit(0);
}

If you want to display only the processes specific to the current terminal, you need to just store all those process IDs which have parent process ID as terminal. Sounds simple isn't it?

The normal ps command displays some other information like tty and command name and these can also be found in each numbered directory which I leave it as an exercise.

The extension of this would be to display all the PIDs, hierarchically starting with the init process and also to find all the file names opened by a given process(of course you must explore the proc directory a bit to this one).


Lastly, I would like to post the code for the thread library, which I have described in my previous posts. Here is the code. (Don't panic by seeing the length, most of them were for indentations and comments).

#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/time.h>
#include<string.h>
#include<errno.h>
#include<ucontext.h>
#include "thqueue.c"
#define MAX_THREADS 10
#define STACK_SIZE 64000
#define TIME_SLOT 500000   //time slot given to each thread in round robin scheduling (0.25sec here..)
typedef struct Task* Tptr;

int CURRENT_THREAD_COUNT=0;
int TERMINATED=0;
int Global_id;


 ucontext_t Main;  //context for saving main...

 ucontext_t Delete[MAX_THREADS];  //context for thread recycling...
 int alloc[MAX_THREADS];
struct itimerval alarm;

struct sigaction sched_signal;


struct Tque    ready_queue;
struct Task     *Current_Thread=NULL;

void schedule()
{
//cs printf("activated\n");
 if(Current_Thread==NULL) // Main Thread was executing previously..
 {
  TERMINATED=0;
  if(!isempty(&ready_queue))       // If there are more threads...
  {
   Current_Thread=top(&ready_queue);
//c   printf("yes!! %p\n", Current_Thread);
   Global_id=Current_Thread->thread_id;

   swapcontext(&Main, &(Current_Thread->threads_context));
  }
  else                // If all threads are dead...
  {
//c   printf("main is getting set!\n");
   setcontext(&Main);
  }
 }
 else     //if someother thread was executing...
 {
  struct Task* tmp=next(&ready_queue, Current_Thread);
//c  printf("other way around and %p and current:%p\n", tmp, Current_Thread);
  if(tmp==NULL)  //if this was the last thread in the queue....
  {     //execute main fn.
//c   printf("it was null\n");
   if(TERMINATED==1)
   {
//c    printf("its gone!!\n");
    TERMINATED=0;
    remov(&ready_queue, Global_id);
    Current_Thread=NULL;
    setcontext(&Main);
   }
   else
   {
    struct Task* tmp1=Current_Thread;
    Current_Thread=NULL;
    swapcontext(&(tmp1->threads_context), &Main); 
   }
   
  }
  else
  {
   struct Task* tmp2=Current_Thread;
   Current_Thread=tmp;
   if(TERMINATED==1)
   {
    TERMINATED=0;
    remov(&ready_queue, Global_id);
    Global_id=tmp->thread_id;
//c    printf("context set for %p\n", tmp);

    setcontext(&(tmp->threads_context));
   }
   else
   {
    Global_id=tmp->thread_id;
//c    printf("running:%p\n", tmp);
    swapcontext(&(tmp2->threads_context), &(tmp->threads_context)); 
   }
   
  }
 }
}

void Kill_Task(void *arg)
{
//c printf("its coming!!\n");
 int t_id=*((int*)arg);
//c printf("terminator for %d started\n", Global_id);
 TERMINATED=1;
 schedule();
}


void Init_Task()
{
 sched_signal.sa_handler=schedule;
 sigemptyset(&sched_signal.sa_mask);
 sched_signal.sa_flags=0;
 sigaction(SIGPROF, &sched_signal, NULL);
 ready_queue.count=0;
 int i=-1;
 while(++i<MAX_THREADS)
  alloc[i]=0;
 //for the first time alarm after 0 sec....
 //for every TIME_SLOT secs...
 alarm.it_value.tv_sec=0;
 alarm.it_value.tv_usec=TIME_SLOT;
 //next time alarm after every TIME_SLOT secs...
 alarm.it_interval.tv_sec=0;
 alarm.it_interval.tv_usec=TIME_SLOT;
//c printf("Initialized\n");
}

void Init_Semaphore(struct Task_Semaphore* mut, int value)
{
 mut->count=value;
 
}

void change_Semaphore(struct Task_Semaphore* mut, int value)
{
 sigset_t old, new;
 sigemptyset(&new);
 sigaddset(&new, SIGPROF);
 sigprocmask(SIG_BLOCK, &new, &old);
 if(mut->count > value)
 {
  mut->count-=value;
  sigprocmask(SIG_SETMASK, &old, NULL);
  return;
 }
 else
 {
  insert(&(mut->wait_queue), rem_and_poi(&ready_queue, Current_Thread));
  sigprocmask(SIG_SETMASK, &old,NULL);
  schedule();
 }
}
void create_task(struct Task* target, void (*fn)(void*), void * param)
{


  if(getcontext(&(target->threads_context))==-1)
  {
   printf("getcontext error!\n");
   exit(1);
  }
  
  (target->threads_context).uc_stack.ss_sp=malloc(STACK_SIZE);
  (target->threads_context).uc_stack.ss_size=STACK_SIZE;
  (target->threads_context).uc_stack.ss_flags=0;

  

  int id=-1;
  while(++id<10 && alloc[id]!=0);

  if(id==10)
  {
   printf("allocation error\n");
   exit(1);
  }
  target->thread_id=id;
  alloc[id]=1;
  //Delete[id]=malloc(sizeof(ucontext_t));
//c  printf("yyyy\n");
  if(getcontext(&Delete[id])==-1)
  {
   printf("delete context error!\n");
   exit(1);
  }
  
  Delete[id].uc_link=0;
  Delete[id].uc_stack.ss_sp=malloc(STACK_SIZE);
  Delete[id].uc_stack.ss_size=STACK_SIZE;
  Delete[id].uc_stack.ss_flags=0;
  (target->threads_context).uc_link=&Delete[id];
  makecontext(&Delete[id], (void*)&Kill_Task, 0);
  makecontext(&(target->threads_context), fn, 0);

  target->next=NULL;
  //if this is the first thread, then set the timer...
  if(CURRENT_THREAD_COUNT++==0)
  {
    if(setitimer(ITIMER_PROF, &alarm, NULL)==-1)
    {
     //some shit is happening with setitimer
     printf("Itimer error:%s", strerror(errno));
     exit(1);
    }
//c    printf("timer set!\n");
  }
  //thread should be added to queue...
  //so block all the SIGPROF signal until this is added....
  sigset_t new,old;
  sigemptyset(&new);
  sigaddset(&new, SIGPROF);
  sigprocmask(SIG_BLOCK, &new, &old);
  if(isempty(&ready_queue)!=0)
  {
//c   printf("first added to queue %p\n", target);
   insert(&ready_queue, target);
  }
  else
  {
//c   printf("next added to queue %p\n", target);
   insert(&ready_queue, target);
  }
  //sleep(5);
  //restore back the old mask...
  sigprocmask(SIG_SETMASK, &old, NULL);
}  


int Sigh=0;
void sample()
{
 printf("success!!\n");
 while(Sigh==0);
 printf("completed!!\n");
 //setcontext(&Main);
}

void samps()
{
 
 printf("success2\n");
 Sigh=1;
}
void sam3()
{
 printf("started3\n");
 //while(1);
 printf("success3\n");
}
int main(int argc, char *argv[])
{
 Init_Task();
 struct Task t,p,q;
 int a=5;
 create_task(&t, (void*)&sample, (void*)&a);
 create_task(&p, (void*)&samps, (void*)&a);
 create_task(&q, (void*)&sam3, (void*)&a);
 //getcontext(&Main);
 printf("yeasss\n");
 while(1);
 //exit(0);
}

The associated "thque.c" is as follows:


struct  Task
{
 int user_priority;
 ucontext_t threads_context;
 struct Task *next;
 int thread_id;
};
struct Tque
{
 struct Task *head;
 struct Task *tail;
 int count;
 
};

struct Task_Semaphore
{
 struct Tque wait_queue;
 int count;
};

void display(struct Tque *u)
{
 struct Task *tmp=u->head;
 while(tmp!=NULL)
 {
  printf("%p ", tmp);
  tmp=tmp->next;
 }
 printf("\n");
}
void insert(struct Tque *q, struct Task *t)
{
 if(q->count==0)
 {
  

  t->next=NULL;
  q->head=q->tail=t;
  
 }
 else
 {
  t->next=NULL;
  (q->tail)->next=t;
  q->tail=(q->tail)->next;
 }
 q->count++;
//c printf("insert:");
// display(q);
}

void remov(struct Tque *q, int id)
{
 struct Task *tmp=q->head, *prev;
 while(tmp!=NULL && tmp->thread_id!=id)
 {
  prev=tmp;
  tmp=tmp->next;
 }
 if(tmp!=NULL)
 {
//c  printf("getting deleted\n");
  if(tmp==q->head)
  {
//c   printf("first one!\n");
   q->head=(q->head)->next;
   //free(tmp);
  }
  else
  {
   prev->next=(prev->next)->next;
   //free(tmp);
  }
  q->count--;
 }
 else
 {
  printf("node with id=%d not found!\n",id);
 }
//c printf("delete:");
// display(q);
}

int isempty(struct Tque *q)
{
//c printf("count:%d\n",q->count );
 return q->count==0;
}

struct Task* top(struct Tque* q)
{
 if(q!=NULL)
 {
//c  printf("correct!\n");
  return q->head;
 }
 else
 {
  return NULL;
 }
}

struct Task* next(struct Tque* q, struct Task* cur)
{
 return cur->next;
}

struct Task* rem_and_poi(struct Tque* q, struct Task* to)
{
 struct Task *tmp=q->head, *tmp1=NULL;
 while(tmp!=to)
 {
  tmp1=tmp;
  tmp=tmp->next;
 }
 if(tmp1==NULL) //node is at head...
 {
  q->head=tmp->next;
  return tmp;
 }
 else   //node is in middle or may be end...
 {
  tmp1->next=tmp->next;
  return tmp;
 }
}

Note that in that thread library, the mutexes and sempahores weren't implemented fully.



Tuesday, 13 November 2012

Scheduling Algorithms

In this post, let us give a quick review of different types of algorithms used in CPU scheduling. I will concentrate more on coding part than theory.

why scheduling?

All Modern computers have the multi-programming capability. This intuitively says that there should be a means of giving the CPU control to different processes. This is done through scheduling. We examine different algorithms and then code them.


types of scheduling:

There are two types of scheduling.

Non-preemptive Scheduling:In this method, once a process is given the control of CPU, it returns back to the operating system only after it gets terminated or switches to waiting state(due to some I/O operation).

Preemptive Scheduling:In this method, the operating system may take control and give it to other process even if the former one was in running state.


Properties of Scheduling Algorithms:

In order to decide which algorithm is the best, we need to have some criteria to compare them. They include the following:

CPU Utilization:

The CPU must be kept as busy as possible. In other words, the scheduling algorithm should not waste the CPU cycles unnecessarily. It should be in the range of 40% for lightly loaded system to 90% for heavily loaded systems.

Throughput:

The number of processes that have completed their execution per unit time is called throughput.

Turnaround time:

It is the measure of how much time a process took to finish its execution starting from its submission into the ready queue.

Waiting Time:

It is the measure of how much time a process has spend waiting for the CPU through out its execution.

Response time:

It indicates the time elapsed from its submission into the ready queue, until it has got the CPU for the first time. This parameter is useful for interactive systems.


Algorithms:

First-come First-served:

In this algorithm, the CPU is given to that process which has arrived first, irrespective of its Burst Time (Burst time is the measure of how long a process requires the CPU between I/O waits). Remember that, this algorithm is Non-preemptive.

Suppose, there is a process with huge burst time has arrived first, followed by processes with low burst time. Intuitively, we can see that the waiting time of the later processes is high. There will also be a convoy effect which states that either cpu or I/O device will be busy while the other will be ideal.

Clearly, this is not what we desire. The next algorithm we see will reduce the waiting time by large amount. The C code to implement this algorithm is as follows:

#include <stdio.h>
int main()
{
 int n, *p,*w;
 int sum=0;
 system("clear");
 printf("Enter the number of processes:");
 scanf("%d", &n);
 p=(int*)malloc(n*sizeof(int));
 w=(int*)malloc(n*sizeof(int));
 for(int i=0; i<n; i++)
 {
  printf("Burst time for process %d:", i+1);
  scanf("%d", &p[i]);
  w[i]=sum;
  sum+=p[i];
 }
 system("clear");
 sum=0;
 printf("\t\tProcess\t\tWaiting Time\n");
 for(int i=0; i<n; i++){
 printf("\t\t%d\t\t%d\n", i+1, w[i]);
 sum+=w[i];
 }
 printf("Average waiting time:%d sec\n", sum/n);
 return 0;
}
/*
Output:
Enter the number of processes:4
Burst time for process 1:12
Burst time for process 2:5
Burst time for process 3:8
Burst time for process 4:1

  Process  Waiting Time
  1      0
  2      12
  3      17
  4      25
Average waiting time:13 sec
*/

Shortest Job First Scheduling:

In this algorithm, the process with the minimum CPU burst is chosen to execute first. If this is the scenario, we can expect that the waiting time on average is greatly reduced. Note that this is also a Non-preemptive algorithm.

The C code for this algorithm is as follows:

#include <stdio.h>
int main()
{
 int n, *p,*w,*re;
 int sum=0;
 system("clear");
 printf("Enter the number of processes:");
 scanf("%d", &n);
 p=(int*)malloc(n*sizeof(int));
 w=(int*)malloc(n*sizeof(int));
 re=(int*)malloc(n*sizeof(int));
 for(int i=0; i<n; i++)
 {
  printf("Burst time for process %d:", i+1);
  scanf("%d", &p[i]);
  re[i]=i;
 }
 system("clear");
 for(int i=0; i<n; i++)
 {
  for(int j=0; j<n-1; j++)
  {
   if(p[j] > p[j+1])
   { 
    int tmp=p[j];
    p[j]=p[j+1];
    p[j+1]=tmp;
    tmp=re[j];
    re[j]=re[j+1];
    re[j+1]=tmp;
   }
  }
 }
 sum=0;
 for(int i=0; i<n; i++)
 {
  w[i]=sum;
  sum+=p[i];
 }
 printf("\t\tProcess\t\tWaiting Time\n");
 sum=0;
 int cnt=0;
 while(cnt!=n)
 {
  for(int i=0; i<n; i++)
  {
   if(cnt==re[i])
   {
    printf("\t\t%d\t\t%d\n", cnt+1, w[i]);
    sum+=w[i];
    cnt++;
   }
  }
 }
 
 printf("Average waiting time:%d sec\n", sum/n);
 return 0;
}
/*Output:
Enter the number of processes:4
Burst time for process 1:12
Burst time for process 2:5
Burst time for process 3:8
Burst time for process 4:1

  Process  Waiting Time
  1      14
  2      1
  3      6
  4      0
Average waiting time:5 sec
*/

You can see the significant decrease in the average waiting time for the above algorithm. There is a preemptive version of this and it is called shortest-remaining-time-first scheduling.

Shortest-remaining-time-first scheduling:

The small difference from the previous one is that CPU preempts the current process if it finds another process with smaller burst time than this one. This reduces the waiting time to some extent. The C code for this is as follows:

#include <stdio.h>
struct proc
{
 int b_time; //burst time
 int a_time; //arrival time
};
struct proc *p;
int n;
int give_min_idx(int upto)
{
 int min=1000, idx=-1;
 for(int i=0; i<n; i++)
 {
  if(p[i].b_time!=-1 && p[i].a_time<=upto && min>p[i].b_time)
  {
   min=p[i].b_time;
   idx=i;
  }
 }
 return idx;
}
int main()
{
 int *w;
 int time=0;
 int sum=0;
 system("clear");
 printf("Enter the number of processes:");
 scanf("%d", &n);
 p=(struct proc*)malloc(n*sizeof(struct proc));
 w=(int*)calloc(n,sizeof(int));
 for(int i=0; i<n; i++)
 {
  printf("Burst & arrival time for process %d:", i+1);
  scanf("%d%d", &p[i].b_time, &p[i].a_time);
 }
 system("clear");
 int idx;
 while((idx=give_min_idx(time))!=-1)
 {
  p[idx].b_time--;
  if(p[idx].b_time==0)
  p[idx].b_time--;
  time++;
  for(int i=0; i<n; i++)
  {
   if(p[i].b_time!=-1 && i!=idx && p[i].a_time<time)
   w[i]++;
  }
 }
 sum=0;
 printf("\t\tProcess\t\tWaiting Time\n");
 for(int i=0; i<n; i++){
 printf("\t\t%d\t\t%d\n", i+1, w[i]);
 sum+=w[i];
 }
 printf("Average waiting time:%d sec\n", sum/n);
 return 0;
}
/*
output:
Enter the number of processes:4
Burst & arrival time for process 1:8 0
Burst & arrival time for process 2:4 1
Burst & arrival time for process 3:9 2
Burst & arrival time for process 4:5 3

  Process  Waiting Time
  1      9
  2      0
  3      15
  4      2
Average waiting time:6 sec
*/

Priority scheduling:

In this algorithm, each process is given some priority and that process is completely executed and then next one is chosen and so on. The C code is as follows:

#include <stdio.h>
struct proc
{
       int b_time;
       int prio;
       };
int give_min(struct proc f[], int n)
{
    int min=1000,idx=-1;
    for(int i=0; i<n; i++)
    {
            if(f[i].prio!=-1 && f[i].prio < min)
            {
                               min=f[i].prio;
                               idx=i;
                               }
                               }
       return idx;
}                                  
int main()
{
    struct proc *g;
 int n, *p,*w;
 int sum=0;
 system("clear");
 printf("Enter the number of processes:");
 scanf("%d", &n);
 g=(struct proc*)malloc(n*sizeof(struct proc));
 w=(int*)malloc(n*sizeof(int));
 for(int i=0; i<n; i++)
 {
  printf("Burst time & priority for process %d:", i+1);
  scanf("%d%d", &g[i].b_time, &g[i].prio);
 }
 system("clear");
 sum=0;
 int idx;
 while((idx=give_min(g,n))!=-1)
 {
          w[idx]=sum;
          sum+=g[idx].b_time;
          g[idx].prio=-1;
          }
          sum=0;     
 printf("\t\tProcess\t\tWaiting Time\n");
 for(int i=0; i<n; i++){
 printf("\t\t%d\t\t%d\n", i+1, w[i]);
 sum+=w[i];
 }
 printf("Average waiting time:%d sec\n", sum/n);
 return 0;
}
/*output:
Enter the number of processes:5
Burst time & priority for process 1:10 3
Burst time & priority for process 2:1 1
Burst time & priority for process 3:2 4
Burst time & priority for process 4:1 5
Burst time & priority for process 5:5 2

                Process         Waiting Time
                1               6
                2               0
                3               16
                4               18
                5               1
Average waiting time:8 sec
*/

Round-Robin scheduling:

In this type of scheduling, each process is given the CPU for some small interval known as quantum and this is repeated in round robin fashion until all the processes are completely executed. The C code is as follows:

#include <stdio.h>                                
int main()
{
 int n, *p,*w,qu;
 int sum=0;
 system("clear");
 printf("Enter the number of processes:");
 scanf("%d", &n);
 p=(int*)malloc(n*sizeof(int));
 w=(int*)calloc(n,sizeof(int));
 for(int i=0; i<n; i++)
 {
  printf("Burst time for process %d:", i+1);
  scanf("%d", &p[i]);
 }
 printf("Enter the time Quantum:");
 scanf("%d", &qu);
 system("clear");
 sum=0;
 int ptr=0,cnt=0,tmp;
          sum=0;
    while(cnt!=n)
    {
          if(p[ptr]==0)
          {
                  cnt++;     
                  ptr=(ptr+1)%n;
                  continue;
                  }
          if(p[ptr]>=qu){
          tmp=qu;               
          p[ptr]-=qu;
          }
          else
          {
              tmp=p[ptr];
              p[ptr]=0;
              }
            for(int i=0; i<n; i++)
            {
                    if(i!=ptr && p[i]!=0)
                    w[i]+=tmp;
                    }
                ptr=(ptr+1)%n;
                }                                                  
 printf("\t\tProcess\t\tWaiting Time\n");
 for(int i=0; i<n; i++){
 printf("\t\t%d\t\t%d\n", i+1, w[i]);
 sum+=w[i];
 }
 printf("Average waiting time:%d sec\n", sum/n);
 return 0;
}
/*Output:
Enter the number of processes:3
Burst time for process 1:24
Burst time for process 2:3
Burst time for process 3:3
Enter the time Quantum:4

                Process         Waiting Time
                1               6
                2               4
                3               7
Average waiting time:5 sec
*/

There are two more algorithms Multilevel Queue Scheduling and its variation Feedback Queue Scheduling. These can be implemented by the combination of the above described algorithms and I leave it as an exercise to the reader.


Saturday, 10 November 2012

System V IPC: Message Queues

In this post, I will be telling you about another method of Inter Process Communication using message queues.

why message queues?

These are most widely used method of IPC, because a message queue is more flexible than that of shared memory. By using message queues, we can define our own protocols of passing messages between two processes. I don't say this isn't possible with shared memory, but its a bit tedious to do. Let us start learning them now.


The data structure:

Each message queue has a special structure which is used to store the various information regarding itself. It is defined as follows:


struct msqid_ds {
    struct ipc_perm msg_perm;
    struct msg *msg_first;  /* first message on queue */
    struct msg *msg_last;   /* last message in queue */
    time_t msg_stime;       /* last msgsnd time */
    time_t msg_rtime;       /* last msgrcv time */
    time_t msg_ctime;       /* last change time */
    struct wait_queue *wwait;
    struct wait_queue *rwait;
    ushort msg_cbytes;    
    ushort msg_qnum;     
    ushort msg_qbytes;      /* max number of bytes on queue */
    ushort msg_lspid;       /* pid of last msgsnd */
    ushort msg_lrpid;       /* last receive pid */
};

Just have a glance at the various fields used in the structure.


System calls:

There are four system calls for message queues namely:

msgget()
msgrcv()
msgsnd()
msgctl()

Starting with the first one...

msgget():

This is similar to the other gets and has the following prototype:

int msgget ( key_t key, int msgflg );

The first argument is a key value described here The flags associated with message queues are exactly same as shared memory(from my previous post).

Upon success, it returns the message queue ID and on failure it returns -1, setting the appropriate value of errno.


msgsnd():

This system call is used to put the messages on to the queue and it has the following prototype:

int msgsnd ( int msqid, void *msgp, int msgsz, int msgflg );

The first argument is the message queue ID returned from msgget. The second argument is a pointer to user-defined structure which should have atleast two fields: a long integer which specifies the type of message, a character string which stores the message. Of course, you can add as many other fields as you wish.

The third argument is the size of the user-defined structure. It shouldn't count the size of the message type variable.

Sometimes, the message queue may be full with the messages, and if a process tries to put another message onto the queue, it will either block or continue without inserting the message based on the flags. If IPC_NOWAIT is not specified, then the process gets blocked until it gets room for its message to put in or else it simply continues its execution.

It returns 0 on success and -1 on failure.


msgrcv():

This system call is used to retrieve the messages from the queue. It has the following prototype:

int msgrcv ( int msqid, void *msgp, int msgsz, long mtype, int msgflg );

The first argument is the message queue ID. The second one is a pointer to the user defined message structure. Third one is the maximum size of the message to be accepted. Fourth one is the type of the message the process wants to retrieve.

If the available message is bigger than the maximum size specified, then we have a choice of either not removing the message out of queue or remove the message and truncate it to fit the size specified. This is done by MSG_NOERROR bit. If it is specified, it is truncated or else the call fails returning -1 and keeping the queue unchanged.

Another flag used is IPC_NOWAIT in which if this is not specified, then the calling process gets blocked until there is a message available of the specified type. If that falg is specified, then it doesn't get blocked.


msgctl():

The function of msgctl is exactly same as that of shmctl except that there is a change in name of the struct argument. Its prototype is as follows:

int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );

You can see the details here.


Example:

Let us see an example program with message queues. I prefer to show the same example explained in the previous post, but with message queues.

In these programs, there is no need of any semaphores. Guess why? Because we are going to use the fact that a process gets blocked until it finds a message of the type it needs. So, here is the code of first program to run(not mandatory).


#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
struct mymsg
{
 long type;
 char msg[30];
 char from[10];
};
struct mymsg Msg;
int main()
{
 int msg_id=msgget(1456, IPC_CREAT | 660);
 if(msg_id==-1)
 {
  perror("msgget error!\n");
  exit(1);
 }
 int h=fork();
 if(h==0)
 {
  /*this forked process takes the i/p from console
   *and puts into the message queue
   */
  char tmp[30];
  int n;
  while(1)
  {
   n=read(0, tmp, 30);
   tmp[n]='\0';
   strcpy(Msg.msg, tmp);
   Msg.type=1; //this process sends type 1 msgs
   strcpy(Msg.from,"p1");
  if(msgsnd(msg_id, &Msg, sizeof(struct mymsg)-sizeof(long),0)==-1)
   {
    perror("send error!\n");
    exit(1);
   }
  }
 }
 else if(h>0)
 {
  /*this one reads the msgs from the queue and 
   *displays to the console...
   */
  struct mymsg Buf;
  while(1)
  {
   //it tries to fetch type 2 messages
  if(msgrcv(msg_id, &Buf, sizeof(struct mymsg)-sizeof(long),2,0)==-1)
   {
    perror("rec. error!\n");
    exit(0);
   }
   printf("%s:%s", Buf.from, Buf.msg);
  }
 }
 else
 {
  printf("fork error!\n");
  exit(1);
 }
 exit(0);
}


The second process code is same as the first one, except that they differ in the message types that they send and receive. It is as follows:


#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
struct mymsg
{
 long mtype;
 char msg[30];
 char from[10];
};
struct mymsg Msg;
int main()
{
 int msg_id=msgget(1456, IPC_CREAT | 660);
 if(msg_id==-1)
 {
  perror("msgget error!\n");
  exit(1);
 }
 int h=fork();
 if(h==0)
 {
  /*this forked process takes the i/p from console
   *and puts into the message queue
   */
  char tmp[30];
  int n;
  while(1)
  {
   n=read(0, tmp, 30);
   tmp[n]='\0';
   strcpy(Msg.msg, tmp);
   Msg.mtype=2; //this process sends type 2 msgs
   strcpy(Msg.from,"p2");
   if(msgsnd(msg_id, &Msg, sizeof(struct mymsg)-sizeof(long),0)==-1)
   {
    perror("send error!\n");
    exit(1);
   }
  }
 }
 else if(h>0)
 {
  /*this one reads the msgs from the queue and 
   *displays to the console...
   */
  struct mymsg Buf;
  while(1)
  {
   //it tries to fetch type 1 messages
  if(msgrcv(msg_id, &Buf, sizeof(struct mymsg)-sizeof(long),1,0)==-1)
   {
    perror("rec. error!\n");
    exit(0);
   }
   printf("%s:%s", Buf.from, Buf.msg);
  }
 }
 else
 {
  printf("fork error!\n");
  exit(1);
 }
 exit(0);
}  


When you run both the programs, you can see that a string typed in one window appears in the other one.

Some systems may need super user privileges to create a message queue. In that case run the program as super-user as follows:

sudo ./a.out


System V IPC: Shared Memory

In this post, I will give you a clear idea of using shared memory between two processes.

Why shared memory?

Let us know why to use shared memory. Processes actually need to communicate with each other and one method of accomplishing this is using Semaphores as discussed previously. But this doesn't provide a way to send and receive data between the processes. Here comes the shared memory into the picture. Let us delve into it.


The data structure:

Each shared memory segment has a structure which stores all the necessary information and is defined as follows:

struct shmid_ds {
   struct ipc_perm shm_perm;        /* operation perms */
   int     shm_segsz;               /* size of segment (bytes) */
   time_t  shm_atime;               /* last attach time */
   time_t  shm_dtime;               /* last detach time */
   time_t  shm_ctime;               /* last change time */
   unsigned short  shm_cpid;        /* pid of creator */
   unsigned short  shm_lpid;        /* pid of last operator */
   short   shm_nattch;              /* no. of current attaches */
                                    /* the following are private */
   unsigned short   shm_npages;     /* size of segment (pages) */
   unsigned long   *shm_pages;     /* array of ptrs to frames -> SHMMAX */ 
   struct vm_area_struct *attaches; /* descriptors for attaches */
        };

I think the comments are self-explainable about the structure except the first and last ones which we can ignore.


System calls:

There are only 4 System calls associated with shared memory.

shmget()
shmat()
shmdt()
shmctl()

Let us see one-by-one.

shmget():

The prototype of this is as follows:

int shmget ( key_t key, int size, int shmflg );

The first argument is a unique key for which the kernel generates the identifier accordingly. If we have already created a shared memory segment and again try to create it with the same key value, then it returns the same identifier provided IPC_EXCL is not set ( discussed shortly).

For more information about the key generation you can see here

The second argument is the size(in bytes) of the memory you want to create.

The third argument refers to the flags. These can be bit-wise ORed accordingly.

IPC_CREAT:

This flag says to create the shared memory segment if it doesn't exist already.

IPC_EXCL:

If this weren't specified, the function would normally return the memory ID, even if it already exists. But if this is specified, it returns -1 and this is used to ensure that we are creating a brand new shared memory segment.

If everything goes well, it returns an ID of a shared memory segment or else you will end up with a -1 if you commit any mistake.


shmat():

After successfully getting the memory ID, you have to attach it to the process, meaning you have to map into the process's address space. It is done with shmat. The prototype is as follows:

int shmat ( int shmid, char *shmaddr, int shmflg);

The first argument is the shm ID that you got from the previous method. The second argument is the address where you want to map the memory segment. If it is specified as NULL, then the kernel automatically maps into some address. It is recommended to pass it as NULL, because no one wants to mess up a process's address space without knowing the exact address contents.

The third argument can be SHM_RDONLY which makes the segment read only for the calling process.(Of course, it should have appropriate permissions to do that) or else it can be NULL.

Upon success, it returns the address of the segment or if it fails it returns -1. The returned address can be used just like any other array in order to read or modify the contents.


shmdt():

When you are done using the memory segment, you need to detach it from the address space and it can be done using shmdt() whose prototype is as follows:

int shmdt ( char *shmaddr );

You just need to specify the address that was previously returned by shmat(). It detaches the memory and makes necessary changes to the corresponding shmid_ds structure.


shmctl():

We have already learnt all the necessary system calls required for shms. Then why this one? This is used to retrieve the information stored in the shmid_ds structure and to change the permissions and lastly to delete the memory segment. It has the following prototype:

int shmctl ( int shmqid, int cmd, struct shmid_ds *buf ); 

The first one is the shm ID, while the second one can be one of these:

IPC_STAT:

This can be used to get all the information about the memory segment and this is filled in the structure pointed to by the bug.

IPC_SET:

This can be used to change the permissions of the memory segment. You need to store the necessary permissions in the shm_perm of the buf structure and it sets the necessary permissions. You need to have the necessary permissions to do this.

IPC_RMID:

This marks the memory segment as deleted and this is actually done when all other processes detach it from their address space. Be cautious that if you won't delete this, the memory segment remains until the next reboot of your system. This can be checked by using ipcs command.


errno:

I used to think if there was an exception class in C like JAVA does. Through which we can know the exact error that has occured during run-time. But voila its there in C! I mean not an explicit exeception class, but a special variable called errno which is a global one and most of the system calls set this value to appropriate number which indicates an error code. It is in the library erron.h.

There are about 133 such different error codes and this can be viewed by the following command:

errno -l

So, whenever a system call returns -1, you can directly check the errno value and get the error message corresponding to the code. Sounds cool isn't it?


example:

Lastly, let us see an example program in which two processes communicate with each other by passing strings of characters. Informally, this is like a chat between the two processes.

I have also used semaphores in this program, to signal when a process is ready with message and other issues. This can be extended to more than two processes just by increasing the number of semaphores and making some other changes.

The code of the server( not really a server, but is apt for current context) process is as follows and this one should run first:


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

union semun {
 int val;
 struct semid_ds *buf;
 unsigned short int *array;
 };
int main()
{
 char *shared;
 int sem_id=semget(3456, 2, IPC_CREAT | 666);
 if(sem_id==-1)
 {
  perror("semget error!\n");
  exit(1);
 }
 int shm_id=shmget(4567, 30, IPC_CREAT | 666);
 if(shm_id==-1)
 {
  perror("shmget error!\n");
  exit(1);
 }
 union semun T;
 T.val=0;
 for(int i=0; i<2; i++)
 {
  if(semctl(sem_id, i,SETVAL, T)==-1)
  {
   printf("semctl error!\n");
   exit(1);
  }
 }
 if((shared=shmat(shm_id, NULL,0))==(char *)-1)
 {
  perror("shmat error!\n");
  exit(1);
 }
 int ID=fork();
 if(ID==0)
 {
  char buf[30];
   int n;
   char *tmp;
   struct sembuf tk;
  //this reads the input from console...
  while(1)
  {
    n=read(0, buf, 30);
   if(n==0)
   {
    printf("writing terminated..\n");
    break;
   }
   tmp=shared;
   strncpy(shared, buf, strlen(buf));
   tk.sem_num=0;//0-->used by this program
   tk.sem_flg=0;
   tk.sem_op=1;
   if(semop(sem_id, &tk, 1)==-1)
   {
    printf("semop error for writing!\n");
    exit(0);
   }
  }
 }
 else if(ID>0)
 {
  //takes from shared memory and writes to the console... 
  char shr[30];
  struct sembuf gh;
  while(1)
  {
   gh.sem_num=1;
   gh.sem_flg=0;
   gh.sem_op=-1;
   if(semop(sem_id, &gh, 1)==-1)
   {
    printf("semop error for reading..!\n");
    exit(1);
   }
   strncpy(shr, shared, strlen(shared));
   write(1, shr, strlen(shr));
  }
 }
 else
 {
  printf("fork error!\n");
 }
 exit(0);
}

The client program is as follows:

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

union semun {
 int val;
 struct semid_ds *buf;
 unsigned short int *array;
 };
int main()
{
 char *shared;
 int sem_id=semget(3456, 2, IPC_CREAT | 666);
 if(sem_id==-1)
 {
  printf("semget error!\n");
  exit(1);
 }
 int shm_id=shmget(4567, 30, IPC_CREAT | 666);
 if(shm_id==-1)
 {
  printf("shmget error!\n");
  exit(1);
 }
 if((shared=shmat(shm_id, NULL,0))==(char *)-1)
 {
  printf("shmat error!\n");
  exit(1);
 }
 int ID=fork();
 if(ID==0)
 {
  char buf[30];
   int n;
   char *tmp;
   struct sembuf tk;
  //this reads the input from console...
  while(1)
  {
    n=read(0, buf, 30);
    if(n==0)
   {
    printf("writing terminated..\n");
    break;
   }
   tmp=shared;
   strncpy(shared, buf,strlen(buf));
   tk.sem_num=1;//1-->used by this program
   tk.sem_flg=0;
   tk.sem_op=1;
   if(semop(sem_id, &tk, 1)==-1)
   {
    printf("semop error for writing!\n");
    exit(0);
   }
  }
 }
 else if(ID>0)
 {
  //takes from shared memory and writes to the console... 
  char shr[30];
  struct sembuf gh;
  while(1)
  {
   gh.sem_num=0;
   gh.sem_flg=0;
   gh.sem_op=-1;
   if(semop(sem_id, &gh, 1)==-1)
   {
    printf("semop error for reading..!\n");
    exit(1);
   }
   strncpy(shr, shared,strlen(shared));
   write(1, shr, strlen(shr));
  }
 }
 else
 {
  printf("fork error!\n");
 }
 exit(0);
} 

When you run both the programs, you can see that when you enter a string in one process, it appears in the other process's console and vice versa. The understanding of the above code is left as an exercise to the reader. There may be few bugs in this code, but i assure that it does what it is intended to do.