FAQ for Dining Philosophers Project

  • Unions and their use
  • What state does a philosopher start in?
  • Do we need mutual exclusion on local state changes?
  • How do I create shared memory?
  • Why do I get complaints about conversion from void*?
  • Semaphore initialization
  • Fork and attaching shared memory
  • Getting semaphores in groups
  • Using semaphores
  • What manual pages should I read?
  • Ftok doesn't work for me
  • Are we supposed to make sure the arguments are integers?
  • Unions and Their Use

    Due to a flaw in Solaris header files, you must declare the semun union yourself as follows:

            union semun {
                int val; 
                struct semid_ds *buf;
                ushort_t *array;
            };
    

    Note that there is no name after the right curly brace. That defines the type but doesn't create a variable. You can then use it as follows:

            union semun handyVariable;
    
    

    For SETVAL, the fourth argument to semctl must be a thing of type semun. Thus, you have to define semun (as above) and then write:

            union semun x;
            x.val = 1;
            semctl(mysem, 0, SETVAL, x);
    
    On a lot of machines, it would work just fine to write "1" in place of x. Not on Turing. On a SPARC, there's a huge difference between passing an integer and a union.

    What state does a philosopher start in?

    Thinking.

    Do we need mutual exclusion on local state changes?

    Q:Please correct me if I'm wrong, but it seems like, as a philosopher, we don't really need to enter mutual exclusion when changing our own state from thinking to hungry, because this transition really has nothing to do with putting down or snatching up a fork. So the two states are indistinguishable from the point of view of other philosophers looking for forks, and as long as the waiter enters mutual exclusion when reading the states, the output table should not be affected.

    A: It does matter, because if the philosopher process reaches the critical section first then the waiter may still access the state array while the philosopher is updating his state. You don't want the waiter trying to read the state array while one of the philosophers is modifying it.

    If the waiter uses a mutex, that does not prevent the philosopher from accessing data if the philosopher is not using a mutex. Semaphores only work if everyone is using them! That said, as long as the waiter only reads each entry in the state table once per print cycle, then the output should be fine as long as he uses a mutex to prevent the eating values from changing. The philosophers shouldn't need to use a mutex to change to the hungry state (although it wouldn't hurt)

    You "probably' could get away with not using mutexes to control changing your own state from thinking to hungry, but:

    How Do I Create Shared Memory?

    Before you can use shared memory, you must create it with shmget. Note the similarity to semget. Also, shmat will return a pointer to the attached memory, but that memory has no structure. You need to cast the return value to a pointer corresponding to your data structure. After that you can use the address returned as a pointer to a data structure.

    For example,

            struct Mesg
            {
                // ...
            };
    
            shmid = shmget(...)
    
            mesgptr = (Mesg *) shmat(shmid, (char *) 0, 0)
    
    Through mesgptr, the shared memory can now be used as a structure, i.e., the structure you created to be shared.

    Why do I get complaints about conversion from void*?

    Q: Is there a way to avoid the message ``warning: ANSI C++ forbids implicit conversion from 'void *' in argument passing'' when using the shared memory detach statement:

            shmdt((void*) stuff);
    

    A: The compiler is warning you that ANSI C++ won't convert your void* to the required char*. You should cast your stuff variable to a char* instead of a void* to get rid of the warning. The key thing to realize is that Solaris shared memory is getting you an array of bytes, while your program is using a structure.

    void* is a generic pointer (an address without a type associated with it). They use it in the documentation because they don't know what kind of pointer you'll be using. You can assign any pointer to void*, but you have to cast it when you assign the void* to another pointer.

    A void* is a pointer to a memory area. You can treat it as a buffer that is castable to whatever type you'd like.

    Semaphore Initialization

    In the OS book, Tanenbaum creates a semaphore for each philosopher, but does not initialize them. You should probably initialize.

    Fork and attaching shared memory

    Shared memory does not need to be reattached by a child process if it has already been attached by the parent before the fork.

    Getting Semaphores in Groups

    In Solaris, semaphores can be allocated as a group. for example, if you need 10 semaphores, you could ask for that number of semaphores to begin with. Thus, you might do something like the following:

            philsems = semget(key, 5, IPC_CREAT | 0644)
    

    The above statement creates 5 semaphores at once. You can manipulate a particular semaphore by setting the sem_num field in a sembuf argument to a semop call, or by setting the semnum argument in a semctl operation.

    DO NOT attempt to manipulate more than one semaphore at a time in a semop or semctl call. That will get you into trouble. Stick to the simple version first.

    Using Semaphores

    In Solaris, you must pass a semaphore structure for a semaphore operation request.

    semop is the call:

            semop(semid, address, numsems)
    
    where semid is your semaphore set id, as returned by semget (philsems in the code above). Address is the address of the structure that contains the operations for each semaphore to be changed. Numsems is the number of entries in that structure. Never use anything except 1 for numsems.

    The structure looks like this:

    Here is a more complete example:

            // create a semaphore set with 5 semaphores
            philsems = semget(semkey, 5, IPC_CREAT | 0644)
    
            // ...
    
            // Up semaphore number 2
            up(philsems, 2)
    
            // up()   -  V operation
            void up(int semset, int whichsem)
            {
                // structure to be sent
                struct sembuf sops;
                sops.sem_num = whichsem;
                sops.sem_op = 1;
                s.sem_flg = 0;
    
                semop(semset, &s, 1)
            }
    

    What Manual Pages Should I Read?

    The following pages are useful references for doing the dining philosphers project:

    Note: The number in parentheses is the "section number" which represents which section of the printed manual contains the page in question. When viewing manual pages on Turing, you sometimes need to use the "-s" option to specify the desired section:

        man -s 3c ftok
    
    On Linux, the notation is a bit simpler:
        man 3 ftok
    

    You can use the "whatis" command to find out what sections contain pages with a particular name. If there's only one page with the name (as should be true for all pages above except intro) then you can leave out the section number. For all of you Linux & BSD users out there, note that the System V and Solaris versions of man require "-s" when you specify the section, unlike the GNU and BSD versions.

    Also, if you wish to print a man page, you can use the "-t" flag. This will cause the output to go the printer as nicely formatted PostScript.

    Ftok Doesn't Work for Me

    Q: Ftok isn't working for me. It returns -1 regardless of how I set up the file and number inputs to it. How can I fix this? Thanks.

    A: You are probably calling ftok with the name of a file that doesn't exist.

    The general approach for this kind of problem is to look at the value of the global "errno". An easy way to do that is with strerror. For example:

            #include 
            #include 
             // ...
            id = ftok(...);
            if (id == -1) {
               cerr << "ftok failed: " << strerror(errno) << endl;
               exit(1);
            }
    
    If that doesn't make it clear, then look at the man page. The man page gives errors in terms of their abbreviations, such as ENOENT. To translate those into the English used by strerror, look in /usr/include/sys/errno.h. The comments in that file are precisely the messages generated by strerror. (Note: on Linux this file is named /usr/include/asm/errno.h.)

    Are We Supposed to Make Sure the Arguments Are Integers?

    Don't bother; it's too hard. Seriously invalid integers are usually converted to zero. (More accurately, they're converted to their integral prefix.) Just take the result of atoi and check that for validity as if it were an integer -- i.e., if 0 is illegal, reject it, otherwise accept it as if it were 0.

    Last modified February 24, 2002 by geoff@cs.hmc.edu