CSCI 415/515 - Fall 2023
Systems Programming
Exam Prep


Warm-up

1.1

(a) The memory layout of every process contains two important sections: the stack and the heap. Briefly describe the difference between these two.

(b) In the snippet below, in which sections do the values a, b, *b live. For a bonus, in which sections of memory do c and d live.


        int foo(void)
        {
            int a = 1;
            int *b = malloc(sizeof(*b));
            static int c;
            static int d = 5;

            /* ... */
        }
        

1.2

What causes the delivery of a SIGSEGV signal to a process?

1.3

In the macro below, why do we use a do/while loop?


        #define mu_die(fmt, ...) \
            do { \
                fprintf(stderr, fmt "\n",##__VA_ARGS__); \
                exit(1); \
            } while (0)
        

1.4

What is the initial values of a, b[0], and c[0] in the code snippet below:


        void foo(void)
        {
            int a;
            char *b = malloc(10);
            char *c = calloc(1, 10);
        }
        

File I/O and File Systems

2.1

What is a file hole and name two ways to create one.

2.2

Given the st_size field of struct stat, what can we infer about the st_blocks field?

2.3

Give a limitation of a hard link and describe why a soft link overcomes this limitation.

2.4

What is the effect of the following statements?


        fflush(fh);
        fsync(fileno(fh));
        

2.5

Explain why the output of the following code differs depending on whether standard output is redirected to a terminal or to a disk file.


        printf("If I had more time, \n");
        write(STDOUT_FILENO, "I would have written you a shorter letter.\n", 43);
        

2.6

Suppose the file named /tmp/datafile exists (protected with mode 0666) and contains the following 26 bytes of character data:

abcdefghijklmnopqrstuvwxyz

What is the output of the following program?


        #include <sys/types.h>
        #include <sys/stat.h>

        #include <fcntl.h>
        #include <stdio.h>
        #include <unistd.h>

        int
        main(void)
        {
            int fd1, fd2, fd3;
            char c1, c2, c3;

            fd1 = open("/tmp/datafile", O_RDONLY);
            fd2 = dup(fd1);
            lseek(fd1, 2, SEEK_SET);

            fd3 = open("/tmp/datafile", O_RDONLY);

            read(fd1, &c1, 1);
            read(fd2, &c2, 1);
            read(fd3, &c3, 1);

            printf("%c%c%c\n", c1, c2, c3);

            close(fd1);
            close(fd2);
            close(fd3);

            return 0;
        }
        

2.7

How can we ensure that the following file is deleted when the process terminates, even if it terminates in an abnormal fashion (i.e., is killed by a signal). Explain.


        fd = open("foo.tmp", O_RDWR| O_CREAT|O_EXCL, 0600);
        

Signals and Timers

3.1

Which of the following is a potential source of a signal to a process:
  1. the kernel (on behalf of some hardware event)
  2. another process
  3. the process itself
  4. all of the above

3.2

Add a few lines of code to ensure that the following snippet of code is not interrupted by a SIGINT.


        printf("start\n");
        sleep(10);
        printf("end\n");
        

3.3

What is the difference between SIGTERM and SIGKILL? Why is it generally better to first try to kill a process using SIGTERM?

3.4

The following signal handler is designed to reap terminated child processes. What are two problems with this signal handler? Modify the signal handler to fix these problems.


        static void
        sigchld_handler(int sig)
        {
            wait(NULL);
        }
        

3.5

A signal handler often simply sets a global flag and then returns:


        volatile sig_atomic_t flag;

        static void
        handler(int sig)
        {
          flag = 1;
        }
        

Why is flag declared as sig_atomic_t? volatile?


Processes and Pipes

4.1

If a process's parent terminates, how is the process reaped when it itself terminates?

4.2

Explain the difference between an orphaned process and a zombie process?

4.3

In many shells, the following syntax adds a value to the environment used to execute a program, without affecting the parent shell (or any subsequent commands):


        NAME=value program
        

Both the putenv and the setenv C library functions add a variable to the environment. Recalling the bsh project, where (i.e., between which system calls) would bsh call putenv/setenv.

4.4

Suppose the file named /tmp/datafile exists (protected with mode 0666) and contains the following 26 bytes of character data:

abcdefghijklmnopqrstuvwxyz

Consider the following program:


        #include <sys/types.h>
        #include <sys/stat.h>
        #include <sys/wait.h>

        #include <fcntl.h>
        #include <unistd.h>

        int
        main(void)
        {
            int pfd[2],fd;
            int i;
            char ch;

            pipe(pfd);
            if (fork() == 0) {
                close(pfd[1]);
                while (read(pfd[0], &ch, 1) != 0)
                    write(STDOUT_FILENO, &ch, 1);
            } else {
                close(pfd[0]);
                fd = open("/tmp/datafile", O_RDONLY);
                for (i = 0; i < 5; i++) {
                    read(fd, &ch, 1);
                    write(pfd[1], &ch, 1);
                }

                wait(NULL);
            }

            return 0;
        }
        

Select the statement which most accurately describes the process:

  1. The string abcde is output
  2. Only the character a is output
  3. The string abcde is output and then the process hangs
  4. The process hangs without producing output
  5. None of the above

Sockets

5.1

Many of the socket-related system calls take a struct sockaddr as an argument. However, in our programs, we never declare a variable of this type. Therefore, what is the purpose of the struct sockaddr type?

5.2

What is the purpose of the backlog parameter to the listen system call? Why shouldn't we specify this value as 0?

5.3

In socket programs, we often see code along the lines of:


        signal(SIGPIPE, SIG_IGN);
        

What is the purpose of this line of code? Specifically, under what conditions would a networking process receive a SIGPIPE, and how does ignoring the SIGPIPE benefit the program?

5.4

Consider following client and server code:


          /* client */
          int
          main(int argc, char *argv[])
          {
              int sk;
              struct sockaddr_in server;
              char *msg = "abcdefghijlkmnopqrstuvwxyz";
              ssize_t n;

              if (argc != 3)
                  mu_die("Usage: %s IP PORT", argv[0]);

              sk = socket(AF_INET, SOCK_DGRAM, 0);
              if (sk == -1)
                  mu_die_errno(errno, "socket");

              mu_init_sockaddr_in(&server, argv[1], argv[2]);

              n = sendto(sk, msg, 26, 0, (struct sockaddr *)&server, sizeof(server));
              if (n == -1)
                  mu_die_errno(errno, "sendto #1");

              n = sendto(sk, msg, 26, 0, (struct sockaddr *)&server, sizeof(server));
              if (n == -1)
                  mu_die_errno(errno, "sendto #2");

              close(sk);

              return 0;
          }
        

          /* server */
          #define BUF_SIZE 6

          int
          main(int argc, char *argv[])
          {
              struct sockaddr_in sa;
              int sk;
              int err;
              char buf[BUF_SIZE] = { 0 };
              ssize_t n;

              if (argc != 3)
                  mu_die("Usage: %s IP PORT", argv[0]);

              sk = socket(AF_INET, SOCK_DGRAM, 0);
              if (sk == -1)
                  mu_die_errno(errno, "socket");

              mu_init_sockaddr_in(&sa, argv[1], argv[2]);
              err = bind(sk, (struct sockaddr *)&sa, sizeof(sa));
              if (err == -1)
                  mu_die_errno(errno, "bind");

              n = recvfrom(sk, buf, sizeof(buf) - 1, 0, NULL, NULL);
              if (n == -1)
                  mu_die_errno(errno, "recvfrom #1");

              printf("%s\n", buf);

              memset(buf, 0x00, sizeof(buf));
              n = recvfrom(sk, buf, sizeof(buf) - 1, 0, NULL, NULL);
              if (n == -1)
                  mu_die_errno(errno, "recvfrom #2");

              printf("%s\n", buf);

              close(sk);

              return 0;
          }
        

Given the following command-lines, what does the server output?


            ./client 127.0.0.1 5140
            

            ./server 127.0.0.1 5140
            

Threads

6.1

Which of the following are ways a thread may terminate (mark all that apply):
  1. The thread's start function returns
  2. The thread calls pthread_exit
  3. Any thread in the process calls exit
  4. The process's main() function returns.
  5. The main thread (the one executing main()) calls pthread_exit.

6.2

Consider the program below:


        static void *
        thread_func(void *arg)
        {
            int v = *(int *)arg;
            printf("%d\n", v);
            return NULL;
        }


        int
        main(void)
        {
            pthread_t t[4];
            int i;

            for (i = 0; i < NUM_THREADS; i++)
                xpthread_create(&t[i], NULL, thread_func, &i);

            for (i = 0; i < NUM_THREADS; i++)
                xpthread_join(t[i], NULL);

            return 0;
        }
        

Which best describes the output:

  1. The lines 0, 1, 2, 3
  2. The lines 0, 1, 2, 3, but printed in any order
  3. The lines 3, 3, 3, 3
  4. The output cannot be determined