CSC374 Midterm Solutions

Correct answers are marked and solution remarks are included.


  1. How many Hello's are printed. (Assume fork does not fail.)

    
    int main() {
      int i;
      for(i = 0; i < 2; i++) {
        fork();
        printf("Hello\n");
      }
      return 0;
    }
    
    1. 2
    2. 4
    3. 6
    4. 8

    This code is equivalent to:

      fork();
      printf("Hello\n");
      fork();
      printf("Hello\n");
    	    

    Now draw the diagram (where H is for the print statement):

                   +--- H --
                   |
          +--- H --+--- H --
          |     
          |        +--- H --
          |        |
    ------+--- H --+--- H --
    
    	    
  2. Mark ALL the following statements either Y if correct and N if not. ASSUME all functions calls succeed unless otherwise noted.

    1. The function execvp is called once, but doesn't return.

    2. The function fork is called once, but returns twice.

    3. The function longjmp is called once, but returns one or more times.

    4. The function setjmp is called once, but doesn't return.

    5. If the fork function fails, it returns once.

    • When longjmp is called, the call stack replaced by the call stack in the saved jmp_buf variable. This means the call stack is modified so that control executes as if returning from the call to setjmp instead of longjmp. So when longjmp is called it doesn't return.
    • When setjmp is called the state of the stack is saved in jmp_buf variable as if about to return from setjmp. Then setjmp returns 0. But everytime longjmp is called with the same jmp_buf variable, the same setjmp returns again. So setjmp is called once, but returns 1 or more times.
  3. How many hello's are printed?

    void doit() 
    {
        printf("hello\n");
        fork();
        fork();
    
        return;
    }
    
    int main() 
    {
        doit();
        printf("hello\n");
    
        exit(0);
    }
    
    1. 4
    2. 5
    3. 6
    4. 7

    	      +--- H --
                  |
               +--+--- H --
               |
               |  +--- H --
               |  |
    ---+-- H --+--+--- H --
       d
    		
  4. What is one possible output of the following program?

    int main() 
    {
        int x = 3;
        int y = 1;
        if (fork() != 0) {
            x = x + y;
            y = y + 1;
    	printf("x=%d ", x);
        }
        x = x + y;
        printf("x=%d ", x);
        exit(0);
    }
    
    1. x=4 x=5 x=6
    2. x=4 x=5 x=5
    3. x=4 x=6 x=6
    4. x=4 x=4 x=6

                   +- x=3,y=1 -- x=4 -- P(x = 4) ---
                   |
                   |
    --- x=3,y=1 ---+------ x=4,y=2 -- P(x = 4) -- x=6 -- P(x = 6) ---
    	      

    where P(...) stands for a print statement

  5. Mark ALL the following outputs either Y if possible and N if not.

    void doit(int n) 
    {
    
        if (fork() > 0) {
           printf("%d ", n);
        } else {
           printf("%d ", n + 1);
        }
    
        return;
    }
    
    int main() 
    {
        int pid;
      
        pid = fork();
        waitpid(-1, 0, 0);
        doit(!pid);
    
        exit(0);
    }
    
    1. 2 1 1 0
    2. 2 0 1 1
    3. 1 2 1 0
    4. 1 0 2 1
    5. 0 1 1 2

    If a process that has no (remaining) child processes calls waitpid, it returns -1 without blocking. So the first child calls waitpid, but doesn't block.

       w = waitpid
       d = doit
       P = printf
    		
                +------- P(2) ---|
                |
       +--------+--- P(1) ---|
       |  w d(1)
       |                          +----P(1) ---|
       |                          |
    ---+--- wait ............-----+--- P(0) ---|
          w                  d(0)
    	      

    The first process (bottom line) must wait until its first child prints 1. So 0 must be printed after this child's 1. So 2 0 1 1 is not possible and 0 1 1 2 is not possible.

    But note that the first process doesn't have to wait for its "grandchild" that prints 2.

  6. What is the output of this program?

    int counter = 1;
    
    int main() 
    {
        if (fork() == 0) {
    	counter--;  
        } else {
    	waitpid(-1, 0, 0); 
        }
        printf("%d ", ++counter);	
        exit(0);
    }
    
    1. 1 1
    2. 2 2
    3. 1 2
    4. 2 1

    		
                  + counter=1 -- counter=0 -- P(++counter)--|
                  |
     counter=1 ---+------ wait .............................-- P(++counter)--|
                         w
    	      

    So the child prints 1. The parent must wait until the child finishes and then prints 2. So the output is 1 2.

  7. Mark ALL the following outputs either Y if possible and N if not.

    int main() 
    {
        int status;
        pid_t pid;
      
        printf("A ");
        pid = fork();
        printf("%d ", !pid);
        if (waitpid(-1, &status, 0) > 0) {
    	if (WIFEXITED(status) != 0) {
    	    printf("%d ", WEXITSTATUS(status));
            }
            exit(0);
        }
    
        printf("B\n");
        exit(1);
    }
    
    1. A 1 B 0 1
    2. A 1 B 1 0
    3. A 1 0 B 1
    4. A 0 1 B 1
    5. A 0 B 1 1

    Again, the waitpid returns immediately for the child process since it has no child processes of its own.

     P=printf
     w=waitpid
          
                +--- P(1) --- P(B) -- exit(1)-|
                |         w
                |
    ---- P(A)---+--- P(0) -...................-- P(1)--exit(0)
                          w
    	      

    A is printed, then the parent prints 0 and waits while the child prints 1 B. After the child exits, the parent prints the childs exit value, 1.

    A must be printed first.

    Next the parent prints 0 while the child prints 1 and B. This means the next output could be 0 1 B or 1 0 B or 1 B 0.

    Finally after waiting for the child to finish, the parent prints 1.

    All possible outputs are:

    		A 0 1 B 1
    		A 1 0 B 1
    		A 1 B 0 1
    	      
  8. Mark ALL the following outputs either Y if possible and N if not.

    void end(void) 
    {
        printf("A"); 
    }
    
    int main() 
    {
        atexit(end);
        if (fork() == 0) {
    	printf("B");
        } else {
    	printf("C");
        }
        exit(0);
    }
    
    1. BACA
    2. BCAA
    3. AABC
    4. AACB
    5. BAAC

      ['end'] means 'end' function is registered to execute when the
              process exits
    
    
    		 
                     +-- B -- A -|
                     |
     --- ['end' ] ---+-- C -- A -|
    	      

    Note that the 'end' function is registered in the parent before the call to fork(). This means 'end' is registered both in the parent and the child.

    If atexit(end) had been only been called by the child process, it would not be registered in the parent.

    The parent prints C A while the child prints B A.

    All possible interleavings of these are possible:

    • CABA
    • CBAA
    • BACA
    • BCAA
  9. Which ONE of the choices below is a possible output of the following program?

    pid_t pid;
    int counter = 2;
    
    void handler(int sig) {
        counter = counter - 1;
        printf("%d", counter);
        fflush(stdout); 
        exit(0);
    }
    
    int main() {
        signal(SIGUSR1, handler);
    
        printf("%d", counter);
        fflush(stdout);
    
        if ((pid = fork()) == 0) {
    	while(1) {};
        }
        kill(pid, SIGUSR1); 
        waitpid(-1, NULL, 0);
        counter = counter + 1;
        printf("%d", counter);
        exit(0);
    }
    
    1. 231
    2. 211
    3. 233
    4. 213

    • The initial process prints 2.
    • While the child process becomes caught in the while loop, the parent executes the kill function which sends the SIGUSR1 signal to the child.
    • The parent then waits for the child to terminate.
    • The child receives the SIGUSR1 signal and enters the handler function and prints 1 and exits.
    • Since the child has exited, the parent returns from waitpid and prints 3 and exits.

    So 213 is the possible output.

  10. Whic one of the choices below is a possible output of the following program.

    jmp_buf buf;
    
    void handler(int sig)
    {
      siglongjmp(buf, 1);
    }
    
    int main()
    {
      signal(SIGUSR1, handler);
      if (sigsetjmp(buf, 1) == 0) {
         printf("A");
      } else { 
         printf("B");
         exit(0);
      }
      if (fork() == 0) {
         kill(getppid(), SIGUSR1);
         exit(0);
      }
    
      waitpid(-1, 0, 0);
      printf("C");
      return 0;
    }     
    
    1. AB
    2. AC
    3. ABC
    4. ACB

    • The first call to sigsetjmp returns 0, so the intial process prints A and then creates a child.
    • The child sends the SIGUSR1 signal to the parent and the child exits while the parent waits for the child to finish.
    • After the child exits, the parent receives the SIGUSR1 signal and so instead of executing the printf("C") statement, first executes the code in handler.
    • In handler, the parent executes siglongjmp which resets the stack so that the parent executes a return from the sigsetjmp instead of a return from siglongjmp.
    • But the return value from sigsetjmp this second time is 1. So the parent prints B and exits.

    The output is: AB

  11. Mark ALL statements Y if correct and N if not.

    1. Most signals can be blocked temporarily using the sigprocmask system call.

    2. If a process calls setpgrp(), its group id becomes the same as its process id.

    3. If kill(1400, SIGKILL) is called,the SIGKILL signal will be sent to all processes with group id 1400.

      kill(-1400, SIGKILL) sends the SIGKILL to all processes with group id 1400 while kill(1400, SIGKILL) would only send SIGKILL to the the single process with process id 1400.

    4. If 5 child processes terminate while the parent has the SIGCHLD signal blocked, and then the parent unblocks SIGCHLD, the parent will execute its SIGCHLD handler 5 times, once for each child.

      The handler will only execute once. The SIGCHLD bit in the pending signals will have been (re)set 5 times, but since it is only 1 bit, there is no record of how many times it was set. The handler needs to loop repeatedly calling waitpid while it returns a value > 0 in order to count or otherwise handle all 5 child processes.

    5. In Linux, a SIGCHLD signal is sent to a parent if a child is stopped by a signal or if the child terminates.