Conditional Variable Injection with System V Semaphores

I am trying to implement a conditional variable using System V semaphores. For simplicity, let's assume only one process can wait for a conditional variable at a time. The concept seems simple, so just take a look at my implementation of mutex coils and conditional variables, where:

  • Mutex is a binary semaphore with 1 free and 0 for blocking.
  • The conditional variable is a binary semaphore with 1 value meaning that someone is waiting for it and 0 otherwise.

Here he is:

//Sets value of 'num' semaphore from 'semid' semaphore set to be 'val'.
// Returns 0 on success and -1 on failure.
int set_value(int semid, int num, int val)
{
    union semun setval_semun;
    setval_semun.val = val;
    return semctl(semid, num, SETVAL, setval_semun);
}

// Returns the value of semaphore 'num' from 'semid' semaphore set
// or -1 on failure.
int get_value(int semid, int num)
{
    return semctl(semid, num, GETVAL);
}

// Locks 'mutex'. Returns 0 on success and 1 if 'mutex' is invalid.
// Returns -1 on other error.
int lock_mutex(int semid, int mutex)
{
    struct sembuf op;

    if (get_value(semid, mutex) > 1) return 1;

    op.sem_num  = mutex;
    op.sem_op   = -1;
    op.sem_flg  = 0;

    return semop(semid, &op, 1);
}

// Unlocks 'mutex'. Returns 0 on success and 1 ig the 'mutex' isn't locked.
// Returns -1 on different error.
int unlock_mutex(int semid, int mutex)
{
    struct sembuf op;

    if (get_value(semid, mutex) != 0) return 1;

    op.sem_num  = mutex;
    op.sem_op   = 1;
    op.sem_flg  = 0;

    return semop(semid, &op, 1);
}

// Waits on condition 'cond' and frees 'mutex'.
// Returns 0 on success and 1 if either some other process is waiting on 'cond'
// or 'mutex' is not locked. Returns -1 on other failure.
int wait_cond(int semid, int cond, int mutex)
{
    struct sembuf   ops[2];

    if (get_value(semid, cond) != 0 || get_value(semid, mutex) != 0)
        return 1;

    ops[0].sem_num  = cond;
    ops[0].sem_op   = 1;
    ops[0].sem_flg  = 0;

    semop(semid, ops, 1);

    ops[0].sem_num  = mutex;
    ops[0].sem_op   = 1;
    ops[0].sem_flg  = 0;

    ops[1].sem_num  = cond;
    ops[1].sem_op   = 0;
    ops[1].sem_flg  = 0;

    return semop(semid, ops, 2);
}

// Wakes up process waiting for 'cond'. Returns 0 on success. Returns 1 when
// no process is waiting for 'cond' and -1 on other error.
int wake_cond(int semid, int cond)
{
    struct sembuf   op;

    if (semctl(semid, cond, GETVAL) != 1)
        return 1;

    op.sem_num  = cond;
    op.sem_op   = -1;
    op.sem_flg  = 0;

    return semop(semid, &op, 1);
}

      

Now check out my minimal test program. It consists of two processes sharing two semaphores. One of them serves as a mutex and the other serves as a conditional variable. They both have the same flow:

  • Mutex lock.
  • Do something.
  • A wake-up process awaiting a condition variable.
  • Wait for the conditional variable using a mutex.

If I get it all right, they should be doing "something" one at a time.

int main(int argc, char** argv)
{
    key_t       key     = 0;
    pid_t       pid     = 0;
    int         semid   = 0;
    int const   mutex   = 0;
    int const   cond    = 1;
    int         repeat  = 5;
    char const* name    = NULL;
    union semun setval_semun;

    // Create unique key.
    key = get_unique_key(argv[0], 47); 
    if (key == -1)
        return 1;

    // Create and intialize semaphore set.
    semid = semget(key, 2, 0666 | IPC_CREAT);
    if (semid == -1)
        return 1;
    if (set_value(semid, mutex, 1) != 0 || set_value(semid, cond, 1) != 0)
    {
        semctl(semid, 0, IPC_RMID);
        return 1;
    }

    // Fork.
    pid = fork();
    if (fork() < 0)
        return 1;
    else if(pid > 0)
        name = "parent";
    else
        name = " child";

    while(repeat--)
    {
        printf("%s: (0), mutex: %d, cond: %d, repeat: %d\n", name,
            get_value(semid, mutex), get_value(semid, cond), repeat);

        if (lock_mutex(semid, mutex) != 0) return 1;

        sleep(1);

        printf("%s: (1), mutex: %d, cond: %d, repeat: %d\n", name,
            get_value(semid, mutex), get_value(semid, cond), repeat);

        if (wake_cond(semid, cond) != 0) return 1;

        printf("%s: (2), mutex: %d, cond: %d, repeat: %d\n", name,
            get_value(semid, mutex), get_value(semid, cond), repeat);

        if (repeat == 0)
        {
            if (unlock_mutex(semid, mutex) != 0) return 1;
        }
        else if (wait_cond(semid, cond, mutex) != 0) return 1;

        printf("%s: (3), mutex: %d, cond: %d, repeat: %d\n", name,
            get_value(semid, mutex), get_value(semid, cond), repeat);
    }

    //Wait for child and clear resources.
    if (pid > 0)
    {
        wait(NULL);
        semctl(semid, 0, IPC_RMID);
    }

    return 0;
}

      

And here is the result I get, which is insanely quirky:

parent: (0), mutex: 1, cond: 1, repeat: 4 
parent: (0), mutex: 0, cond: 1, repeat: 4
 child: (0), mutex: 0, cond: 1, repeat: 4
 child: (0), mutex: 0, cond: 1, repeat: 4
parent: (1), mutex: 0, cond: 1, repeat: 4
parent: (2), mutex: 0, cond: 0, repeat: 4
^C

      

Look especially at the first two lines. They tell you that the notification number (0) was printed twice in the same while loop with different values! Of course he ends up at a dead end, so I bought it.

Don't worry about the get_unique_key () function - it does what the name says.

Any help making this work or explaining the strange exit would be appreciated. Thank.

+3


source to share





All Articles