Reputation: 400
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:
Compile with -lpthread and include the "kbhit.h" above in the same directory
Press 'v' on keyboard to check values of 3 common variables residing in SHM
Press 'c' on keyboard to change the value of one of the variable (increment)
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
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