Midterm Exam

CS354 Spring 2003

Name:


Part 1. True False Questions

Answer True/False (T/F) (1 point each)

__F__ The call to putenv() in your shell can be called by a child process.
__T__ spinlocks have less overhead than mutex-locks as long as the critical section is small.
__F__ The check for permissions in the open() system call could be done in user space.
__T__ If foo() calls xoo() and foo() has a local variable v1 and xoo() has a local variable v2, then &v1 > &v2
__T__ A call to fork() will show in the truss output.
__F__ execvp() will return a value of 0 if successful.
__T__ The counter of a semaphore could be negative at some point in the execution of a program..
__T__ Calling fork() modifies the reference counter of the open file objects.
__F__ A parent and a child process may communicate using a pipe created by the child process.
__F__ The size in bytes of a variable of type (char *) is 4 bytes and the size of a variable of type (char **) is 8 bytes.
__T__ User-level threads use non-preemptive scheduling.
__F__ ELF means Executable Level Format.
_T/F___ SMP stands for Simultaneous Multi Processing (It should be "Symetric" instead of Simultaneous. Modify your class notes).
__F__ A call to strcat(a,b) will allocate space with a number of bytes equal to the lenght of string "a" plus the lenght of string "b" plus 1.
__T__ A process that has "n" POSIX threads, where one of them calls fork() will have only one thread in the child process.
__F__ The number of processes in ready state in mentor cannot exceed 8.
__T__ a[i] is equivalent to *(&a[0] + i)
__T__ The input/output redirection to files can be done by the child.
__F__ It is possible to disable interrupts in user space.
__T__ BSS stores uninitialized global variables
 

Part 2. Short questions.

1. (3 pts) Mention the advantages and disadvantages of clusters compared to multiprocessor machines.
Advantage of cluster:
  1. Inexpensive. Cost O(n) vs O(n^2) for mulitprocessor machines.
  2. More reliability through redundancy. If one fails another can be used.
  3. Easy to fix and replace a broken node.

Disadvantage:
  1. Slower communication vs. using shared memory in multiprocessor machines.
  2. Multithreaded programs will not take advantage of a cluster.
  3. Need to rewrite program to use cluster.

2. (3 pts.) Mention in what circumstances the user time of a process may exceed the real time.
  A multithreaded program running in a multiprocessor machine where different threads run concurrently in multiple processors may make the user time exceed the real time.
 
 

3. (3 pts.) Write the factors used to determine the length of a quantum.

  1. Average response time.
  2. Context switch overhead.
  3. Average burst time.
 

4. (3 pts.) Give the reasons why system calls are implemented using software interrutps.
  Because system calls need to cross the user-kernel boundary to be able to access the kernel data structures, device registers, and operations that are only available in kernel mode.
 
 

5. (3 pts.) What are the advantages and disadvantages of preemptive vs. non-preemptive scheduling.
Advantage of preemptive scheduling:
  1. More robust. One non-cooperative thread will not hang the system.
  2. More fair.
  3. Can use multiple processors.

Disadvantage:
  1. Difficult to implement.
  2. Problems doing synchronization of resources.

Part 3. Programming questions.

 
 
6. (10 pts.) Implement the code for the following function 

       char *strstr(const char *haystack, const char *needle) 

The strstr() function finds the first occurrence of the substring needle in the string haystack.  The terminating `\0' characters  are  not  com­pared. The  strstr()  function  returns  a pointer to the beginning of the sub­string, or NULL if the substring is not found. 

char *strstr(const char *haystack, const char *needle)
{

 char * start = haystack; 

 while( * start) {    
   //compare needle at this position. 
   char * p = needle;
   char * q = start;
   while (*p && *q && *p == * q) { 
     p++;
     q++;
   }
   if( *p == 0) {
      return start; 
   } 
   start++;
 }  
 return NULL;
}
      
 
 
7. (10 pts.) Write the implementation of the procedure getenv() that returns the value of an environment variable or NULL if that variable does not exist. 
extern char ** environ;
     
char * getenv( char * var_val ) {

   char ** p = environ;
   while(*p != NULL) { 
      char * q = strstr(*p, var_val); 
      if(q == p && (*p)[strlen(var_val)] == '=') {
         break;
      }
       p++;
   }
   if(*p != NULL) { 
      return &(*p)[strlen(var_val) + 1];
   }
   else 
     return NULL;
}
      
 
8. (10 pts.) The add() method adds a number to a linked list of integers. This add method does not have mutexes. Thread T1 calls add(5) and thread T2 calls add(7) at the same time. Give a sequence of instructions (use the labels a, b, c, d) that can cause the node with the "7" value  to be lost. 
struct Node {
  int _val;
  Node * _next;
};     
       
class IntList {
  Node * _head;

  IntList() {
    _head = NULL;
  }

  void add( int val ) {

    a) Node * n = new Node()

    b) n->_val = val;
  
    c) n->next = _head;

    d) _head = n;

  }
}

    
t --------------------------------> 

T1 ->       a b c         d 

T2 ->             a b c d      


 
 
 
9. (5 pts) Write a synchronized bounded buffer  class with a circular buffer where the writer thread that calls add(int val) will block if the buffer is full. The reader thread calling remove() will block if the bounded buffer is empty. The constructor BoundedBuffer(int max) allocates space for _buffer with max size elements and it also initializes semaphores. Add also the semaphores and mutexes that are necessary.
class BoundedBuffer {
  int * _buffer;
  int _max;
  int _head;
  int _tail;

  // More variables or semaphores if necessary
  sema_t _full; 
  sema_t _empty;
  mutex_t _mutex;
  BoundedBuffer( int max ) {

       _buffer = (int *) malloc (max * sizeof(int));

       assert(_buffer != NULL);

       _max = max;

       _head = 0;

       _full = 0; 

       seam_init(&_full, max);

       seam_init(&_empty, 0);

       mutex_init(&_mutex); 
  }

  void add( int val ) {
     // Block if buffer is full

     sema_wait(&_full);

     mutex_lock(&_mutex);

     _buffer[tail] = val;

     _tail = (_tail + 1 ) % _max;

     mutex_unlock(&_mutex);

     sema_post(&_empty);
  }

  int remove() {
      // Block if buffer is empty
     sema_wait(&_empty);

     mutex_lock(&_mutex);

     int tmp = _buffer[_head];

     _head = (_head + 1) % _max;

     mutex_unlock(&_mutex);

     sema_post(&_full);

     return temp;


  }


 
 
10. (10 pts.) Write a function redirectOutput(args, outfile) that will execute the program in args[0] in a separate process and it will redirect the output to the file named outfile. args is an array of arguments where args[0] is the program to run and the last aregument is a NULL pointer. You will have to implement the procedure in two ways: 1. Do the reedirection in the parent. and 2. Do the redirection in the child.The procedure will return 0 if success or different than 0 otherwise.
int redirectOutput(char ** args, char * outfile)
{
  // Do the redirection in the parent

    int tmpout = dup(1);

    int fd = open(outfile, O_WRITE|O_CREATE|O_TRUNC, 0666);

    if(fd < 0) {
       return -1;
    }

    dup2(fd, 1);

    int res = fork();

    if(res == 0) {

      execvp(args[0], args);

      exit(1);

    }

    if(res < 0) {

       close(fd);

       return -1;

     }

    //restore output

    dup2(tmpout, 1);

    close(tmpout);

    waitpid(res);

    return 0;

}



int redirectOutput(char ** args, char * outfile)
{
  // Do the redirection in the child



     int res = fork();

     if(res == 0) {

         //child

       int fd =  open(outfile, O_WRITE|O_CREATE|O_TRUNC, 0666);

       if(fd < 0) {

         exit(1);

        }

      dup2(fd, 1);

      execvp(args[0], args);

      exit(1);

      }

     else if (res < 0)

       return -1;

     }

    waitpid(res);
}

 
 
11. (10 pts.) Implement the procedure int runpipe( char ** args1, char ** args2) that runs the programs in args1 and args2 in different children processes where the output of args1 goes to the input of args2 using a pipe. args1 and args2 are arrays of arguments where the last argument is NULL. The element args1[0] and args2[0] are the names of the programs to execute. runpipe() will return 0 if success or different than 0 otherwise.
int runpipe( char ** args1, char ** args2)
{

     int fdpipe[2];

     pipe(fdpipe);

     //process1

     int ret1 = fork();

     if(ret1 < 0)

        return -1;

     if(ret1 == 0) {

        //direct output

      dup2(fdpipe[1], 1);

      execvp(args1[0], args1);

      exit(1);

     }

     //process 2

     int ret2 = fork();

     if(ret2 < 0)

      return -1;

     if(ret2 == 0) {

       dup2(fdpipe[0], 0);

       execvp(args2[0], args2);

       exit(1);

     }

     close(fdpipe[0]);

     close(fdpipe[1]);

     waitpid(ret1);

     waitpid(ret2);
}

 
 
12. (10 pts.) Implement a name table class using an array of TableEntry struct's. The constructor NameTable(max) will allocate space for a table with n entries. The function add(var,val) will block until there is space in the table, then it will search for an entry in the table that is unused ( _var and _val are NULL) and it will add the var and val strings in that entry. Duplicated values are allowed.
The function lookup(var) will lookup the string var in the table and it will return the corresponding value. If the variable does not exist, lookup() will wait until that variable is added with the add() function. Add the semaphores and mutexes that are necessary and the code that implements the behavior described.

struct TableEntry {
  char * _var;
  char * _val;
  // Other variables here

   sema_t _pendingSem;
   int _pending;
   int _used;
};

class NameTable {
  // Max length of table
  int _max; 

  // Array of TableEntry entries 
  TableEntry * _table;

  // Other variables here
  sema_t _full; 
  mutex_t _mutex; 
 

public:
  NameTable(int max) {
     _table = new TableEntry[max]; 
     _max = max; 
     sema_init(&_full, max); 
     mutex_init(&_mutex); 
     for(int i = 0; i < _max; i++) {
       TableEntry * t = &_table[i];
       t->_var = NULL; 
       t->._val = NULL;
       sema_init(t->_PendingSem, 0 );
       t->_pending = 0;
       t->_used = 0;
     }
  }

  void add(char *var, char *val) {
    // Wait for an entry
    sema_wait( &_full );

    mutex_lock( &_mutex )
 
    // See if there is a "lookup" pending for this variable
    for ( int i = 0; i < _max; i++ ) {
      TableEntry * t = &_table[i];
      if ( t->_used && t->_pending && !strcmp( var, t->_var)) {
        t->_var = var;
        sema_post(&t->_pendingSem);
        mutex_unlock( _mutex );
        return;
      }
    }

    // Search for an unused entry in the table
    for ( int i = 0; i < _max; i++ ) {
      TableEntry * t = &_table[i];
      if ( t->_used == 0 ) {
        t->_var = var;
        t->_val = value;
        t->_pending = 0;
        t->_used = 1;
        mutex_unlock( _mutex );
        return;
      }
    }

    // We should never reach this point
    assert( 0 );
  }

  char * lookup(char * var) { 
    // See if there is a value already for this variable
    for ( int i = 0; i < _max; i++ ) {
      TableEntry * t = &_table[i];
      if ( t->_used && !t->_pending && !strcmp( var, t->_var)) {
        char * tmp = t->_val;
        t->_used = 0;
        t->_var = NULL;
        t->_val = NULL;
        sema_post( _full );
        mutex_unlock( _mutex );
        return tmp;
      }
    }

    // There is no entry. Add one entry, label it as pending and wait
    // Search for an unused entry in the table
    for ( int i = 0; i < _max; i++ ) {
      TableEntry * t = &_table[i];
      if ( t->_used == 0 ) {
        t->_var = var;
        t->_val = NULL;
        t->_pending = 1;
        t->_used = 1;
        mutex_unlock( _mutex );

        // Wait until this entry is added
        sema_wait(t->_pendingSem );

        // The variable was added
        char * tmp = t->_val;
        t->_used = 0;
        t->_var = NULL;
        t->_val = NULL;
        sema_post( _full );
        mutex_unlock( _mutex );
        return tmp;
    }

    // We should never reach this point
    assert( 0 );
  }
 

};