CSE 120: Homework #1 Solutions

Fall 2026
  1. For each of the three mechanisms that supports dual-mode operation — privileged instructions, memory protection, and timer interrupts — explain what might go wrong without that mechanism, assuming the system only had the other two.

    Without privileged instructions: Without privileged instructions, any process could execute any instruction (e.g., instructions for interacting with I/O devices, masking interrupts, manipulating virtual memory state, etc.). As a result, a buggy or malicious process could violate memory protection (access data in the address space of another process or the operating system), I/O protection (issue requests to disk for someone else's data), fairness (turning off interrupts to gain full control over the CPU), etc.

    Without memory protection: Similar to above, without memory protection any process can potentially access the data of any other process (of any user), as well as the data maintained by the operating system. [Early personal computers lacked such memory protection, and any application could potentially disrupt other applications or the operating system with stray pointers.]

    Without timer interrupts: Fundamentally, the timer interrupt is the essential mechanism that enables the operating system to regain control over the CPU on a regular basis. Without timer interrupts, a buggy or malicious process could run indefinitely on the CPU and deny the CPU to other processes and the operating system (e.g., an infinite loop).

  2. Which of the following instructions should be privileged? (Also give a one-sentence explanation for why.)

    a) Set value of timer
    b) Read the clock
    c) Clear memory
    d) Turn off interrupts
    e) Switch from user to kernel mode

    a) Set value of timer: Yes, otherwise the user program can manipulate it such that the OS never gains control

    b) Read the Clock: No, as a user can't really do anything harmful by simply reading the clock.

    c) Clear Memory: Yes, since a user program shouldn't be able to clear arbitrary memory. (Exception: No, if interpreted as simply clearing memory belonging to the process.)

    d) Turn off interrupts: Yes, same reasoning as for setting the value of the timer.

    e) Switch from user to kernel mode: Yes, since otherwise a user program could simply switch to kernel mode to execute instructions it wouldn't otherwise be able to, and defeat security.

  3. For each of the following system calls, give a condition that causes it to fail: open, read, fork, exec, unlink.

    open: File does not exist.

    read: Invalid file descriptor.

    fork: No more processes (OS out of memory).

    exec: Program file does not exist; file exists, but does not represent a valid executable file (e.g., it is a spreadsheet); file exists, but the user does not have permission to execute it (e.g., the file does not have the exec bit set, or the user does not have permission to execute the file); ...

    unlink: File does not exist; user does not have permission to access the file.

  4. On Unix, two signals that cannot be caught and handled by an application are SIGKILL and SIGSTOP.

    a) SIGKILL immediately terminates a process. What is a scenario that motivates having the OS make it impossible for an application to handle SIGKILL?

    One scenario is a buggy program with an infinite loop. If a program with an infinite loop can handle (and ignore) SIGKILL, then it may not be possible to ever terminate the process without rebooting.

    Another scenario is if the process is malware. If malware applications could handle SIGKILL then they could prevent users from terminating them.

    b) SIGSTOP immediately pauses a process (which can later be resumed with SIGCONT). Why is it safe (in terms of application correctness) for an application to be arbitrarily paused using SIGSTOP?

    It is safe in terms of correctness because applications are already written assuming that they can be paused/suspended at any time. The effect of SIGSTOP is the same as if a process were paused by the OS as a result of a timer interrupt. The difference is that the OS would automatically schedule the process to run again in the future with a timer interrupt, whereas with SIGSTOP a process is paused until it receives a SIGCONT.

  5. An important feature of the process abstraction is that the operating system has complete control over when a process executes, and the OS can arbitrarily suspend or resume a process at its discretion. As a result, applications are generally implemented to be independent of the passage of real time. However, many applications want to know about the passage of real time to implement various features, even as simple as a cursor that blinks every second. To support such functionality, OSes provide an interface to inform applications of the passage of real time. OSes typically use the same mechanism as with exceptions (e.g., divide-by-zero) to interrupt and notify a process as time passes.

    Below is a small C program that prints the passage of time in seconds using the simple Unix alarm interface (see man 2 alarm on ieng6):

    #include <unistd.h>
    #include <signal.h>
    #include <stdio.h>
    
    volatile int seconds;
    
    void handler (int signum) {
        seconds++;
        alarm (1);   // schedule next SIGALRM
    }
    
    int main (int argc, char *argv[]) {
        int last = seconds = 0;
        signal (SIGALRM, handler);  // SIGALRM is the timer signal
        alarm (1);   // schedule SIGALRM handler callback, arg is # of seconds
    
        while (1) {  // normally we would do useful work instead of spinning
    	if (last != seconds) {
    	    printf ("%d sec\n", seconds);
    	    last = seconds;
    	}
    	if (last == 5) return 0;
        }
    }
    

    a) What is the output of this program? To verify your answer, you can create a file on ieng6, copy the code into it, and then compile and execute it:

    $ ssh account@ieng6.ucsd.edu
    [do _not_ prep for CSE 120, we need to use the normal system compiler]
    $ cc q5.c          [assuming you put the program contents into q5.c]
    $ ./a.out
    
    a) The output is:
    1 sec
    2 sec
    3 sec
    4 sec
    5 sec      
    

    b) Generally it is not possible to predict how long a program will take to execute by inspecting its code. However, for this program we can. Approximately how long does it take to execute? (You can run it on ieng6 with time ./a.out.)

    b) Approximately 5 seconds

  6. The use of mechanisms like Unix signals introduces a source of non-determinism and concurrency in the execution of a process, even for single-threaded applications. Consider the following very similar C program:
    #include <unistd.h>
    #include <signal.h>
    #include <stdio.h>
    
    volatile int seconds;
    
    void handler (int signum) {
        seconds++;
        alarm (1);   // schedule next SIGALRM
    }
    
    int main (int argc, char *argv[]) {
        int last = seconds = 0;
        signal (SIGALRM, handler);  // SIGALRM is the timer signal
        alarm (1);   // schedule SIGALRM handler callback, arg is # of seconds
    
        while (1) {  // normally we would do useful work instead of spinning
            if (last != seconds) {
                printf ("%d sec\n", seconds);
                last = seconds;
            }
            if (seconds == 5) return 0;  // line changed from problem 5
        }
    }
    

    Only one line is changed from Problem 5: the "if (...) return 0" statement in the while loop. When executing the program, sometimes the program prints out "n sec" four times and sometimes five times. Briefly explain why. (Again you can compile the program and experiment with it, although you may need to run the program a handful of times to see different outputs.)

    Even though it is a single-threaded program, the timer interrupt introduces a race condition on the variable "seconds". A timer interrupt can invoke the handler in between the two "ifs": the first "if" sees seconds as 4; the timer interrupt fires, invokes the handler, and the handler increments seconds to 5; the second "if" now sees seconds as 5 and exits.

  7. You may have written programs that call exit, or return from main with some value, and wondered what happens to that value. The value can be returned to a parent process when the parent calls wait on the child: if the child calls exit(0) then the value 0 can be returned to the parent via wait. Why have this functionality, why might it be useful?

    The status value that the child process passes to exit allows the child to indicate to the parent what happened when it executed — in its most basic form, the status value allows the child to indicate to the parent whether the child was successful or not. From this perspective, you can think of the child process as a large, coarse-grained procedure. The parent "invokes" the child as a process, and the child calls exit to pass a return value back to the parent.

    Shell scripts frequently take advantage of this functionality, such as when a shell script wants to take a different action depending on whether a command was successful or not. The man pages for command line tools typically end by describing their exit status values and their interpretations. For instance, here is the list from man ls on ieng6:

       Exit status:
           0      if OK,
           1      if minor problems (e.g., cannot access subdirectory),
           2      if serious trouble (e.g., cannot access command-line argument).       

  8. Consider the following C program:
    #include <stdlib.h>
    
    int main (int argc, char *arg[])
    {
        fork ();
        if (fork ()) {
    	fork ();
        } else {
            char *argv[2] = {"/bin/ls", NULL};
            execv (argv[0], argv);
            fork ();
        }
    }
    

    a) How many total processes are created (including the first process running the program)? (Note that execv is just one of multiple ways of invoking exec, see man 3 exec for all possibilities.)

    Hint: You can always add debugging code, compile it, and run the program to experiment with what happens.

    6 processes are created, including the first process. For reference, an example process tree looks like:

    30938
    30938 -> 30939
             30939 -> 30940
                      30940 -> 30942
    30938 -> 30941
             30941 -> 30943
    

    b) How many times does the /bin/ls program execute?

    The /bin/ls program executes twice.

    Note that exec does not create a new process, it overwrites the currently running process with a new program. As a result, the branch in the program that calls exec will not call fork after the exec.