Processes and Signals Exercise:
End goal: A parent launches a child that prints "child reporting" 20 times slowly. The parent interrupts the child when a letter S is entered. The child sends back a 99 and the parent reports it.
Step 1: Create this
child program that has a main method that prints "child reporting" to
the screen 20 times, and sleeps 1 second between each print. Test it and
examine the return value.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main(void){
int c;
for (c = 0; c < 20; c++){
sleep(1);
printf("child reporting\n") ; }
return 50;
}
Compile and run and notice:
· After it runs, type echo $? and see that a 50 is returned.
· Run once more and interrupt it with ctrl c and then echo $? and see a different value instead.
Step 2: Create this parent program that does not yet
launch a child. It just prints that it is the parent, and then asks the user to
type S and press enter. Then it says you pressed S and stops. We will later use
this to launch the child.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void){
printf("Program has started at the parent\n");
printf ("I am the parent\n");
char entry;
printf ("Type S and enter to stop\n");
do {
int nread = read(0,&entry,1);
} while (entry != 'S');
printf ("You pressed S!\n");
printf ("Goodbye from parent \n");
return 0;
}
Compile and run and notice:
· Just ensure it handles the S option well by printing and ending.
Step 3: Execute in place
Make this parent execute the child after it says "the program has started."
Code to add to execute the child.
char *cmd[] = {"name of my child", NULL }; // replace with the name of your child program
execv("name of my child",cmd); // replace with the name of your child program
perror("child did not fork");
Explanation:
· to execute the child there are 2 steps:
o you first need to create an array that holds the parameters your child is expecting. Your child is expecting no parameters, so you only have to fill in the first value, the name of the program. First create an array of characters pointers that holds the name of the child program and a null. For example: char *cmd[] = {"name of my child", NULL };
o then you need to execute the execv command, giving it the name of the program and the parameter array. For example: execv("name of my child",cmd);
· To print an error if a problem occurred, use perror and pass it the label you want to print before the error. For example: perror("child did not fork");
Compile and run and notice:
· The parent never asked for the S. Write down why it did not ask for the S.
______________________________________________________________________________
· Now rename your child program so the parent will fail to launch the child. What happens:
_____________________________________________________________________________
· Finally, rename it back so the parent can launch the child before you continue.
Step 4: So that the
parent can continue while the child is running, fork into a new session before
launching the child.
Code to add to parent in order to fork. Do this right after printing "the program has started"
pid_t childpid = fork();
if (childpid == 0) // child
{
///Inside here should be your execute child code from step 3
}
else // parent
{
// inside here should be all the rest of your parent code that you had from step 2.
}
Explanation:
· pid_t is a typedef found in <sys/types.h>. It is really just an integer, but might be a different type of integer depending upon your system, so use pid_t instead of int.
· fork() created a new bash shell process, and copied the entire program into the new shell. It also started the new program at the same place.
· fork() returned the child id of the fork. It returns the new child's id to the parent and returns a 0 to the child. Each of the new processes continues on down its own code.
· The if statement: The only thing different about each process when it starts is the value of the fork result. That is why you test the returned fork value for 0 or not. Surround everything you want the child to do in the child branch and everything you want the parent to do in the other branch of the if statement.
Compile and run and notice:
· The parent now asks for S. Why does it ask for S now when it did not before?
______________________________________________________________________________
· Also notice that the child never ends. You have to press ctrl c to end it.
· Now rename your child program so the parent will fail to launch the child. What happens:
_____________________________________________________________________________
· Rename the child back and also open another panther window (right click on the top and select duplicate session). Then run your program and in the other window type ps -af | grep "Your username". See that you have 2 processes, one for the parent and one for the child. Did you see the 2 processes? ____ What is the child's process id. Use the command kill -9 processid to kill the child (if the pid was 20, use kill -9 20)
Step 5: Make the
parent wait for the exit.
Put some code between these 2 lines that will make the parent wait on the child to finish.
printf ("You pressed S!\n");
printf ("Goodbye from parent \n");
Place this code between those 2 lines above:
int
status
wait(&status);
if (WIFEXITED(status)) {
printf("child exited with %d\n", WEXITSTATUS(status));
}
Explanation:
· The wait command will wait for any child of this process to finish. It will receive the exit code and other information from the parent into this status integer.
· The returned integer from wait is not just the exit code, but also has other information packed into it.
· WIFEXITED(status) is a command that extracts from the status integer just the information about whether the child ended normally. It returns true or false.
· The WEXITSTATUS(status) command extracts just the exit code from the child.
Compile and run and notice:
· You no longer have to press Ctrl C at the end of the process. Why?
______________________________________________________________________________
Step
6. Make the parent kill the
child
In the parent, Before the int status line, add :
sleep (3);
// so you can first see the child run a little bit
kill (childpid,SIGUSR1);
Explanation:
· The parent has the id of the child process in its childpid variable. This sends the same signal you sent when you typed kill -9 on the shell.
· The child will end abnormally, so it will not properly send back an exit code.
Compile and run and notice:
· Did the child continue to print all its 10 child reporting messages? And did the exit code change?
______________________________________________________________________________
Step
7. Set the child up to expect
and handle the parent's interrupt.
This time you will change the child.
Add this function before the main function: (This stopped function can accept a signal event. It will print the child is killed and change the dead flag to indicate it should now die. It can do anything with the signal and does not have to stop running. That is just for this example. )
int dead = 0;
void stopped(int sig){
printf("child was killed\n");
dead = 1;
}
After the first main line, add this line to make the stopped function be the handler for the SIGUSR1 type of interrupt:
(void) signal(SIGUSR1, stopped);
Finally, make the loop pay attention to the dead variable value by inserting this inside your loop:
if (dead == 1){ return 99;}
In the parent, remove the sleep.
Explanation:
· The stopped function can accept a signal event because it accepts 1 integer.
· The signal command hooked up the SIGUSR1 signal to the stopped function. If a SIGUSR1 interrupt occurs, the stopped function will now handle it, and then return back to the line the program was executing when the interrupt occurred.
· The for loop now exits when it finds the dead variable is 1, which means an interrupt has occurred.
Compile and run and notice:
· Did the child continue to print all its 10 child reporting messages? Which exit code did you receive?
______________________________________________________________________________
Step 8: (Not for this
homework but for the project) Adjust this for your
project:
Make your child print one of your project files. In your menu, when option 2 is chosen, you will want it to fork and then execute the child.
You also have an interrupt option on your menu. That menu option can send the kill signal and wait for a return.