layout | title | <!--license |
---|---|---|
default |
Linux IPC and pipes |
- TOC {: toc}
This lab has a number of elements:
- Practice with C structs, arrays and pointers
- Linux IPC with signals
- Linux IPC with pipes
You need to understand how to use C structs, arrays and pointers in order to develop a strong understanding of operating systems. You should already have a good C text book. There is also some useful material online. See the module page.
-
Read Section 3 Complex Data Types in Essential C.
-
Create a program called
c_structs_and_pointers.c
from the example below.#include <stdio.h> struct fraction { int numerator; int denominator; }; void printFraction(struct fraction f) { printf("%i/%i", f.numerator, f.denominator); } int main () { struct fraction f1; struct fraction f2; f1.numerator = 21; f1.denominator = 3; f2 = f1; printf("f1 = "); printFraction(f1); printf("\n"); printf("f2 = "); printFraction(f2); printf("\n"); }
{: .code} Compile and run the program. Read through it carefully. Ask your lab tutor about any aspects that you don't understand. In the exercises that follow, make sure that you compile and run the program after every modification. Don't leave compiling and running to the end.
-
Edit the program to add two new variables of type
struct fraction
, calledf3
andf4
. Add assignment statements to set the numerator off3
to 24 and its denominator to 6. Do the same forf4
making its numerator and denominator 48 and 8, respectively. Add statements to print out the details off3
andf4
, similarly to the way thatf1
andf2
are printed in the example. -
Add two new integer variables called
num
anddenom
to the program. Write assignments to make the value ofnum
equal to the numerator off3
and the value ofdenom
equal to the denominator off4
. Add print statements to print out the values ofnum
anddenom
. -
Declare an array
a
of 5 variables of typestruct fraction
. Write a functioninitFractionArray
to initialise the numerators and denominators of all elements in the array to the value 1 (use a loop). CallinitFractionArray
from yourmain
function to initialisea
. Now add code to your main function to set the values of the numerator and denominator of the third element in the array to the same values as f2, and the values of the numerator and denominator of the fourth element in the array to 6 and 12, respectively. Add code to print out the values of all elements ofa
. -
Declare a variable
p1
to be a pointer to astruct fraction
. Write an assignment statement to makep1
point to the fractionf1
. Use the*
notation withp1
to assign the value 5 to the numerator of the fraction that it points to. Use the->
notation withp1
to assign the value 25 to the denominator. Write statements to print the values of the fractionf1
and the fraction pointed to byp1
. Using a single assignment statement, assign the value of the fraction pointed to byp1
to the fractionf3
. Print out the value off3
. -
Write an assignment statement to make
p1
point to the third element of the arraya
. Now assign the value off4
to the fraction pointed to byp1
. Print out the values of all the variables in your program. Make sure that you can explain the results that you see.
A very basic form of inter-process communication in Unix is the use of signals. Signals are described in this lecture.
The program below gives a simple example of the use of a signal in a C program.
The alarm()
function causes the SIGALRM
signal to be generated at the end
of a specified period of time, e.g. alarm(2)
generates the signal after 2
seconds. The pause()
function simply waits until any signal is generated.
The program shows how to install your own handler for the SIGALRM
signal. You
can use this approach to install a handler for any signal.
struct sigaction
is a C struct that includes the fields
-
sa_handler
- this should be set to the function that you want to act as the handler for the signal. In the example below, the handler function iscatchAlarm()
. Notice that the type of this function shows that it takes a single integer argument and returns a void result, i.e.void catchAlarm(int sig)
. Every signal handler function that you write must have a type like this. -
sa_mask
- this is a value that determines which other signals will be blocked while your signal handler function is executed. Ifact
is astruct sigaction
then callingsigfillset(&act.sa_mask)
ensures that all signals will be blocked. -
sa_flags
is a field that controls some aspects of the signal handling. For the moment, it is fine for you to accept the default behaviour by setting the value of this field to 0.#include <signal.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #define TIMEOUT_SECS 2 int tick = 0; void give_up(char *msg) {perror(msg); exit(1);} void catchAlarm(int ignored) {tick += 1;} int main() { struct sigaction act; act.sa_handler = catchAlarm; if (sigfillset(&act.sa_mask) < 0) { give_up("sigfillset"); } act.sa_flags = 0; if (sigaction(SIGALRM, &act, 0) < 0) { give_up("sigaction"); } do { alarm(TIMEOUT_SECS); pause(); printf("Tick %i\n", tick); } while (1); }
{: .code}
-
Create a program called
ticker.c
using the program above. Compile and build the program. Run it and observe its behaviour. Make sure that you understand the program. Ask your lab tutor about any aspects that you're not clear about. -
Add code to this program to install your own handler for the
SIGQUIT
signal. Your handler should just print the message"SIGQUIT handler called"
and allow the program to continue execution. Build and test your program.
It is also possible to handle signals in a shell script. The trap
command can
be used to install your own signal handler. It is good practice to write a
shell function to act as the handler. See the program below for examples of how
this is done.
#!/bin/bash
SIG_handler() {
echo "This is a signal handler for SIGQUIT and SIGTERM"
exit 0
}
SIGINT_handler() {
echo ""
echo "This is the SIGINT handler"
read -p "Press ENTER ..."
}
EXIT_handler() {
echo "This is the EXIT handler"
exit 0
}
trap SIG_handler SIGQUIT SIGTERM
trap SIGINT_handler SIGINT
trap EXIT_handler EXIT
while true
do
echo "Hello"
sleep 1
done
{: .code}
-
Create a shell script called
trap_demo
that contains the code above. Make the the script executable. Run it and observe its behaviour. -
Try pressing Control-C and Control-\ while your script is running. What happens? Why?
-
Run the script again. Discover the process id your running script. In another terminal, use the
kill
command to send theTERM
signal to the process. What happens? Why?
You have already seen many examples of the use of pipes. What you are aiming to
do here is to clarify your understanding of what the shell does when you enter
a command that contains the pipe symbol (|
). You will also look at a simple
example of using a named pipe.
-
Create a shell script that will print
Hello
30 times at 1 second intervals. Call this scripthello30
. Make the script executable. -
Execute the script
hello30
and pipe its output into the commandwc -l
. What behaviour do you expect from this pipeline? -
Discover the process id of your terminal. Use
echo $$
to do this. Run the pipeline again. -
While your pipeline is running, in another terminal window, use the
ps
command to identify which processes have been created to run the pipeline. Explain what you observe.
Read the section on named pipes in the lecture
-
Implement the example from the lecture and observe the behaviour.
-
Write a C program that writes "Hello named pipe world" to
stdout
. Write a second C program that reads a string fromstdin
. Open a terminal window. Create a named pipe. Run your first program with output redirected to the named pipe. Open a second terminal window. Run the second program with input redirected from the named pipe. Observe what happens. Use thels -l
command to examine the named pipe. -
Repeat the previous exercise but this time using shell scripts.