0

Hey I was wondering if someone could help me figure out why my implementation of Peterson's Algo isn't working.

Currently my output is:

From Process total = [number > 100,000]
Child with ID -1 has just exited
Child with ID [Child ID] has just exited
     End of Program
From Process 2 total = [number > 200,000]
Child with ID -1 has just exited

My output should be along the lines of:

From Process 1: total = 60234. 
From Process 2: total = 100000.
Child with ID 2412 has just exited. 
Child with ID 2411 has just exited.
     End of  Program.

For some reason one of my child processes has an ID of -1 which means there is some sort of error (I think). I have no idea what's going on with that. I'm still confused how this algorithm ever works because if turn never changes, how does the process ever get access to the critical section?

My code is as follows:

#define TRUE 1
#define FALSE 0


typedef struct
{
  int value;
  int flag[2] = {FALSE, FALSE};
  int turn = 0;
} shared_mem;


shared_mem *total;



       void process1 ()
{
  int k = 0;

  while (k < 100000)
    {
      total->flag[0] = TRUE;
      total->turn = 1;

      //prevent P1 from access CS
      while(total->flag[1] == TRUE && total->turn == 1)
      {
        int k = 0;
        if(k = 0)
        {
            total->p2Block = total->p2Block + 1;
        }
        k++;
      };

      k++;
      total->value = total->value + 1;

      total->flag[0] = FALSE;
    }

  printf("Process 2 interrupted %d times in critical section by Process 1.\n", total->p2Block);
  printf ("From process1 total = %d\n", total->value);
}

    void process2 ()
{
  int k = 0;

  while (k < 200000)
    {
      total->flag[1] = TRUE;
      total->turn = 0;
      int critical;
      //prevent P2 from accessing CS
      while(total->flag[0] == TRUE && total->turn == 0)
      {
            int k = 0;
            if(k = 0)
            {
                total->p2Block = total->p1Block + 1;
            }
        k++;
      };

      k++;
      total->value = total->value + 1;
      critical++;

      total->flag[1] = FALSE;
    }

  printf("Process 1 interrupted %d times in critical section by Process 2.\n", total->p1Block);
  printf("From process2 total = %d\n", total->value);
}



main()
{
  int   shmid;
  int   pid1;
  int   pid2;
  int   ID;
  int   status;
  int   waitID1, waitID2;

  char *shmadd;
  shmadd = (char *) 0;

/* Create and connect to a shared memory segmentt*/

  if ((shmid = shmget (SHMKEY, sizeof(int), IPC_CREAT | 0666)) < 0)
    {
      perror ("shmget");
      exit (1);
    }


 if ((total = (shared_mem *) shmat (shmid, shmadd, 0)) == (shared_mem *) -1)
    {
      perror ("shmat");
      exit (0);
    }


  total->value = 0;
  total->flag[0] = FALSE;
  total->flag[1] = FALSE;
  total->turn = 0;
  total->p1Block = 0;
  total->p2Block = 0;

  if ((pid1 = fork()) == 0)
    process1();

  if ((pid1 != 0) && (pid2 = fork()) == 0)
    process2();

  do{
    waitID1 = waitpid(pid1, &status, 0);
    waitID2 = waitpid(pid2, &status, 0);
    if(WIFEXITED(status) && waitID1 != -1)
    {
      printf("Child with ID %d has just exited\n", waitID1);
    }
    if(WIFEXITED(status) && waitID2 != -1)
    {
      printf("Child with ID %d has just exited\n", waitID2);
    }
  }while (!WIFEXITED(status) && !WIFSIGNALED(status));  

  if ((pid1 != 0) && (pid2 != 0))
    {

      if ((shmctl (shmid, IPC_RMID, (struct shmid_ds *) 0)) == -1)
      {
        perror ("shmctl");
        exit (-1);
      }
      printf ("\t\t  End of Program.\n");
    }
} 
3
  • 2
    This is a userspace program, and not kernel code. The linux-kernel tag is inappropriate. Try using linux instead. Commented Feb 20, 2016 at 19:02
  • When -1 is returned by fork, the errno is be set with the code (see manpage of fork at Return value). You can get the error message associated to this errno with strerror. Commented Feb 20, 2016 at 19:28
  • Hey, I actually need to synchronize 2 child processes using Peterson algorithm but it seems very tough to me as I am new to OS concepts. If you got the working code, I would request you to kindly update your question with the answer. Thanks. Commented Jun 13, 2022 at 18:10

2 Answers 2

1

The fork() and wait() functions return -1 when they fail. You do not test for that.

Moreover, all processes involved in your program call wait(). That call will certainly fail (returning -1) in at least one process, as regardless of the success or failure of the fork()'s, there will be one process without any child processes.

Your program has more serious problems, however. It appears to assume that ordinary global variables flag and turn will be shared between processes, but since I see you using shmget(), I presume you know that that won't actually happen.

And even if you move flag and turn into your shared memory segment, you still have a synchronization problem: you cannot rely on one process to see the other's modifications to shared memory in a timely manner -- or, in fact, ever -- without employing appropriate synchronization. The synchronization aid that naturally goes with SysV-style shared memory segments is the SysV-style semaphore.

And when you do implement synchronization, it should naturally fall out that you no longer need to use busy loops to make each process wait on the other.

Sign up to request clarification or add additional context in comments.

3 Comments

Peterson's Algorithm is frequently taught to introduce the student to synchronization. It uses only shared memory and programming logic the student should already know. It is just an expedient means to gain some understanding before later introducing more details such as busy waiting vs. blocking, hardware support for "compare & swap" instructions, etc. It's (usually) not intended as an ideal solution, but as a stepping stone in the learning process. By the time they learn the "real" methods, Peterson's Algorithm will seem silly.
It's for school, so it's what I have to use.
@e0k, that would all be well and good if it could be relied upon to work. For that, Peterson's depends on modifications to the turn and flag variables propagating immediately and atomically between processes, and you cannot safely rely on that to happen in this C implementation. Teaching the implementation of Peterson's without teaching the assumptions on which it relies seems to entirely miss the point.
1

Watch what you share

Your global declarations

int flag[2] = {FALSE, FALSE};
int turn = 0;

are not shared memory. Your total is shared memory because you share it, but the flag and turn are not. Each process has its own copy of the flag and turn and modify only their own copy of it. Since the synchronization mechanism you're trying to implement (Peterson's Algorithm) relies on both processes modifying the same flag and turn, it doesn't work. When one process changes the flag, the other doesn't know about it.

(The above has since been addressed in an edit to the question.)

Sharing and synchronizing between processes is typically more complicated than sharing and synchronizing between threads. The two are different things for different purposes. If it is possible, I'd consider using threads instead of processes. This way, sharing memory is trivial. This problem seems to be an educational exercise, and schools typically teach processes before threads.

Watch how you share it

You will need to initialize turn and flag after creating the shared memory, such as where you initialize total->value = 0; and not in a typedef declaration.

Go through your notes about how to set up shared memory and read through the relevant man pages.

In smget() you have a size of sizeof(int), which is not enough for the whole struct. Personally, I'm against hard coding types in sizeof() operators. I suggest using something like sizeof(*shared_mem) and let the compiler figure out what the type and size should be. It's also easier if you change the declaration for some reason, because you don't have to sift through your code looking for sizeof() operators.

Restructure the fork() calls and test the return values

Library functions return values for a reason. Ignoring what they say is like ignoring alarm bells. Sometimes such an alarm might help you figure out what is wrong with your program.

There are three return states to check from a fork() (check all of them):

  • -1, an error occurred
  • 0, in child process
  • >0, in parent process (with child's PID)

Also, notice that after completing process1() and process2(), the children return to main() and fall through the subsequent instructions, even trying to wait for children they don't have. This is probably not what you want.

Check the return value from wait()

The -1 printed is the return value from wait(). This means that wait() encountered an error. The likely cause for this is that the calling process has no child to wait for.

From POSIX.1-2008/IEEE Std 1003.1-2008 (2013 Ed.) for the function wait():

pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc, int options);

. . .

If wait() or waitpid() return because the status of a child process is available, these functions shall return a value equal to the process ID of the child process. In this case, if the value of the argument stat_loc is not a null pointer, information shall be stored in the location pointed to by stat_loc.

I am interpreting this to mean that if wait() did not "return because the status of a child process is available", then we can not rely on the value written to stat_loc.

In other words, you should have checked the return value from wait() before using your status. A garbage value (status is uninitialized) could possibly evaluate WIFEXITED(status) to non-zero (true). If wait() returns an error, you shouldn't rely on the value of status. You can also use errno to check what went wrong (be sure to set errno = 0 before the call to wait()). See documentation for details.

Some comments about pointers

The lines

char *shmadd;
shmadd = (char *) 0;

are equivalent to char *shmadd = NULL;. You don't have to type cast it. (Do you even need the local variable shmadd? Can you just use NULL instead?)

Similarly, in

total = (shared_mem *) shmat (...)

you don't have to type cast the return value because it is a void *. (C++ or C# might make you do this, but not in C.)

Watch the variables

You have a second variable declaration of a variable named k that shadows the first one. Here is the declaration of the second one:

while(total->flag[1] == TRUE && total->turn == 1)
{
  int k = 0;
  if(k = 0)
  {
    total->p2Block = total->p2Block + 1;
  }
  k++;
};

This int k shadows the earlier k. Any reference to k in this block will use this one instead of the earlier one. There are several other problems with this code:

  • if (k = 0) is an assignment of 0 to k, not a comparison. Since this evaluates to 0 (interpreted as false), the block will never be executed. The if has no effect.
  • If you change this to if (k == 0) (a comparison) it still makes no sense since it will always be true. You just assigned 0 to k then check to see if k is 0. Why would it change?
  • The k++ also has no logical meaning. This k was declared at the beginning of the block, and its scope is just this block. The next iteration of the while loop pushes a new k and initializes it to 0. So there is no logical effect of incrementing it.
  • Why write total->p2Block = total->p2Block + 1 instead of just total->p2Block++?

This may explain why your counting is not behaving as intended.

10 Comments

I tried adding flag and turn to the shared_mem struct, however when I try to access it via total->flag[x] or total->turn I get an error: 'shared_mem' has no member named 'flag'. Which makes no sense to me because it's clearly a part of the struct now.
It's hard to determine why it would complain like that without seeing the actual code. The basic idea remains: you must somehow share flag and turn to make it work. As it is written in your question, they are not shared and each process only modifies and reads its own copy. Without communication, there is no synchronization.
I've updated the code. It's been a few years since I've used C. I usually use C++ or C# now. I'm probably doing something wrong that I can't remember.
whoops, that was accidentally left in here. It wasn't in my code when I tried to compile it. I'll update it again.
@JakeWilliams, Peterson's relies on some assumptions about propagation of changes to its shared variables that neither C nor POSIX guarantees to be justified. You may have better luck in practice if you compile with all optimizations disabled, but you cannot really implement Peterson's correctly in C, because C provides no way to ensure that the algorithm's assumptions are satisfied.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.