Fan Jin
Fan Jin

Reputation: 400

Child Fails to Acquire Mutex Lock in Shared Memory Region

I'm trying to experiment with IPC and I've come up with a basic example involving Mutex. This is a trimmed down version to highlight the bug. The code without mutex works as expected and the child process can read value from SHM region modified by the parent. However, in this version, as soon as the parent releases the mutex lock, the child fails to acquire it and it is stuck in the block. I'm using "kbhit.h" for simplified keyboard interface. The code is also attached below. If anyone knows why this is happening, please share the knowledge.

Program Snippet:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/mman.h>
#include "kbhit.h"

typedef struct {
    pthread_cond_t cond;
    pthread_condattr_t condattr;
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutexattr;
    int status;
} IPCData;

int main(int argc, char const *argv[])
{
    IPCData* sharedMemory;
    pid_t childPID; //child process PID returned by fork
    char ch='x';

    sharedMemory = (IPCData*)mmap(NULL, sizeof(IPCData), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    if ((void*)sharedMemory == -1){
        printf("Memory mapping failed\n");
        return -1;
    }

    sharedMemory->status = 1; //Set shared variable value

    if (pthread_condattr_init(&(sharedMemory->condattr)) != 0 && pthread_condattr_setpshared(&(sharedMemory->condattr), PTHREAD_PROCESS_SHARED) != 0){
        printf("Error initializing and setting condition variable attribute values\n");
        return -1;
    }

    if (pthread_cond_init(&(sharedMemory->cond), &(sharedMemory->condattr)) !=0){
        printf("Error initializing condition variable with attribute\n");
        return -1;
    }

    if (pthread_mutexattr_init(&(sharedMemory->mutexattr)) != 0 && pthread_mutexattr_setpshared(&(sharedMemory->mutexattr), PTHREAD_PROCESS_SHARED) != 0){
        printf("Error initializing mutex attribute and set to process shared\n");
        return -1;
    }

    if (pthread_mutex_init(&(sharedMemory->mutex), &(sharedMemory->mutexattr)) !=0){
        printf("Error initializing mutex with attributes\n");
        return -1;
    }

    //forking
    switch (childPID = fork()){
        case -1:
            printf("Failure to fork new child process\n");
            return -1;
        case 0:
            //child process
            printf("Child Started\n");
            while (__sync_fetch_and_add(&(sharedMemory->status), 0) !=0){ //atomically vertify variable value to continue execution as long as value !=0
                printf("Child - Looping, Status: %d\n", sharedMemory->status);
                //child fails to acquire lock after parent release it in SHM region
                pthread_mutex_lock(&(sharedMemory->mutex)); //acquire lock
                printf("Child Mutex Acquired\n");
                if (__sync_fetch_and_add(&(sharedMemory->status), 0) > 1) {printf("Increment Detected. Status: %d\n", sharedMemory->status);}
                printf("Child Mutex Released\n");
                pthread_mutex_unlock(&(sharedMemory->mutex)); //release lock
            }
            printf("Child Exiting\n");
            _Exit(3);
            break;
        default:
            //parent process

            init_keyboard();

            while(ch != 'q') {
                if (kbhit()) {
                    do{
                        ch=readch();
                    } while(kbhit());
                    if (ch == 's'){
                        pthread_mutex_lock(&(sharedMemory->mutex));
                        printf("Parent Mutex Acquired\n");
                        printf("Subbing\n");
                        printf("Status: %d\n", __sync_fetch_and_sub(&(sharedMemory->status), 1));
                        printf("Parent Mutex Released\n");
                        pthread_mutex_unlock(&(sharedMemory->mutex));
                    } else if (ch == 'a'){
                        pthread_mutex_lock(&(sharedMemory->mutex));
                        printf("Parent Mutex Acquired\n");
                        printf("Adding\n");
                        printf("Status: %d\n", __sync_fetch_and_add(&(sharedMemory->status), 1));
                        printf("Parent Mutex Released\n");
                        pthread_mutex_unlock(&(sharedMemory->mutex));
                    } else if (ch == 'z'){
                        printf("Returning value(Adding 0)\n");
                        printf("Status: %d\n", __sync_fetch_and_add(&(sharedMemory->status), 0));
                    }
                }
            }
            printf("Parent Exiting\n");
            close_keyboard();
            break;
    }

    pthread_condattr_destroy(&(sharedMemory->condattr));
    pthread_cond_destroy(&(sharedMemory->cond));
    pthread_mutexattr_destroy(&(sharedMemory->mutexattr));
    pthread_mutex_destroy(&(sharedMemory->mutex));

    if (munmap(sharedMemory, sizeof(IPCData)) == -1){
        printf("Unmap shared memory region failed\n");
        return -1;
    }

    return 0;
}

kbhit.h

#ifndef KBHITh
#define KBHITh

#include <termios.h>
#include <unistd.h>   // for read()

void   init_keyboard(void);
void   close_keyboard(void);
int      kbhit(void);
int     readch(void); 

static struct termios initial_settings, new_settings;
static int peek_character = -1;

void init_keyboard()
{
    tcgetattr(0,&initial_settings);
    new_settings = initial_settings;
    new_settings.c_lflag &= ~ICANON;
    new_settings.c_lflag &= ~ECHO;
    new_settings.c_lflag &= ~ISIG;
    new_settings.c_cc[VMIN] = 1;
    new_settings.c_cc[VTIME] = 0;
    tcsetattr(0, TCSANOW, &new_settings);
}

void close_keyboard()
{
    tcsetattr(0, TCSANOW, &initial_settings);
}

int kbhit()
{
unsigned char ch;
int nread;

    if (peek_character != -1) return 1;
    new_settings.c_cc[VMIN]=0;
    tcsetattr(0, TCSANOW, &new_settings);
    nread = read(0,&ch,1);
    new_settings.c_cc[VMIN]=1;
    tcsetattr(0, TCSANOW, &new_settings);
    if(nread == 1) 
    {
        peek_character = ch;
        return 1;
    }
    return 0;
}

int readch()
{
char ch;

    if(peek_character != -1) 
    {
        ch = peek_character;
        peek_character = -1;
        return ch;
    }
    read(0,&ch,1);
    return ch;
}


#endif

After playing around with the code, unsure of exactly what happened, it functioned as expected. Below is an example of IPC using conditional variables and mutexes across processes; specifically, between the parent and the child.

How to play around with the code:

  1. Compile with -lpthread and include the "kbhit.h" above in the same directory

  2. Press 'v' on keyboard to check values of 3 common variables residing in SHM

  3. Press 'c' on keyboard to change the value of one of the variable (increment)

  4. Press 'v' again to verify

To exit:

1.Press 'k' to modify variable value

2.Press 'c' again as the child will be blocked on cond wait before exiting the loop

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/mman.h>
#include "kbhit.h"

typedef struct {
    pthread_cond_t cond;
    pthread_condattr_t condattr;
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutexattr;
    int status;
    int jobsignal;
    int count;
} IPCData;

int main(int argc, char const *argv[])
{
    IPCData* sharedMemory;
    pid_t childPID; //child process PID returned by fork
    char ch='x';

    sharedMemory = (IPCData*)mmap(NULL, sizeof(IPCData), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    if ((void*)sharedMemory == -1){
        printf("Memory mapping failed\n");
        return -1;
    }

    sharedMemory->status = 1; //set shared variable value 1 = execute 0 = terminate
    sharedMemory->jobsignal = 0; //turn off Signal status 1 = job exist 0 = no job
    sharedMemory->count = 0; //common count variable

    if (pthread_condattr_init(&(sharedMemory->condattr)) != 0 || pthread_condattr_setpshared(&(sharedMemory->condattr), PTHREAD_PROCESS_SHARED) != 0){
        printf("Error initializing and setting condition variable attribute values\n");
        return -1;
    }

    if (pthread_cond_init(&(sharedMemory->cond), &(sharedMemory->condattr)) !=0){
        printf("Error initializing condition variable with attribute\n");
        return -1;
    }

    if (pthread_mutexattr_init(&(sharedMemory->mutexattr)) != 0 || pthread_mutexattr_setpshared(&(sharedMemory->mutexattr), PTHREAD_PROCESS_SHARED) != 0){
        printf("Error initializing mutex attribute and set to process shared\n");
        return -1;
    }

    if (pthread_mutex_init(&(sharedMemory->mutex), &(sharedMemory->mutexattr)) !=0){
        printf("Error initializing mutex with attributes\n");
        return -1;
    }

    //forking
    switch (childPID = fork()){
        case -1:
            printf("Failure to fork new child process\n");
            return -1;
        case 0:
            //child process
            printf("Child Process Started\n");
            while (__sync_fetch_and_add(&(sharedMemory->status), 0) !=0 ){ //atomically vertify variable value to continue execution as long as value !=0
                if (pthread_mutex_lock(&(sharedMemory->mutex)) != 0) { //acquire lock
                    printf("Child Process Mutex Acquisition Failed\n");
                } else {
                    printf("Child Process Mutex Acquisition Success\n");
                    if (pthread_cond_wait(&(sharedMemory->cond), &(sharedMemory->mutex)) != 0){
                        printf("Child Process Condition Wait Failed\n");
                    } else {
                        printf("Child Process Condition Wait Success\n");
                    }
                    __sync_fetch_and_add(&(sharedMemory->count), 1); //add 1
                    if (pthread_mutex_unlock(&(sharedMemory->mutex)) != 0) { //release lock
                        printf("Child Process Mutex Released Failed\n");
                    } else {
                        printf("Child Process Mutex Release Success\n");
                    }
                }           
            }
            printf("Child Process Exiting\n");
            _Exit(3);
            break;
        default:
            //parent process
            printf("Parent Process Started\n");
            init_keyboard();

            while(ch != 'q') {
                if (kbhit()) {
                    do{
                        ch=readch();
                    } while(kbhit());
                    if (ch == 'c'){
                        if (pthread_mutex_lock(&(sharedMemory->mutex)) != 0) {
                            printf("Parent Process Mutex Acquisition Failed\n");
                        } else {
                            printf("Parent Process Mutex Acquisition Success\n");
                            if (pthread_cond_signal(&(sharedMemory->cond)) != 0){
                                printf("Parent Process Signal Failed\n");
                            } else {
                                printf("Parent Process Signal Success\n");
                            }
                            if (pthread_mutex_unlock(&(sharedMemory->mutex)) != 0){
                                printf("Parent Process Mutex Release Failed\n");
                            } else {
                                printf("Parent Process Mutex Release Success\n");
                            }
                        }
                    } else if (ch == 'v'){
                        printf("Status: %d\n", __sync_fetch_and_add(&(sharedMemory->status), 0));
                        printf("JobSignal: %d\n", __sync_fetch_and_add(&(sharedMemory->jobsignal), 0));
                        printf("Count: %d\n", __sync_fetch_and_add(&(sharedMemory->count), 0));
                    } else if (ch == 'k'){
                        printf("Terminating Child Process\n");
                        __sync_fetch_and_sub(&(sharedMemory->status), 1);
                    }
                }
            }
            printf("Parent Process Exiting\n");
            close_keyboard();
            break;
    }

    pthread_condattr_destroy(&(sharedMemory->condattr));
    pthread_cond_destroy(&(sharedMemory->cond));
    pthread_mutexattr_destroy(&(sharedMemory->mutexattr));
    pthread_mutex_destroy(&(sharedMemory->mutex));

    if (munmap(sharedMemory, sizeof(IPCData)) == -1){
        printf("Unmap shared memory region failed\n");
        return -1;
    }

    return 0;
}

Upvotes: 1

Views: 1317

Answers (1)

youdontneedtothankme
youdontneedtothankme

Reputation: 672

You can't use a pthread_mutex to syncronize different processes, only threads of the same process. If you can get away with it, use pipes for ipc, then you don't need to syncronize access to different datastructures.

Edit:

Just googled around, seems you sometimes can when setting the right attributes, sorry

Upvotes: 1

Related Questions