CS360 Final Exam: December 8, 2003. Answers

Question 1

  1. Free looks 8 bytes in front of the pointer, and assumes that the integer there is the size of the block to be freed.

  2. A bad argument is any address or pointer that was not returned as a result of a malloc() call, or has already been free()'d.

  3. There are three possible results. First, you can get a bus error, if the address is not a multiple of 4. This will occur when free() tries to figure out the size, and dereferences 8 bytes in front of the pointer. Second, you can get a segmentation violation, as free() tries to hook the piece of memory onto the free list. If either the pointer itself, or any pointers that are assumed to exist once the freelist structure is enforced on the memory, is in "the void" or in read-only memory, then you will get the segmentation violation. Finally, you may simply corrupt memory, as the values that are there are legal pointers, but you won't see the results of this corruption until after the free() call return.

  4. I have no problems with seg faults and bus errors -- those are proper identifications of bad arguments. Its the last problemmatic addresses -- those that result in memory corruption, that you seek to avoid. In order to do this, you can have malloc() put checksums before and after the memory that it allocates. Then free() can check these for corruption (or existence) before attempting to free.

Grading: 9 points

You had to be precise in your wording. For example "the size is at the front of the memory block," while technically correct, is not precise enough.


Question 2

Grading: 6 points

One point each:


Question 3

You don't need multiple processes for this one, but it was ok if you used them. All you need to do is write to a pipe whose read end is closed:
main()
{
  int p[2];
  char c;

  pipe(p);

  close(p[0]);
  write(p[1], &c, 1);
}

Grading: 5 points

You pretty much got this one or you didn't. Those who effectively did "cat | ls" only got 4 points, because that doesn't generate SIGPIPE until the user types into cat.

Also, if you had two processes, and the first writes fewer than PIPE_BUF bytes to the second before the second process gets a chance to run, then the first process can exit before SIGPIPE is generated. Think about it.


Question 4

main(int argc, char **argv)
{
  int ifd, ofd, p[2], i, status;

  if (argc < 3) {
    fprintf(stderr, "Need >= 2 arguments\n");
    exit(1);
  }

  for (i = 1; i < argc; i++) {
    if (i > 1) ifd = p[0];
    if (i < argc-1) {
      if (pipe(p) < 0) { perror("pipe"); exit(1); }
      ofd = p[1];
    }
    if (fork() == 0) {
      if (i > 1) { 
        dup2(ifd, 0); 
        close(ifd);
      }    
      if (i < argc-1) {
        dup2(ofd, 1); 
        close(ofd);
      }
      execlp(argv[i], argv[i], NULL);
      perror(argv[i]);
      exit(1);
    }
    close(ofd);
    if (i < argc-1) close(ofd);
    if (i > 1) close(ifd);
  }
  for (i = 1; i < argc; i++) {
    wait(&status);
  }
}

Grading: 13 points


Question 5

So, what you need to do is save your state by calling setjmp() (or, more properly, sigsetjmp(). Then set up a handler for SIGALRM. then call alarm(10) and code_cracker(). If code_cracker() returns before 10 seconds, unset the alarm by calling alarm(0) and return. Otherwise, your handler will be executed. Call longjmp() (or, more properly, siglongjmp()) to have crack_10() gain control again, and it returns NULL.
jmp_buf env;
void handler(int dummy)
{
  siglongjmp(env, 1);
}

char *crack_10(char *s)
{
  char *output;
 
  output = (char *) malloc(1001);

  if (sigsetjmp(env, 0) == 0) {
    signal(SIGALRM, handler);
    alarm(10);
    code_cracker(s, output);
    alarm(0);
    return output;
  } else {
    free(output);
    return NULL;
  }
}
  

Grading: 5 points

Yes, I didn't realize that Brad didn't go over setjmp()/longjmp(). That said, you still should have known to allocate the output buffer, call alarm() and set up a signal handler. That got you 60% credit, and in my accounting for grades, I treated the other two points as extra credit.


Question 6

Part A: This code takes three file descriptors that are opened for reading. It waits for something to read on one of them, and when that one has something to read, it reads exactly ten bytes from it (stalling if necessary if ten bytes have not arrived yet), prints those bytes on standard output, and then waits for something to read on the remaining two file descriptors. It repeats this task on the remaining two file descriptors, and then returns.

Part B: Yes, you do need a mutex. Why? Well, you are going to fork off three threads, and have each thread read from a different file descriptor. If each file descriptor has ten bytes to read, then there are no problems, and no mutex is necessary. But, suppose file descriptor a has three bytes to read instantly, and then a big pause occurs before the remaining seven bytes are available. Unless you have a mutex keeping the other threads from reading, then those other threads can read from their file descriptors and print to standard output before the remaining seven bytes from file descriptor a are read. Thus, you do need a mutex.

Grading: 6 points

The first 4 points were for the explanation. Again, I wanted some level of precision. Often your description was ambiguous, and if so, I took off points (e.g. does your description allow for 30 bytes to be read from one file descriptor)?

The remaining points were for saying "yes", and giving either the reason that I mentioned above, or that you needed to protect the shared buffer of printf(). Saying that you need to protect buf is wrong, because in a threaded implementation, each thread would have its own buffer (there's no good reason to have them share a buffer).