CMPS-3600 Lab-5 - Introduction to SysV IPC Semaphores

Goal: Learn to use SysV IPC semaphores

We will start the lab together in class.
Today's lab is in rvlab5.c

Things to do together on the big-screen...
  1. Compile and run rvlab5.c.
  2. Complete the fib() function.
  3. Run lab5 to see it fail.
  4. Identify the critical section or sections.
  5. Identify a race condition happening.
  6. Add comments where the semaphore activity belongs. (around the critical section)
  7. Look at the functions needed in sem_ctrl.c.	 
  8. Start your work on the lab.

Copy the example C programs, Makefile, and IPC cleanup script into your 3600/5 directory:

    $ cd
    $ cd /3600/5
    $ cp -p /home/fac/gordon/public_html/3600b/examples/5/* .  
Read the source of each program. Compile and execute as directed. When comfortable with how each program works, start coding. In particular, review semtest.c. This process forks a child. The parent and child asynchronously lock, pause, and unlock a semaphore. The critical section is the code between locking and unlocking. The semaphore behavior you need for lab-5 is demonstrated in this file.

Your job is to add synchronization to rvlab5.c using SysV semaphores. The semaphore code you need is in:

    examples/5/semtest.c     # to create and use semaphores 
    examples/5/sem_delete.c  # to remove semaphores 
    examples/5/sem_ctrl.c    # all semaphore functions shown 

Your instructor (gordon) recommends using sem_ctrl.c
to see all the SysV semaphore functions you will need for this lab.

The file you will start with, tvlab5.c, calls fork(2) and splits itself into a parent and a child process. The two processes communicate using a shared memory segment. If you do not understand how shared memory works, review week-4 examples. Note that this program generates an IPC key using IPC_PRIVATE:
   shmid = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | 0666);
This means that the kernel will generate a unique key for you. This method works as long as you do not need to generate the same key again in another process and is often the best way to generate a key.

You will need to create a semaphore set for this lab. You can use the flag IPC_PRIVATE as well or you can generate an IPC key using ftok and foo as we did in the last lab. This is up to you - but you should know how to do both methods.

The parent writes to and the child reads from the shared segment. The desired outcome is for the child to read the value *after* the parent writes it and then write that value to "log". There are two reasons why this does not occur in the existing code. The first is that the output could depend on how the kernel schedules the processes; i.e., it is possible for the child to be scheduled before the parent or vice versa. Behavior of this sort is called a race condition. The parent generally wins the race but this is not guaranteed (see fork(2) for details on this undefined behavior). The second problem is that the parent has to compute fib(n) and write that value to shared memory. If n is large enough it will force out-of-sync output:

    $ make lab5
    $ ./lab5 10
    child reads: 55   # 10 is OK
    $ ./lab5 20
    child reads: 0    # 20 is not OK 
The desired result is for the parent to compute fib(n), write the result to shared memory, the child reads the result and writes it to "log". But, if n is large enough, by the time the parent computes fib(n) the child has already read memory; i.e., the parent modifies the segment too late.

Race conditions make debugging difficult since the code actually works sometimes. To fix race conditions you must to add a semaphore to control order. You want the parent to grab the semaphore first. Meanwhile the child is blocked on the semaphore. After the parent writes fib(n) to memory the parent releases the semaphore so the child can grab it.

The semaphore enforces mutual exclusion and synchronization on shared memory. You must ensure that you do not get deadlock. Don't forget to add the new header for semaphores:

#include <sys/sem.h>
To test that the semaphore operations are working you should be able to pass any value for n at the cmdline and the child will grab the correct result.

A single semaphore is sufficent for this lab since all you need to do is enforce order one time: Parent writes, Child reads.

The parent actually does not need a semaphore, but you may create and use one if you like.

Sample steps:

  parent grabs the semaphore
  fork is done

  parent starts
  calculates fibonacci number
  write number to shared memory segment
  release child's semaphore

  child starts
  waits for a semaphore that is already set
  can't continue until the semaphore is available (released by the parent)
  grabs the semaphore
  read the shared memory to get fibonacci number

  child and parent finish
  parent does cleanup of IPC components

The child opens its log file in the usual fashion and calls close() before exiting (see readwrite.c in examples for code to open a file). Finally, after the parent waits for the child to die the parent should remove the semaphore. The code to remove a semphore is in sem_delete.c.

Important for testing:
Make the parent process sleep for about 1-second just after the fork.
Or generate a large fib number that takes about 1-second of processing.
Do not sleep too long, or your program will look like it's not working,
and Gordon's script will chug to a stop. :(

Remove any sleeps when you get the program working. :)

If you get into deadlock run gdb against your processes and see what line of code is executing in both parent and child:

  $ ps -ef | grep gordon
  $ gdb program 12344   # attach to the parent  
  $ gdb program 12345   # attach to the child 
This will not solve the deadlock but you will at least know where it is occurring.

Your code must work if any valid number is passed to fib().

sample runs...
$ ./lab5 1
$ cat log
child reads: 1

$ ./lab5 41
cat log
child reads: 165580141

$ strace -f -e trace=ipc ./lab5 1
shmget(IPC_PRIVATE, 4, IPC_CREAT|0666)  = 960266245
shmat(960266245, 0, 0)                  = ?
semget(0x2a02bf3c, 1, IPC_CREAT|0666)   = 401833994
semctl(401833994, 0, SETVAL, 0)         = 0
Process 31700 attached
[pid 31699] semop(401833994, {{0, 1, SEM_UNDO}}, 1 
[pid 31700] shmat( <unfinished ...>
[pid 31699] <... semop resumed> )       = 0
[pid 31700] <... shmat resumed> 960266245, 0, 0) = ?
Process 31699 suspended
[pid 31700] semop(401833994, {{0, -1, SEM_UNDO}}, 1) = 0
Process 31699 resumed
Process 31700 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
shmdt(0x7f39c08c5000)                   = 0
shmctl(960266245, IPC_RMID, 0)          = 0
semctl(401833994, 0, IPC_RMID, 0xffffffffffffffff) = 0
$ cat log
child reads: 1

$ cat log
child reads: 1

Files to be collected at end of lab...

Your source and Makefile:

  3600/5/rvlab5.c
  3600/5/Makefile
  3600/5/foo        <--- optional