CS31 Weekly Lab: Week 10

man, signals, global variables, fixed-sized queues

Week 10 lab goals:

  1. Introduce man and apropos
  2. Introduce signals and signal handler functions
  3. Learn how to declare and use global variables in C
  4. Learn how to implement a fixed-size queue (using circular arrays)

Create a week10 subdirectory in your weeklylab subdirectory and copy over some files:
    cd cs31/weeklylab		
    pwd
    mkdir week10
    ls
    cd week10
    pwd
    cp ~lammert/public/cs31/week10/* .
    ls
    Makefile  circularqueue.c  globalvars.c  signals.c
man and apropos
First, we are going to learn how to read manual page using man and searching for commands using apropos man and apropos

Next, let's look at the man page for fork and see what it is telling us:

$ man fork  # the man page for the system call fork
$ man 2 fork   # section 2 of the man page

Signals and signal handlers
Let's look at the program signals.c. This program has some examples of registering a signal handler function on a signal, and some examples of sending signals to processes (for example, alarm can be used to send a SIGALRM to one's self).

We will run this program and use the kill command to send the process signals:

kill -INT 1234  # sends a SIGINT signal to process 1234

The man page for signal lists the signals on this system and describes the signal system call in more detail.

Global variables in C
Global variables are variables that are declared outside any function definition. They are always in scope, and the space for them persists for the entire run of the program.

Open globals.c in vim or emacs, and lets take a look.

The static modifier to their declaration limits the scope to functions within this .c file (only those can name these global variables), but there storage space still persists for the entire run of the program.

You should avoid using global variables in your program except when absolutely necessary or when explicitly allowed. In general, you should always design functions to take as parameters the values they need; if a function needs a value, pass it in, and if a caller needs a value from the function return it (or pass in pass-by-reference style parameters through which the function can modify the caller's argument values). This makes your code more generic than using globals, and it avoids allocation of space for the entire run of your program when it is not needed.

Sometimes it is necessary to use global variables. For instance, when you need a value needs to persist between function calls, beyond the return from a function that could create it as a local, glocal variables are required. If there is no function that could be on the call stack and have the variable as a local variable, you will be forced to use global variables.

The example in globals.c is not an obvious need for a global variable, but if this code was being used to implement a library, then there may be a real need for using a global. You may see such a situation next week! For now, I just want you to see and use global variables.

Fixed-size queues (using circular arrays)
A queue is a data structure whose values are ordered in FIFO order (first-in-first-out). New items are added to the end of the queue and removed from the front of the queue (FIFO semantics for adding and removing). In the case of a fixed-size queue, it is likely that the queue will fill up completely, and space will run out. When a fixed-size queue is full, adding an item requires removing another. The item that gets removed will always be the one added first (again, FIFO order). Thus, the "out" in first-in-first-out may actually mean "kicked out"!

You are going to implement a fixed-size queue a circular array of int values. Each time a new value is added to a full array, it replaces the item that is currently first in the array (i.e., the oldest value added to the queue).

As values are added and removed the first and last bucket index values of the queue change. When the very last bucket is filled the first time, the next value added will replace the value in bucket 0 (the new end of the list) and the first bucket in the list now becomes bucket 1 (the next bucket to replace). When the next value after that is added, it is added to bucket 1 (the new end of the list) and the new start of the list becomes bucket 2, and so on. This is a circular array implementation of a queue because the first and last bucket indices cycle around the array indexes (0, 1, ..., 4, 0, 1, ..., 4, 0, 1, ...)

For example, for a circular queue of int values of size 5:

After adding values 3, 6, 17: 
- the queue has 3 elements
- the next bucket to insert into is bucket 3
- the first value in the queue is in bucket 0
- the last value in the queue is in bucket 2
---------------------------- 
|  3 |  6 | 17 |    |    |
----------------------------
                 
After adding values 10, 4:   
- the queue has 5 elements
- the next bucket to insert into is bucket 0
- the first value in the queue is in bucket 0
- the last value in the queue is in bucket 4
---------------------------- 
|  3 |  6 | 17 | 10 |  4 |
----------------------------

After adding the value 7:      
- the list has 5 elements
- the next bucket to insert into is bucket 1
- the first value in the queue is in bucket 1
- the last value in the queue is in bucket 0
---------------------------- 
|  7 |  6 | 17 | 10 |  4 |
----------------------------

After adding the value 9:      
- the list has 5 elements
- the next bucket to insert into is bucket 2  
- the first value in the queue is in bucket 2
- the last value in the queue is in bucket 1
---------------------------- 
|  7 |  9 | 17 | 10 |  4 |
----------------------------

Printing out the queue from first to last value is:  17  10  4  7  9
In circqueue.c is the starting point of a circular queue implementation. You are going to implement and test two functions:
void add_queue(int value): add a new value to the queue (and update queue state)
                           the value added should replace the first item
                           in the queue if the array is full
void print_queue(): print out the values in the queue from first to last
This code uses global variables for the queue (implemented as an array of ints) and for other state associated with the queue (and feel free to add more state variables if you need them).

This example is not an obvious need for a global variable, but if this code was being used to implement a single queue library, then declaring the queue and its state as a global is necessary (we will discuss globals in more detail later).

For now, I just want you to get some practice with global variables. However, you should always avoid using global variables in your program, and only do so when except explicitly allowed in this class. In general, you should always design functions to take as parameters the values it needs.