Lab Signals

Unix Signals

 Goals

  • Learn to work with Unix signals. You now understand how computer systems might use the notion of interrupt to service requests from I/O devices. The concepts of asynchronous arrival of requests and of mapping specific requests to specific procedures to handle them are not exclusive to the lowest level hardware/software interface, though. This lab illustrates the point by exposing how the Unix operating system defines the concept of signal, an asynchronous mechanism for interprocess communication. Signals are be mapped to functions that serve as handlers for their reception, similarly to how interrupt requests are mapped to interrupt handlers.
  • Reinforce the concept of function pointers in C. In order to define Unix signal handlers, the programmer must construct functions that follow a well specified prototype. (Remember that you have been using function pointers in you work with threads.) Once that function exists, the programmer passes its address to the operating system with a request to associate it with a specific signal. The idea of defining a data type that encapsulates a function prototype corresponds to the concept of function pointers in C. In this lab, you will get additional practice with function pointers.

Credits

This lab was developed by Prof. L. Felipe Perrone. Permission to reuse this material in parts or in its entirety is granted provided that this “credits” note is not removed. Additional students files associated with this lab, as well as any existing solutions can be provided upon request by e-mail to: perrone[at]bucknell[dot]edu


Set Up

This will be your working directory for this lab:

~/csci315/Labs/Lab10/

Copy the following file to your git repository:

~cs315/Labs/Lab10/timer.c 

Background

By now, you should be very much used to the concept of pointers in the C language. The kind of pointers you have been using serve to declare variables that hold addresses of locations in the data segment of a program. For example:
int i;
int *myintp = &i;
The C language also includes the concept of function pointer, which in can be used to define variables that hold the address to functions rather than the address to data. You have used this C construct before, when you worked with threads, but here’s some more information to help you internalize concepts better.
The type of a C function pointer is bound to a function prototype, which specifies the type of data returned by the function and the types of the formal arguments for the function.
For instance, if you have a function called myfunc that returns nothing and takes two integers as formal parameters, you write its prototype as:
void myfunc (int a, int b);
If we abstract away the name of the function and the names of the formal parameters, we can define a function pointer that will represent any function that matches this prototype. For instance, we could define a function pointer type as:
void *function_pointer (int, int);
Note that the line above defines a variable, which is a pointer to a function and therefore can hold only one type of value: the address to a function that matches the prototype “returns nothing and take two int arguments.” We could make an assignment to this variable as follows:
function_pointer = myfunc;

Note that we don’t have to use the address operator & to get the address of myfunc because when the name of a function is not followed by parentheses with parameters, the compiler understands that we are referring to the address of the function.

The neat thing is that once you’ve stored the address of a function in a function pointer variable, you can use the variable to invoke the function. We just have to remember to dereference the function pointer variable. For example:

(*function_pointer) (123, 456);

The line above will call whatever function has its address stored in the function_pointer variable passing the actual parameters 123 and 456.

There are many interesting uses of this language feature; we will see one of them in this lab, which relates to the definition of functions to handle special system events or messages called signals. When a signal is received by a running program, the program deviates temporarily from its normal path of execution to invoke the function that is mapped as handler to that specific type of signal.

Signals are asynchronous “messages” to a running program and to each kind of signal we can associate a specific signal handler. In that sense, the framework for working with signals is similar to that of the framework for dealing with hardware interrupts.

You might want to read this brief tutorial on signals before going on. If you are curious about the mapping of signal names to integer numbers, you should take a look at the following file in our lab systems (the specific path and file might change for other Unix installations):

/usr/include/asm-generic/signal.h

Problem 1 (10 points)

In order to earn full credit for this problem, you must submit a Makefile to compile all the programs you are turning in, which will be called timer, clock, and user. That is, in this file, you need to have one rule for each executable you want to generate.

Problem 2 (30 points)

File timer.c is the source code of a program that installs a handler for Unix signal SIGALRM and then schedules timers to generate such signal every so often (5 second intervals). Read the manual page for the signal(2) system call to understand how this program uses it to install a signal handler. It turns out that this system call has been made outdated by a newer one called sigaction(2).

Read the manual page for sigaction(2) and then modify timer.c so to work with this newer system call.

Hint: You must use memset(3) to zero out all the bytes in any struct sigaction you define. Otherwise, you will see some non-deterministic behavior in your program. If this doesn’t make sense to you at this point, it is because you didn’t read sigaction(2)‘s manual page carefully enough.

When you are done with this, you need to:

  • cd ~/csci315/Labs/Lab10
  • git add Makefile
  • git add timer.c
  • git commit -m “Lab10, problems 1 and 2 completed”
  • git push

Problem 3 (30 points)

Copy your solution to Problem 1 to a new file called clock.c. Now, modify the program so that you can take advantage of your knowledge of the passage of time to implement something like a real time clock, which only shows updates every 5 seconds. Your program should print out the value of the clock in the body of the while loop – the output will scroll by really fast, but you always be able to see the value of the clock.

When you are done with this, you need to:

  • cd ~/csci315/Labs/Lab10
  • git add Makefile
  • git add clock.c
  • git commit -m “Lab10, problem 3 completed”
  • git push

Problem 4 (30 points)

Copy your solution to Problem 2 to a new file called user.c. Modify your program to install a new signal handler for the general purpose signal SIGUSR1 (which corresponds to the number 10); the body of this handler can be identical to timer_handler. Once your program compiles, launch it in your current terminal window and open a second terminal. Follow the guidelines below to test your program.

In your second terminal, run the line:

ps -ef | grep user

Assuming that you compiled your user.c to an executable called user, the line above does the following:

  • Calls the system utility ps(1) to list all processes currently running in your machine,
  • Sends the output of ps to another program (the “|” creates a pipe mapping the standard output of one program to the standard input of the next), and finally,
  • Calls the system utility grep(1) to sift through the output and find all lines that contain the string “user”.
In the resulting line, the number following your userid will be the process identification (or pid) of your running program user. Now, using this number and the shell’s builtin command kill or the kill(1) system utility in your second terminal, you can test whether your new handler for SIGUSR1 is working:
kill -10 pid 

Instead of identifying the signal to send to the process identified by pid by a numerical value (in this case SIGUSR1=10), you could use a string. The actual string for signal 10 may vary according to the shell you use. In order to list the strings associated with each numerical signal value, you can interrogate your shell with:

kill -l

That is the letter “el” above, not the number 1! For bash, the string is exactly SIGUSR1, so you would do:

kill -s SIGUSR1 pid 

For tcsh, sh, and zsh, the string is USR1, so you would do, instead:

kill -s USR1 pid

If intellectual curiosity is getting the best of you, remember that you can read the manual pages (in section 1) for the three system utilities mentioned above (that is, psgrep, and kill.)

When you are done with this, you need to:

  • cd ~/csci315/Labs/Lab10
  • git add Makefile
  • git add user.c
  • git commit -m “Lab10, problem 4 completed”
  • git push

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.