I wrote a C program for the Dining Philosophers Problem using pthread some time ago and am now trying to change it to use fork() instead. This is an exercive for a lecture I already passed. But a friend asked me for help and I can't seem to get it figured out by myself, which is driving me crazy!
If i do a "ps" the processes are there. But there isn't any output to stdout, so I think I'm doing something wrong with the pipes.
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define N 5
#define LEFT (i+4)%N
#define RIGHT (i+1)%N
#define THINKING 0
#define HUNGRY 1
#define EATING 2
sem_t spoon;
sem_t phil[N];
int state[N];
int phil_num[N]={0,1,2,3,4};
int fd[N][2]; // file descriptors for pipes
pid_t pid, pids[N]; // process ids
int i;
int num;
void philosopher(int i);
void test(int i);
void take_spoon(int i);
void put_spoon(int i);
char buffer[100];
int main(void)
{
for(i=0;i<N;++i)
{
pipe(fd[i]);
pids[i] = fork();
printf("i=%d\n",i);
printf("pids[i]=%d\n",pids[i]);
if(pids[i]==0)
{
// child
dup2(fd[i][1],1);
close(fd[i][0]);
close(fd[i][1]);
philosopher(i);
_exit(0);
}
else if(pids[i]>0)
{
// parent
dup2(fd[i][0],0);
close(fd[i][0]);
close(fd[i][1]);
}
}
// wait for child processes to end
for(i=0;i<N;++i) waitpid(pids[i],NULL,0);
return 0;
}
void philosopher(int i)
{
while(1)
{
sleep(1);
take_spoon(i);
sleep(2);
put_spoon(i);
sleep(1);
}
}
void take_spoon(int i)
{
sem_wait(&spoon);
state[i] = HUNGRY;
printf("philosopher %d is hungry\n",i+1);
test(i);
sem_post(&spoon);
sem_wait(&phil[i]);
}
void put_spoon(int i)
{
sem_wait(&spoon);
state[i] = THINKING;
printf("philosopher %d puts down spoon %d and %d hin\n",i+1,LEFT+1,i+1);
printf("philosopher %d thinks\n",i+1);
test(LEFT);
test(RIGHT);
sem_post(&spoon);
}
void test(int i)
{
if( state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING)
{
state[i] = EATING;
printf("philosopher %d takes spoon %d and %d\n",i+1,LEFT+1,i+1);
printf("philosopher %d eats\n",i+1);
sem_post(&phil[i]);
}
}
Thank you in advance for any help.
Several issues. First is that after fork()
, the child process and parent process do not share memory. This is one of the primary differences between a thread and a process. Each process has its own virtual address space. Whatever you want the philosophers to share, you have to explicitly do that by creating shared memory. It seems you intended your global variables to be shared among all the processes. (Note that there are some things shared, such as open file descriptors, and the child does get a copy of the variables from the parent, initialized to the values that were assigned to them at the time of the fork()
call.)
Second, you have some confusingly unnecessary variables. In particular, the pipes do not serve any real purpose. The stdout
for each of the processes will go to the console screen already, without the need for trying to pipe them back to the parent. This is because the child process already inherits the open file descriptors of the parent, so the child will already be using the same stdout
as the parent. In addition, the phil_num
, and num
variables were unused, and the i
. pid
and pids
variables seemed to be needlessly made global.
Third, you failed to initialize your semaphores. The default initialization as a global variable probably leaves the semaphore "useable" but with a 0 initial value, meaning sem_wait()
on it will just block. In your case, you need those semaphores in shared memory, so a call to sem_init()
is mandatory anyway (to indicate it is going to be shared between multiple processes), and the call gives you a chance to properly initialize the semaphore with a value of 1
so that the initial sem_wait()
call has a chance to return.
After adjusting the globals down to what really needs to be shared, they can be bundled together into a structure. Then, a global pointer can be created for the shared data.
struct shared_data {
sem_t spoon;
sem_t phil[N];
int state[N];
};
struct shared_data *shared;
void initialize_shared(); /* at program start */
void finalize_shared(); /* at program end */
One way to create shared memory is to use mmap()
. After the memory is created, the data should be initialized properly. This includes a call to sem_init()
on the semaphores. sem_destroy()
is used to clean up a semaphore, and the mapped memory can be released with munmap()
. These are done for you when the process exits, but provided for completeness. (You should always check the return values of all the operating system calls you make, but I have elided them for the sake of brevity.)
void initialize_shared()
{
int i;
int prot=(PROT_READ|PROT_WRITE);
int flags=(MAP_SHARED|MAP_ANONYMOUS);
shared=mmap(0,sizeof(*shared),prot,flags,-1,0);
memset(shared,'\0',sizeof(*shared));
sem_init(&shared->spoon,1,1);
for(i=0;i<N;++i) sem_init(&shared->phil[i],1,1);
}
void finalize_shared()
{
int i;
for(i=0;i<N;++i) sem_destroy(&shared->phil[i]);
munmap(shared, sizeof(*shared));
}
Your main()
implementation does not really change, except you need to add local variables for the ones that were needlessly global, as well as call initialize_shared()
and optionally finalize_shared()
. Also, remove all the code related to pipe()
.
int main(void)
{
int i;
pid_t pid, pids[N]; // process ids
initialize_shared();
for(i=0;i<N;++i)
{
pid = fork();
if(pid==0)
{
// child
philosopher(i);
_exit(0);
}
else if(pid>0)
{
// parent
pids[i] = pid;
printf("pids[%d]=%d\n",i,pids[i]);
}
else
{
perror("fork");
_exit(0);
}
}
// wait for child processes to end
for(i=0;i<N;++i) waitpid(pids[i],NULL,0);
finalize_shared();
return 0;
}
Note that your program never really exits on its own, since philosopher()
is implemented as an infinite loop.