Why does my solution to The Cigarette Smoker's Problem not terminate? - c

I wrote a solution to The Cigarette Smoker's Problem that does not terminate.
A full description of the problem and pseudo-code for the solution can be found here. Briefly, the task is to synchronize four processes using semaphores.
I used the pseudo-code as a guide for writing my solution. The following is my C implementation of the solution:
#include "sem.h" //Allows the use of semget(), sem_create(), and the P() and V() functions.
#include <stdlib.h> //Allows the use of srand() and rand().
#include <time.h> //Allows the use of time().
#include <stdbool.h> //Allows the use of the boolean variable that terminates smoker processes
#include <unistd.h> //Allows the use of fork().
#include <sys/wait.h> //Allows the use of waitpid().
int main()
{
/*Create and initialize semaphores*/
int smkr_matches = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // Smoker that has matches
int smkr_tobacco = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // Smoker that has tobacco
int smkr_paper = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // Smoker that has paper
int agent = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
int lock = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // The mutex variable
/*According to http://www.cs.umd.edu/~hollings/cs412/s96/synch/smokers.html,
all semaphores are initialized to zero except "lock", which is initialized to one.
*/
sem_create(smkr_matches, 0);
sem_create(smkr_tobacco, 0);
sem_create(smkr_paper, 0);
sem_create(agent, 0);
sem_create(lock, 1);
/*Seed the random number generator to generate random choice of ingredients.*/
srand(time(NULL));
// Delcare a boolean variable to terminate the smoker processes.
bool done = false;
//Declare variables to store PID after wait() calls and status.
int pid = 0;
int status = 0;
/*Declare variables to store PIDs.*/
int smkr_mat_PID = 0;
int smkr_tob_PID = 0;
int smkr_pap_PID = 0;
int agent_PID = 0;
/*Implement the agent process*/
agent_PID = fork();
if (agent_PID == -1)
{ // Forking the agent process failed
perror("Forking the agent process");
exit(EXIT_FAILURE);
}
else if (agent_PID == 0)
{
printf("AGENT process created (PID = %d).\n", getpid());
int rand_num = 0;
for (int i = 0; i < 4; i++)
{ // Agent will place items on table ten times.
P(lock);
// Randomly choose a pair of ingredients.
rand_num = (rand() % 3) + 1;
// Place the ingredients on the table and wake the appropriate smoker.
if (rand_num == 1)
{
printf("AGENT has placed TOBACCO and PAPER on the table.\n");
printf("AGENT wakes smoker with MATCHES.\n");
V(smkr_matches);
}
else if (rand_num == 2)
{
printf("AGENT has placed TOBACCO and MATCHES on the table.\n");
printf("AGENT wakes smoker with PAPER.\n");
V(smkr_paper);
}
else if (rand_num == 3)
{
printf("AGENT places MATCHES and PAPER on the table.\n");
printf("AGENT wakes smoker with TOBACCO.\n");
V(smkr_tobacco);
}
// Allow the smoker to work
V(lock);
printf("AGENT will now sleep.\n");
P(agent);
}
done = true;
}
/*Implement the smoker with tobacco*/
else
{
smkr_tob_PID = fork();
if (smkr_tob_PID == -1)
{
perror("Forking smoker with tobacco");
exit(EXIT_FAILURE);
}
else if (smkr_tob_PID == 0)
{
printf("SMOKER WITH TOBACCO created. (PID = %d)\n", getpid());
while(done == false)
{
P(smkr_tobacco);
P(lock);
printf("SMOKER WITH TOBACCO picks up matches and paper from the table.\n");
V(agent);
V(lock);
printf("SMOKER WITH TOBACCO begins smoking.\n");
}
}
/*Implement the smoker with paper*/
else
{
smkr_pap_PID = fork();
if (smkr_pap_PID == -1)
{
perror("Forking smoker with paper");
exit(EXIT_FAILURE);
}
else if (smkr_pap_PID == 0)
{
printf("SMOKER WITH PAPER created (PID = %d).\n", getpid());
while(done == false)
{
P(smkr_paper);
P(lock);
printf("SMOKER WITH PAPER picks up tobacoo and matches from the table.\n");
V(agent);
V(lock);
printf("SMOKER WITH PAPER begins smoking.\n");
}
}
/*Implement the smoker with matches*/
else
{
smkr_mat_PID = fork();
if (smkr_mat_PID == -1)
{
perror("Forking smoker with matches");
exit(EXIT_FAILURE);
}
else if (smkr_mat_PID == 0)
{
printf("SMOKER WITH MATCHES created (PID = %d).\n", getpid());
while(done == false)
{
P(smkr_matches);
P(lock);
printf("SMOKER WITH MATCHES picks up tobacco and paper from the table.\n");
V(agent);
V(lock);
printf("SMOKER WITH MATCHES begins smoking.\n");
}
}
else
{
pid = wait(&status);
printf("PID = %d terminated with status = %d\n.", pid, status);
pid = wait(&status);
printf("PID = %d terminated with status = %d\n.", pid, status);
pid = wait(&status);
printf("PID = %d terminated with status = %d\n.", pid, status);
pid = wait(&status);
printf("PID = %d terminated with status = %d\n.", pid, status);
}
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
I ran my solution through a debugger and noticed that the bug occurs when the second wait() call towards the end of the program is made. In executing that wait() call, it seems that my program runs forever. I suspected that an infinite loop was occurring in one of the smoker processes. After reviewing my code further, I noticed that, although the agent process seems to terminate normally in the first wait() call, the boolean variable done does not change its value to true as intended. This could explain why there seems to be an infinite loop.
In attempting to understand why the bug was happening, I tried several changes to the code, but all of them failed to correct the bug. The following is a list of what I tried and the rationale for trying it:
1. Changing the initial value of done from false to true.
My solution initializes done to false. I reasoned that, during execution, if one of the smoker processes started first, perhaps their while-loops would run forever. However, I realized the while-loops could not run forever because the P() operation would block the smoker process. Nevertheless, I changed the initial value of done to true to confirm my reasoning.
2. Having the agent process acquire the lock before assigning true to done.
As mentioned earlier, I noticed that the agent process terminated normally without changing the value of done to true. In an attempt to correct that, I changed the solution to have the agent acquire the lock one more time before changing the value of done and then releasing the lock again to allow the smoker processes to do work.
3. In the implementations of the smoker processes, releasing the lock before incrementing the agent semaphore.
In the while-loops of the smoker processes, after a smoker picks up the ingredients from the table, the agent semaphore is incremented before the lock is released. My instructor suggested that this order of operations could cause a deadlock and advised that I swap the order, releasing the lock first before incrementing the agent semaphore.
4. In the implementation of the agent process, releasing the lock before incrementing the semaphore of a smoker process.
Similar to what happens in the while-loop of the smoker processes, after the agent process randomly selects two ingredients and places them on the table, the agent increments the semaphore of the corresponding smoker process and then releases the lock. I wanted to see what would happen if I applied my instructor's suggestion to the agent process, so I changed the code so that the agent releases the lock before incrementing the semaphore of the smoker process.
The following additional information may be helpful in answering this problem:
The sem.h header file included at the beginning of the code works fine. However, if you would like to see it, here it is:
/************************************************************************/
/* Operating Systems - Fall
/* Originally developed at KSU by a teaching assistant */
/* */
/* Description : The following library is a collection of */
/* routines for using binary semaphores in C: */
/* 1. seminit - to initialize a semaphore. */
/* 2. P - to perform a P(S) (wait) operation. */
/* 3. V - to perform a V(S) (signal) operation. */
/* 4. semkill - to remove a semaphore */
/* */
/* These routines call system routines: */
/* 1. semget - to get a semaphore */
/* 2. semctl - semaphore control operations */
/* 3. semop - semaphore operations */
/* */
/* Complete manual entries can be obtained by: */
/* man semctl | col -b | lpr */
/************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union arg{ /* This structure is used to call semctl */
int val;
struct semid_ds *buf;
char *array;
};
/*
* Create semaphore based on "key" parameter to "initval"
*/
void sem_create(int semid, int initval)
{
int semval;
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}s;
s.val=initval;
if((semval=semctl(semid,0,SETVAL,s))<0)
printf("\n Erroe in executing semctl");
}
/*
* Remove semaphore with semaphore id (sid) from the kernel
*/
static void semkill(int sid)
{
if (semctl(sid,0,IPC_RMID,0) == -1)
perror("semctl (kill)");
printf("Semaphore with value of sid = %d is killed \n",sid);
}
/*
* Perform the designated "op" operation on the semaphore. If "op" is -1,
* then this implements the "P" operation; it decrements the value of the
* semaphore (semval) if it was >0,
* and blocks the caller if it was zero (semval==0)
* If "op" is 1, then this is simply added to current value of
* the semaphore ("V" operation).
*/
static void semcall(int sid, int op)
{
struct sembuf sb;
sb.sem_num = 0; /* semaphore number within sid */
sb.sem_op = op;
sb.sem_flg = 0; /* blocking call */
if (semop(sid, &sb, 1) == -1)
perror("semop");
}
/*
* P operation on semaphore "sid". Should be called upon entry to critical
* region.
*/
static void P(int sid) {
semcall(sid, -1);
}
/*
* V operation on semaphore "sid". Should be called upon exit from critical
* region.
*/
static void V(int sid) {
semcall(sid, 1);
}
I wrote my code in Microsoft Visual Studio using Ubuntu within a Virtual Box VM.
Finally, my question is: why does my solution not terminate?
Thank you for your time.

Related

How do I block other processes, but still be able to use a Function while other processes are blocked?

I have a multiprocess and I want to block other processes with Semaphores to stop the acces for the function in the middle of the pseudo code.
while(true){
fork()
reset buf
getting input
while(getting input){
if(true) {give only acces to this process for the next function}
function()
if(true){stop acces to only that one process for the function}
reset buf
getting input
}
}
So I want to activate and close the Semaphores with the statements, to give the acces only to that one process which activate the statement, other processes has to wait.
How to implement this in C?
I hope my probleme is clear enough to understand
EDIT 1:
right now I try to use sysV
unsigned short marker2[2];
struct sembuf sem_down;
sem_down.sem_num = 0;
sem_down.sem_op = -1;
sem_down.sem_flg = 0;
struct sembuf sem_up;
sem_up.sem_num = 0;
sem_up.sem_op = +1;
sem_up.sem_flg = 0;
sem_id = semget (IPC_PRIVATE, 1, IPC_CREAT | IPC_EXCL | 0600);
marker2[0] = 1;
semctl(sem_id, 1, SETALL, marker2);
while(true) {
fork();
while(input){
if (strncmp("BEG", inputBuffer[0], 3) == 0) {
semop (sem_id, &sem_down, 1);
printf("blocked from %d\n", getpid());
}
conditions(inputBuffer[0],
inputBuffer[1],
inputBuffer[2],
cfd, semID1, shmID);
if (strncmp("END", inputBuffer[0], 3) == 0) {
semop (sem_id, &sem_up, 1);
printf("open by %d\n", getpid());
}
}
}
right now, other processes doesn't get blocked
Edit 2:
sem_t semvar;
sem_init(&semvar, 0,1);
while(true) {
fork();
while(input){
if (strncmp("BEG", inputBuffer[0], 3) == 0) {
sem_wait(&semvar);
printf("blocked from %d\n", getpid());
}
conditions(inputBuffer[0],
inputBuffer[1],
inputBuffer[2],
cfd, semID1, shmID);
if (strncmp("END", inputBuffer[0], 3) == 0) {
sem_post(&semvar);
printf("open by %d\n", getpid());
}
}
still doesn't work
The basic operations on a semaphore are "acquire" -- waiting until the value is non-zero and atomically decrement it, and "release" -- increment the value (thus allowing someone waiting for am "acrquire" to proceed.)
So to allow only one process to access a critical section like you describe with a semaphore, you would initialize the semaphore value to 1, acquire the semaphore before the critical section, and release it afterwards. So your first "if(true)" should be "acquire" and the second should be "release"
So how do you actually do this in C? Well, C does not have any native semaphore types or operations. There are a number of different libraries that provide them -- the SysV semctl/semop calls, POSIX sem_open, WIN32 semaphores, and probably others. C11 does have native mutex types, but they are only process local (so can only be used for multithreading in a single process)

Wait for all processes reach some point in program, then resume

I have an application which creates many processes via fork(). At some point I want to pause them all and wait until all of them finish earlier tasks. Then start them all at once.
for (int i = 0; i < N; i++)
{
if(fork() == 0)
{
//some operations here
<----- wait here for all N forked processes
//some operations I want all processes start at similiar time
I don't want any of children to quit.
This seems tailor-made for a semaphore. Specifically, this is easy to implement with "System V semaphores". See semget(2) and semop(2).
The idea is that you obtain a semaphore in the parent, initialize its value to N, then have each child as it's "ready" decrement the value by 1. All children wait for the result to become 0. Voila.
Here's a sample program
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define N 5
int main(int ac, char **av)
{
int i, n, sem_id;
sem_id = semget(IPC_PRIVATE, 1, 0777);
struct sembuf buf;
buf.sem_num = 0;
buf.sem_flg = 0;
// Initialize semaphore value to N
buf.sem_op = N;
n = semop(sem_id, &buf, 1);
// All children will do the same thing:
// decrement semaphore value by 1
// wait for semaphore value == 0
for (i = 0; i < N; ++i) {
if (fork() == 0) {
printf("Child %d (%d) started\n", i, getpid());
sleep(i + 1); // Sleep awhile.
buf.sem_op = -1;
n = semop(sem_id, &buf, 1);
buf.sem_op = 0;
n = semop(sem_id, &buf, 1);
printf("Child %d (%d) done\n", i, getpid());
return 0;
}
}
return 0;
}
There are many ways to communicate with processes, it is called IPC (Inter Process Communication). It depends on what kind of information you need to send, how large etc. Process signals is also a way to control processes.
Here are a few resources to get you started:
Personally I like Beej Guides, which are awsome, clear, concise and FREE!
http://beej.us/guide/bgipc/
There is also Advanced Linux Programming by CodeSourcery LLC.
http://www.advancedlinuxprogramming.com/
And of course, once you have some notions of the basics, you can look up more specifically http://stackoverflow.com, i.e. right here :-)
What you can use is a synchronization construct like semaphore.
At the point of code where you want to stop all childrens make them wait in queue (call wait on a semaphore with 0 as initial value )of semaphore and thus they all will block.
In parent then you can signal semaphore and thus start them all.
I would highly recommend Message Passing Interface (MPI) for this, since it has MPI_Barrier that you can used to achieve exactly what you want with almost no logic you have to code yourself. There are alternatives such as OpenMP as well.
I've managed to resolve this via group signals:
for (int i = 0; i < N; i++)
{
if(fork() == 0)
{
/* save first process pid in shared memory */
if(localID == 0)
*sh_temp = getpid();
/* wait in all processes for shared memory with group id to be set */
while(*sh_temp == 0)
usleep(10000);
/* set group id to first process PID */
setpgid(getpid(), *sh_temp);
//some operations here
fprintf(stderr, "Process %d paused... W8ing for signal to resume\n", getpid());
raise(SIGSTOP);
//some operations I want all processes start at similiar time
}
}
if(parentPID == getpid())
{
fprintf(stderr, "Wake-up signal sent to group %ld.\n", *sh_temp);
killpg(*sh_temp, SIGCONT);
}
But - if anyone has better solution please post it.

Semaphores and the standard out buffer

I implemented the Reader/Writer problem in C using semaphores (http://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem).
If I put the respective processes to sleep() for one second, the program acts as expected, however if I let it run without interrupting I get something like this:
writer wrote 5
writer wrote 10
reader reads the data:5
writer wrote 15
reader reads the data:15
reader reads the data:15
Semaphore with value of sid = 11599873 is killed
Semaphore with value of sid = 11632642 is killed
Semaphore with value of sid = 11665411 is killed
or
writer wrote 5
writer wrote 10
writer wrote 15
reader reads the data:5
reader reads the data:15
reader reads the data:15
Semaphore with value of sid = 11599873 is killed
Semaphore with value of sid = 11632642 is killed
Semaphore with value of sid = 11665411 is killed
Is this just because the standard out buffer prints these lines out of order, or is there a race condition in my implementation?
Here's the code:
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#include "sem.h"
#define N 3
#define BUFSIZE 1
#define PERMS 0666 //0666 - To grant read and write permissions
int *buffer;
int mutex,write_allowed,rd_count; /* semaphore variables
* mutex, write_allowed - binary semaphore -- critical section
* rd_count - counting semaphore */
void reading()
{
//perform read
int g;
g=*buffer;
printf("reader reads the data:%d\n",g);
}
int main()
{
int shmid,no=1,i;
int pid,n;
if((shmid=shmget(1000,BUFSIZE,IPC_CREAT| PERMS )) < 0)
{
printf("\n unable to create shared memory");
return;
}
if((buffer=(int*)shmat(shmid,(char*)0,0)) == (int*)-1)
{
printf("\n Shared memory allocation error\n");
exit(1);
}
// semaphore creation
if((mutex=semget(IPC_PRIVATE,1,PERMS | IPC_CREAT)) == -1)
{
printf("\n can't create mutex semaphore");
exit(1);
}
if((write_allowed=semget(IPC_PRIVATE,1,PERMS | IPC_CREAT)) == -1)
{
printf("\n can't create empty semaphore");
exit(1);
}
if((rd_count=semget(IPC_PRIVATE,1,PERMS | IPC_CREAT)) == -1)
{
printf("\ncan't create full semaphore");
exit(1);
}
// initialze the semaphore
sem_create(mutex,1);
sem_create(write_allowed,1);
sem_create(rd_count,0);
//forking a child
if((pid=fork()) < 0)
{
printf("\n Error in process creation");
exit(1);
}
// write process
if(pid > 0)
{
for(i=0;i<N;i++)
{
P(write_allowed);
//perform write
*buffer=*buffer+5;
printf("write wrote %d\n", *buffer);
//sleep(1);
V(write_allowed);
}
}
//read process
if(pid == 0)
{
for(i=0;i<N;i++)
{
P(mutex);
rd_count++;
if(1 == rd_count)
P(write_allowed);
V(mutex);
reading();
//sleep(1);
P(mutex);
rd_count--;
if(0==rd_count)
V(write_allowed);
V(mutex);
}
shmdt(0);
shmctl(shmid, IPC_RMID, NULL);
semkill(mutex);
semkill(write_allowed);
semkill(rd_count);
}
}
The sem.h file:
/************************************************************************/
/* Operating Systems - Fall 2006
/* */
/* Semaphore library : sem.h */
/* */
/* Originally developed at KSU by a teaching assistant */
/* */
/* Description : The following library is a collection of */
/* routines for using binary semaphores in C: */
/* 1. seminit - to initialize a semaphore. */
/* 2. P - to perform a P(S) (wait) operation. */
/* 3. V - to perform a V(S) (signal) operation. */
/* 4. semkill - to remove a semaphore */
/* */
/* These routines call system routines: */
/* 1. semget - to get a semaphore */
/* 2. semctl - semaphore control operations */
/* 3. semop - semaphore operations */
/* */
/* Complete manual entries can be obtained by: */
/* man semctl | col -b | lpr */
/************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union arg{ /* This structure is used to call semctl */
int val;
struct semid_ds *buf;
char *array;
};
/*
* Create semaphore based on "key" parameter to "initval"
*/
void sem_create(int semid, int initval)
{
int semval;
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
}s;
s.val=initval;
if((semval=semctl(semid,0,SETVAL,s))<0)
printf("\n Erroe in executing semctl");
}
/*
* Remove semaphore with semaphore id (sid) from the kernel
*/
static void semkill (sid)
int sid;
{
if (semctl(sid,0,IPC_RMID,0) == -1)
perror("semctl (kill)");
printf("Semaphore with value of sid = %d is killed \n",sid);
}
/*
* Perform the designated "op" operation on the semaphore. If "op" is -1,
* then this implements the "P" operation; it decrements the value of the
* semaphore (semval) if it was >0,
* and blocks the caller if it was zero (semval==0)
* If "op" is 1, then this is simply added to current value of
* the semaphore ("V" operation).
*/
static void semcall(sid, op)
int sid;
int op;
{
struct sembuf sb;
sb.sem_num = 0; /* semaphore number within sid */
sb.sem_op = op;
sb.sem_flg = 0; /* blocking call */
if (semop(sid, &sb, 1) == -1)
perror("semop");
}
/*
* P operation on semaphore "sid". Should be called upon entry to critical
* region.
*/
static void P(sid)
int sid;
{
semcall(sid, -1);
}
/*
* V operation on semaphore "sid". Should be called upon exit from critical
* region.
*/
static void V(sid)
int sid;
{
semcall(sid, 1);
}
You don't increment a semaphore by rd_count++;. rd_count is supposedly just a semaphore ID. You need to use some kind of function that operates using the semaphore ID to change the state of the semaphore, which is kept somewhere outside your two processes.
Also I am not familiar with sem_create(), but unless it is a C++ function taking a reference argument, I have my doubts it has any side-effect on the variables you pass it.

How to use POSIX semaphores on forked processes in C?

I want to fork multiple processes and then use a semaphore on them. Here is what I tried:
sem_init(&sem, 1, 1); /* semaphore*, pshared, value */
.
.
.
if(pid != 0){ /* parent process */
wait(NULL); /* wait all child processes */
printf("\nParent: All children have exited.\n");
.
.
/* cleanup semaphores */
sem_destroy(&sem);
exit(0);
}
else{ /* child process */
sem_wait(&sem); /* P operation */
printf(" Child(%d) is in critical section.\n",i);
sleep(1);
*p += i%3; /* increment *p by 0, 1 or 2 based on i */
printf(" Child(%d) new value of *p=%d.\n",i,*p);
sem_post(&sem); /* V operation */
exit(0);
}
And the output is:
child(0) forked
child(1) forked
Child(0) is in critical section.
Child(1) is in critical section.
child(2) forked
Child(2) is in critical section.
child(3) forked
Child(3) is in critical section.
child(4) forked
Child(4) is in critical section.
Child(0) new value of *p=0.
Child(1) new value of *p=1.
Child(2) new value of *p=3.
Child(3) new value of *p=3.
Child(4) new value of *p=4.
Parent: All children have exited.
This clearly means the semaphore didn't work as it was supposed to. Can you explain how I should use semaphores on forked processes?
The problem you are facing is the misunderstanding of sem_init() function. When you read the manual page
you will see this:
The pshared argument indicates whether this semaphore is to be shared
between the threads of a process, or between processes.
If you are done reading up to this point, you will think that the non-zero value of pshared will make the semaphore inter-process semaphore. However, this is wrong.
You should continue reading and you'll understand that you have to locate the semaphore in a shared memory region. To do that, several functions can be used as
you can see below:
If pshared is nonzero, then the semaphore is shared between processes,
and should be located in a region of shared memory (see shm_open(3),
mmap(2), and shmget(2)). (Since a child created by fork(2) inherits
its parent's memory mappings, it can also access the semaphore.) Any
process that can access the shared memory region can operate on the
semaphore using sem_post(3), sem_wait(3), etc.
I find this approach as a more complicated approach than others, therefore I want to encourage people to use sem_open() instead of sem_init().
Below you can see a complete program illustrates the following:
How to allocate shared memory and use shared variables between forked
processes.
How to initialize a semaphore in a shared memory region and is used
by multiple processes.
How to fork multiple processes and make the parent wait until all of
its children exit.
#include <stdio.h> /* printf() */
#include <stdlib.h> /* exit(), malloc(), free() */
#include <sys/types.h> /* key_t, sem_t, pid_t */
#include <sys/shm.h> /* shmat(), IPC_RMID */
#include <errno.h> /* errno, ECHILD */
#include <semaphore.h> /* sem_open(), sem_destroy(), sem_wait().. */
#include <fcntl.h> /* O_CREAT, O_EXEC */
int main (int argc, char **argv){
int i; /* loop variables */
key_t shmkey; /* shared memory key */
int shmid; /* shared memory id */
sem_t *sem; /* synch semaphore *//*shared */
pid_t pid; /* fork pid */
int *p; /* shared variable *//*shared */
unsigned int n; /* fork count */
unsigned int value; /* semaphore value */
/* initialize a shared variable in shared memory */
shmkey = ftok ("/dev/null", 5); /* valid directory name and a number */
printf ("shmkey for p = %d\n", shmkey);
shmid = shmget (shmkey, sizeof (int), 0644 | IPC_CREAT);
if (shmid < 0){ /* shared memory error check */
perror ("shmget\n");
exit (1);
}
p = (int *) shmat (shmid, NULL, 0); /* attach p to shared memory */
*p = 0;
printf ("p=%d is allocated in shared memory.\n\n", *p);
/********************************************************/
printf ("How many children do you want to fork?\n");
printf ("Fork count: ");
scanf ("%u", &n);
printf ("What do you want the semaphore value to be?\n");
printf ("Semaphore value: ");
scanf ("%u", &value);
/* initialize semaphores for shared processes */
sem = sem_open ("pSem", O_CREAT | O_EXCL, 0644, value);
/* name of semaphore is "pSem", semaphore is reached using this name */
printf ("semaphores initialized.\n\n");
/* fork child processes */
for (i = 0; i < n; i++){
pid = fork ();
if (pid < 0) {
/* check for error */
sem_unlink ("pSem");
sem_close(sem);
/* unlink prevents the semaphore existing forever */
/* if a crash occurs during the execution */
printf ("Fork error.\n");
 }
else if (pid == 0)
break; /* child processes */
}
/******************************************************/
/****************** PARENT PROCESS ****************/
/******************************************************/
if (pid != 0){
/* wait for all children to exit */
while (pid = waitpid (-1, NULL, 0)){
if (errno == ECHILD)
break;
}
printf ("\nParent: All children have exited.\n");
/* shared memory detach */
shmdt (p);
shmctl (shmid, IPC_RMID, 0);
/* cleanup semaphores */
sem_unlink ("pSem");
sem_close(sem);
/* unlink prevents the semaphore existing forever */
/* if a crash occurs during the execution */
exit (0);
}
/******************************************************/
/****************** CHILD PROCESS *****************/
/******************************************************/
else{
sem_wait (sem); /* P operation */
printf (" Child(%d) is in critical section.\n", i);
sleep (1);
*p += i % 3; /* increment *p by 0, 1 or 2 based on i */
printf (" Child(%d) new value of *p=%d.\n", i, *p);
sem_post (sem); /* V operation */
exit (0);
}
}
OUTPUT
./a.out
shmkey for p = 84214791
p=0 is allocated in shared memory.
How many children do you want to fork?
Fork count: 6
What do you want the semaphore value to be?
Semaphore value: 2
semaphores initialized.
Child(0) is in critical section.
Child(1) is in critical section.
Child(0) new value of *p=0.
Child(1) new value of *p=1.
Child(2) is in critical section.
Child(3) is in critical section.
Child(2) new value of *p=3.
Child(3) new value of *p=3.
Child(4) is in critical section.
Child(5) is in critical section.
Child(4) new value of *p=4.
Child(5) new value of *p=6.
Parent: All children have exited.
It is not bad to check shmkey since when ftok() fails, it returns -1. However if you have multiple shared variables and
if the ftok() function fails multiple times, the shared variables that have a shmkey with value -1 will reside in the same
region of the shared memory resulting in a change in one affecting the other. Therefore the program execution will get messy. To avoid this, it is better checked if the ftok()
returns -1 or not (better to check in source code rather than printing to screen like I did, although I wanted to show you the key values in case there is a collision).
Pay attention to how the semaphore is declared and initialized. It's different than what you have done in the question (sem_t sem vs sem_t* sem). Moreover, you should use them as they appear in this example. You cannot define sem_t* and use it in sem_init().
Linux minimal anonymous sem_init + mmap MAP_ANONYMOUS example
I like this setup as it does not pollute any global namespace as sem_open does.
The only downside is that MAP_ANONYMOUS is not POSIX, and I don't know any replacement: Anonymous shared memory? shm_open for example takes a global identifier just like sem_open.
main.c:
#define _GNU_SOURCE
#include <assert.h>
#include <semaphore.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv) {
pid_t pid;
typedef struct {
sem_t sem;
int i;
} Semint;
Semint *semint;
size_t size = sizeof(Semint);
semint = (Semint *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
assert(semint != MAP_FAILED);
/* 1: shared across processes
* 0: initial value, wait locked until one post happens (making it > 0)
*/
sem_init(&semint->sem, 1, 0);
semint->i = 0;
pid = fork();
assert(pid != -1);
if (pid == 0) {
sleep(1);
semint->i = 1;
msync(&semint->sem, size, MS_SYNC);
sem_post(&semint->sem);
exit(EXIT_SUCCESS);
}
if (argc == 1) {
sem_wait(&semint->sem);
}
/* Was modified on the other process. */
assert(semint->i == 1);
wait(NULL);
sem_destroy(&semint->sem);
assert(munmap(semint, size) != -1);
return EXIT_SUCCESS;
}
Compile:
gcc -g -std=c99 -Wall -Wextra -o main main.c -lpthread
Run with sem_wait:
./main
Run without sem_wait:
./main 1
Without this synchronization, the assert is very likely to fail, since the child sleeps for one whole second:
main: main.c:39: main: Assertion `semint->i == 1' failed.
Tested on Ubuntu 18.04. GitHub upstream.

Tracking the death of a child process

How could I track down the death of a child process without making the parent process wait until the child process got killed?
I am trying a client-server scenario where the server accepts the connection from a client and forks a new process for each and every connection it accepts.
I am ignoring SIGCHLD signals to prevent zombie creation.
signal(SIGCHLD, SIG_IGN);
while(1)
{
accept();
clients++;
if(fork() ==0)
{
childfunction();
clients--;
}
else
{
}
}
The problem in the above scenario is that if the child process gets killed in the childfunction() function, the global variable clients is not getting decremented.
NOTE: I am looking for a solution without using SIGCHLD signal ... If possible
Typically you write a handler for SIGCHLD which calls waitpid() on pid -1. You can use the return value from that to determine what pid died. For example:
void my_sigchld_handler(int sig)
{
pid_t p;
int status;
while ((p=waitpid(-1, &status, WNOHANG)) != -1)
{
/* Handle the death of pid p */
}
}
/* It's better to use sigaction() over signal(). You won't run into the
* issue where BSD signal() acts one way and Linux or SysV acts another. */
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = my_sigchld_handler;
sigaction(SIGCHLD, &sa, NULL);
Alternatively you can call waitpid(pid, &status, 0) with the child's process ID specified, and synchronously wait for it to die. Or use WNOHANG to check its status without blocking.
None of the solutions so far offer an approach without using SIGCHLD as the question requests. Here is an implementation of an alternative approach using poll as outlined in this answer (which also explains why you should avoid using SIGCHLD in situations like this):
Make sure you have a pipe to/from each child process you create. It can be either their stdin/stdout/stderr or just an extra dummy fd. When the child process terminates, its end of the pipe will be closed, and your main event loop will detect the activity on that file descriptor. From the fact that it closed, you recognize that the child process died, and call waitpid to reap the zombie.
(Note: I omitted some best practices like error-checking and cleaning up file descriptors for brevity)
/**
* Specifies the maximum number of clients to keep track of.
*/
#define MAX_CLIENT_COUNT 1000
/**
* Tracks clients by storing their process IDs and pipe file descriptors.
*/
struct process_table {
pid_t clientpids[MAX_CLIENT_COUNT];
struct pollfd clientfds[MAX_CLIENT_COUNT];
} PT;
/**
* Initializes the process table. -1 means the entry in the table is available.
*/
void initialize_table() {
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
PT.clientfds[i].fd = -1;
}
}
/**
* Returns the index of the next available entry in the process table.
*/
int get_next_available_entry() {
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
if (PT.clientfds[i].fd == -1) {
return i;
}
}
return -1;
}
/**
* Adds information about a new client to the process table.
*/
void add_process_to_table(int i, pid_t pid, int fd) {
PT.clientpids[i] = pid;
PT.clientfds[i].fd = fd;
}
/**
* Removes information about a client from the process table.
*/
void remove_process_from_table(int i) {
PT.clientfds[i].fd = -1;
}
/**
* Cleans up any dead child processes from the process table.
*/
void reap_zombie_processes() {
int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0);
if (p > 0) {
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
/* Has the pipe closed? */
if ((PT.clientfds[i].revents & POLLHUP) != 0) {
// printf("[%d] done\n", PT.clientpids[i]);
waitpid(PT.clientpids[i], NULL, 0);
remove_process_from_table(i);
}
}
}
}
/**
* Simulates waiting for a new client to connect.
*/
void accept() {
sleep((rand() % 4) + 1);
}
/**
* Simulates useful work being done by the child process, then exiting.
*/
void childfunction() {
sleep((rand() % 10) + 1);
exit(0);
}
/**
* Main program
*/
int main() {
/* Initialize the process table */
initialize_table();
while (1) {
accept();
/* Create the pipe */
int p[2];
pipe(p);
/* Fork off a child process. */
pid_t cpid = fork();
if (cpid == 0) {
/* Child process */
close(p[0]);
childfunction();
}
else {
/* Parent process */
close(p[1]);
int i = get_next_available_entry();
add_process_to_table(i, cpid, p[0]);
// printf("[%d] started\n", cpid);
reap_zombie_processes();
}
}
return 0;
}
And here is some sample output from running the program with the printf statements uncommented:
[31066] started
[31067] started
[31068] started
[31069] started
[31066] done
[31070] started
[31067] done
[31068] done
[31071] started
[31069] done
[31072] started
[31070] done
[31073] started
[31074] started
[31072] done
[31075] started
[31071] done
[31074] done
[31081] started
[31075] done
You don't want a zombie. If a child process dies and the parent is still RUNNING but never issues a wait()/waitpid() call to harvest the status, the system does not release the resources associated with the child and a zombie/defunct process is left in the proc table.
Try changing your SIGCHLD handler to something closer to the following:
void chld_handler(int sig) {
pid_t p;
int status;
/* loop as long as there are children to process */
while (1) {
/* retrieve child process ID (if any) */
p = waitpid(-1, &status, WNOHANG);
/* check for conditions causing the loop to terminate */
if (p == -1) {
/* continue on interruption (EINTR) */
if (errno == EINTR) {
continue;
}
/* break on anything else (EINVAL or ECHILD according to manpage) */
break;
}
else if (p == 0) {
/* no more children to process, so break */
break;
}
/* valid child process ID retrieved, process accordingly */
...
}
}
You could optionally mask/block additional SIGCHLD signals during execution of the signal handler using sigprocmask(). The blocked mask must be returned to its original value when the signal handling routine has finished.
If you really don't want to use a SIGCHLD handler, you could try adding the child processing loop somewhere where it would be called regularly and poll for terminated children.
The variable 'clients' are in different process address spaces after fork() and when you decrement the variable in the child, this will not affect the value in the parent. I think you need to handle SIGCHLD to handle the count correctly.

Resources