• Home
  • Blog
  • Mutex Locks or Semaphores in Shared Memory

Mutex Locks or Semaphores in Shared Memory


Mutex Locks or Semaphores in Shared Memory
Last one of the semester. Let’s combine what we did in program #4 and program #5 and show, and then prevent, a race condition between processes rather than threads. Start with your code from program #5 but instead of simply putting an integer in shared memory, use a structure like the one that you used in program #4 to represent a 2-dimensional Cartesian point having two integer coordinates called x and y. Instead of communicating between parent and child by simply updating the integer with a user supplied value (as you did in program #5), update the racePoint coordinates as we did in program #4 (no user interaction necessary), which will create critical sections and then,as in program #4, use a stupidly placed sleep(1) call to simulate a random preemption causing a genuine race condition. Here, of course, we’ll have a race condition between two processes (over a shared resource in shared memory) rather than between two threads of the same process. Finally, just as in program #4, add entry and exit sections with a synchronization mechanism (semaphore, binary semaphore or mutex lock, whatever worked for you previously) to protect your critical sections and prevent the race condition.
Couple of notes:
A. Since both the parent and the child process obviously (I sure hope it’s obvious by now) need to use the same semaphore or mutex lock, it too will have to be in shared memory, no? Now I suppose you could just create a second shared memory segment for that; but that seems to me pretty inefficient. Instead, since a structure in C can contain other structures, why not put both the racePoint and the semaphore/mutex that will protect it inside a single structure and then create the shared memory to hold the “outer” structure containing the two “inner” structures your processes need to share access to; that way you don’t need an extra shmget, shmat, etc.
B. Remember that semaphores, mutex locks, and such like have to be initialized before they can be used. In program #4, where the semaphore or mutex lock was simply declared as a global variable, that initialization could be done in the declaration itself and many of you did. I myself, for example, used the following line to declare and initialize a POSIX mutex lock named demoLock:
pthread_mutex_t demoLock = PTHREAD_MUTEX_INITIALIZER;
where PTHREAD_MUTEX_INITIALIZER is #defined in one of the header files as as a set of the appropriate constants inside squiggly brackets, e.g., { …, …} That wouldn’t work here, since the the memory for the synchronization structure is being obtained dynamically (via the shmget) rather than by a simple compile-time declaration, as in my example just above. So here in this program, you’ll need to use some additional system service calls from the pthread library to initialize your mutex lock correctly.
Conceptually there’s not much new here. Aside from the half a dozen extra lines or so to get the mutx lock initialized properly, there’s not much new code to be written; you’re just mixing and matching stuff from programs #4 and #5 with a couple of fairly minor changes. But the changes involve handling pointers to components of structures inside dynamically allocated structures, which requires careful coding, so I think this program, as do most interesting programs, really begs for “build-a-little, test-a-little”. You’re obviously not required to proceed via the same sequence of stepwise refinements that I used (from the requirements engineering standpoint, that’s not a testable requirement 😉 but here’s roughly how I did it (compiling and executing after each step):
1. Start from program #5 (the process program, not the thread one) but change the integer in shared memory to the racePoint structure from program #4 and do the spinning/communicating on just the x component, ignoring the y component completely here in the beginning (we’ll use it later though). This step just ensures our code can create and properly access a dynamically allocated C structure (not just an integer) located in shared memory.

a. In program #4, the structure, racePoint, was simply a global variable of an anonymous structured type:
struct { int x, y; } racePoint={0,0};
Here, of course, it has to be placed in shared memory so you’ll need to give the structure definition a name, vis struct point { int x, y; }; so that you can refer to sizeof(struct point) when requesting your shared memory from shmget.

b. Note that I removed the variable declaration for racePoint. Here, we don’t want to allocate storage for racePoint when we define struct point; we just want to define the struct point data type so that storage for the one we need can be dynamically allocated later, during execution, with a shmget call. If you also declare a variable at this point in your code (the compiler wouldn’t care, now would it?), you might easily get confused later and refer to it (the totally unnecessary local variable) rather than the dynamically created one in shared memory, with the result that you’ll manipulate the wrong variable and your code won’t work as intended.

2. After attaching to the shared memory as we did in program #5, set both the x and y coordinates of the shared point to 0 Note that we’ll have to refer to the coordinates by pointer rather than by the name of the structure; e.g., racePointPtr->x, rather than racePoint.x, where racePointPtr contains the address we get when we do the shmat.

3. fork() a child process.

a. In the code executed by the parent process after the fork():
i. Take out the code from program #4 that requests user input and then spins until the child resets the integer.

ii. Replace it with the code for a critical section by copying the 3 lines from the critical section of the main thread of program #4 (suitably modified, as per item #2, above):

1. First, set the x coordinate to 1.

2. Then make a call to sleep(1) to simulate random preemption of the process.

3. Then set the y coordinate to 1 (modifying the copied code to refer to both x and y by pointer).

iii. Last thing in the parent code (after the critical section), print out the values of x and y so we can see if we got the race condition.

b. In the child code, insert a line that spins while the x coordinate is 0 — so the child doesn’t try to enter its critical section too soon, before the parent process even executes after the fork (At this point, we’re trying to force a race condition and if the child gets completely through its critical sectrion before the parent even enters its, we won’t get one.) Next (still in the child code) put in the two line critical section that sets the x and y coordinates to 2.

At this point, when you compile and execute your code you should see the simulated race condition: x will end up 2 while y will be 1.

4. Put the definition of the race point structure inside an outer structure of some sort, called something stunningly original like sharedData or whatever name your sense of programming style deems appropriate:
struct sharedData
struct point
int x, y;
} racePoint;
This outer structure is where, in the next step, we’ll also place the mutex mechanism we’ll need, but for now (build-a-little, test-a-little) just make the necessary modifications to show that your code can still access x and y properly now that they’re inside an inner structure that’s inside an outer structure — and remember to alter your shmget to request enough storage for the outer structure, e.g., sizeof(struct sharedData), or whatever you name your outer structure

 Helpful hint: If you saved the address returned by the shmat in a variable named, say, sharedMemoryPtr, you’d now need to refer to the x coordinate as (sharedMemoryPtr->racePoint).x sharedMemoryPtr points to the outer structure; sharedMemoryPtr->racePoint designates the component of the outer structure named racePoint, which is itself a structure. So (sharedMemoryPtr->racePoint).x refers to the x component of the inner structure.

 Irrelevant aside on programming style: (sharedMemoryPtr->racePoint).x can be written without the parentheses as sharedMemoryPtr->racePoint.x and the compiler will do the right thing, but I think that’s poor style. It forces you or your readers to think explicitly about the precedence or association order of the operators involved. Better to use parentheses and make your intentions clear.

 Note that I had to put the name racePoint back in when I defined struct point, but here it’s not the name of a variable being declared (as it was in program #4) but the name of a structured component in the definition of the structured sharedData type. Components or fields have to have names, no?

Anyway, compile and execute again; you should still see the race condition. Save this version of code somewhere; it’s one of two versions you’ll turn in for this assignment.

5. Now put your definition of your synchronization structure inside the definition of the outer structure and add your call to initialize it somewhere before the fork(). I found it helpful to save and check the value returned by the initialization call (see my examples on using perror or strerror) since it’s all too easy at this point to write code that the compiler accepts but still have the OS nonetheless reject the system service call since it didn’t like the address you sent it. You can waste a lot of time trying to figure out why your program doesn’t work if you assume that just because the compiler buys off on the data types you use as arguments to system service calls and your program executes without blowing up that therefore the OS is actually doing what you are asking it to. That’s why system service calls return success/failure values and what perror is for. Compile and execute again to make sure you haven’t screwed anything up; although you still won’t have fixed the race condition yet.

 Programming hint: While it’s never a good idea to ignore warnings from the compiler (as opposed to errors, which, of course, can’t be ignored), when working with complex pointer accesses it is particularly vital to not ignore warnings. If the compiler thinks you’re pointing to the wrong type of thing, even though that’s only a warning, not an error, it is very unlikely that your system service calls will work correctly even though they may not notice an error. When you tell a system service call to initialize something, for example, all it knows is the address of the

About the Author

Follow me

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}