The following code is my first C program I have made for an university assignment about Interprocess Communication. This involved creating a parent process, the farmer, and then creating a certain number of forked processes, the workers, and we had to let the workers calculate something and use message queues to communicate it from the parent to the worker and vica versa. The computations were chosen to be Mandelbrot computations by the teacher. The goal of the assignment was to get familiar with the POSIX libraries.
These were the functional requirements, in my own words:
- The program must use the settings from
settings.h
- The farmer starts
NROF_WORKER
worker processes - The message queues have a size of
MQ_MAX_MESSAGES
messages - The names of the message queues had to include the student name and the student id
- The worker queue needs to be as full as possible
- The farmer queue needs to be as empty as possible
- Busy waiting is not allowed
- We have to use a
rsleep(10000)
call to simulate some kind of random waiting, implementation given. - Lastly, another implicit requirement is that we cannot use extra processes or threads, we are only allowed to use the 1 farmer process and the
NROF_WORKER
worker processes.
The following implementations were given:
- The code that outputs the image on the screen
- The Mandelbrot computation
The Makefile that was provided:
#
#
BINARIES = worker farmer
CC = gcc
CFLAGS = -Wall -g -c
LDLIBS = -lrt -lX11
all: $(BINARIES)
clean:
rm -f *.o $(BINARIES)
worker: worker.o
farmer: farmer.o output.o
worker.o: worker.c settings.h common.h
output.o: output.c output.h settings.h
farmer.o: farmer.c output.h settings.h common.h
This is the relevant code:
settings.h
#ifndef _SETTINGS_H_
#define _SETTINGS_H_
// remove the comments for the output you like: either graphical (X11) output
// or storage in a BMP file (or both)
#define WITH_X11
//#define WITH_BMP
// settings for interprocess communications
// (note: be sure that /proc/sys/fs/mqueue/msg_max >= MQ_MAX_MESSAGES)
#define NROF_WORKERS 64
#define MQ_MAX_MESSAGES 64
// settings for the fractal computations
#define INFINITY 10.0
#define MAX_ITER 512
// settings for graphics
#define X_PIXEL 880
#define Y_PIXEL 660
#define X_LOWERLEFT -2.0
#define Y_LOWERLEFT -1.0
#define STEP 0.003
//#define X_LOWERLEFT -0.65
//#define Y_LOWERLEFT -0.5
//#define STEP 0.0001
// lower left pixel (0,0) has coordinate
// (X_LOWERLEFT, Y_LOWERLEFT)
// upperright pixel (X_PIXEL-1,Y_PIXEL-1) has coordinate
// (X_LOWERLEFT+((X_PIXEL-1)*STEP),Y_LOWERLEFT+((Y_PIXEL-1)*STEP))
#endif
output.h
#ifndef _OUTPUT_H_
#define _OUTPUT_H_
extern void output_init (void);
extern void output_draw_pixel (int x, int y, int color);
extern void output_end (void);
#endif
settings.h
/*
*
* Contains definitions which are commonly used by the farmer and the workers
*
*/
#ifndef _COMMON_H_
#define _COMMON_H_
#include "settings.h"
#define STUDENT_NAME "FrankVanHeeswijk"
typedef struct
{
int y;
} MQ_FARMER_REQUEST_MESSAGE;
typedef struct
{
int y;
int x_colors[X_PIXEL];
} MQ_WORKER_RESULT_MESSAGE;
#endif
farmer.c
/*
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h> // for execlp
#include <mqueue.h> // for mq
#include "settings.h"
#include "output.h"
#include "common.h"
static char mq_farmer_request_name[80];
static char mq_worker_result_name[80];
static void fork_children(pid_t children_IDs[])
{
int i;
for (i = 0; i < NROF_WORKERS; i++)
{
pid_t processID = fork();
if (processID < 0)
{
perror("fork() failed: " + processID);
}
else
{
if (processID == 0)
{
execlp("./worker", "worker", mq_farmer_request_name, mq_worker_result_name, NULL);
perror("execlp() failed");
}
children_IDs[i] = processID;
}
}
}
static void kill_children(pid_t children_IDs[])
{
int i;
for (i = 0; i < NROF_WORKERS; i++)
{
waitpid(children_IDs[i], NULL, 0);
}
}
static void process_worker_result_message(MQ_WORKER_RESULT_MESSAGE worker_result_message)
{
int x;
for (x = 0; x < X_PIXEL; x++)
{
output_draw_pixel(x, worker_result_message.y, worker_result_message.x_colors[x]);
}
}
int main (int argc, char* argv[])
{
if (argc != 1)
{
fprintf (stderr, "%s: invalid arguments\n", argv[0]);
}
output_init ();
//create message queues
sprintf(mq_farmer_request_name, "/mq_farmer_request_%s_%d", STUDENT_NAME, getpid());
sprintf(mq_worker_result_name, "/mq_worker_result_%s_%d", STUDENT_NAME, getpid());
struct mq_attr attr;
attr.mq_maxmsg = MQ_MAX_MESSAGES;
attr.mq_msgsize = sizeof(MQ_FARMER_REQUEST_MESSAGE);
mqd_t mq_farmer_request = mq_open(mq_farmer_request_name, O_WRONLY | O_CREAT | O_EXCL, 0600, &attr);
if (mq_farmer_request < 0)
{
perror("error opening farmer request message queue in farmer");
}
attr.mq_maxmsg = MQ_MAX_MESSAGES;
attr.mq_msgsize = sizeof(MQ_WORKER_RESULT_MESSAGE);
mqd_t mq_worker_result = mq_open(mq_worker_result_name, O_RDONLY | O_CREAT | O_EXCL, 0600, &attr);
if (mq_worker_result < 0)
{
perror("error opening worker result message queue in farmer");
}
//create children
pid_t children_IDs[NROF_WORKERS];
fork_children(children_IDs);
//send & receive data
MQ_FARMER_REQUEST_MESSAGE farmer_request_message;
MQ_WORKER_RESULT_MESSAGE worker_result_message;
//keep farmer request queue as filled as possible, keep worker result queue as empty as possible
int msg_max = Y_PIXEL;
int msg_num_received = 0;
int msg_num_sent = 0;
while (msg_num_sent < msg_max && msg_num_received < msg_max)
{
//fill up farmer request queue to the max
int get_farmer_request_attr_result = mq_getattr(mq_farmer_request, &attr);
if (get_farmer_request_attr_result < 0)
{
perror("error getting farmer request attr in farmer");
exit(EXIT_FAILURE);
}
while (attr.mq_curmsgs < attr.mq_maxmsg && msg_num_sent < msg_max)
{
//send message
farmer_request_message.y = msg_num_sent;
int sent = mq_send(mq_farmer_request, (char*)&farmer_request_message, sizeof(farmer_request_message), 0);
if (sent < 0)
{
perror("error sending message in farmer");
exit(EXIT_FAILURE);
}
msg_num_sent++;
get_farmer_request_attr_result = mq_getattr(mq_farmer_request, &attr);
if (get_farmer_request_attr_result < 0)
{
perror("error getting farmer request attr in farmer");
exit(EXIT_FAILURE);
}
}
//empty worker result queue
int get_worker_result_attr_result = mq_getattr(mq_worker_result, &attr);
if (get_worker_result_attr_result < 0)
{
perror("error getting worker result attr in farmer");
exit(EXIT_FAILURE);
}
while (attr.mq_curmsgs > 0)
{
//take one message
int received_bytes = mq_receive(mq_worker_result, (char*)&worker_result_message, sizeof(worker_result_message), NULL);
if (received_bytes < 0)
{
perror("error receiving message in farmer");
exit(EXIT_FAILURE);
}
msg_num_received++;
process_worker_result_message(worker_result_message);
//because we took one message, we can now send another one
if (msg_num_sent < msg_max)
{
farmer_request_message.y = msg_num_sent;
int sent = mq_send(mq_farmer_request, (char*)&farmer_request_message, sizeof(farmer_request_message), 0);
if (sent < 0)
{
perror("error sending message in farmer");
exit(EXIT_FAILURE);
}
msg_num_sent++;
}
get_worker_result_attr_result = mq_getattr(mq_worker_result, &attr);
if (get_worker_result_attr_result < 0)
{
perror("error getting worker result attr in farmer");
exit(EXIT_FAILURE);
}
}
}
//stop children
int i;
for (i = 0; i < NROF_WORKERS; i++)
{
farmer_request_message.y = -1;
int sent = mq_send(mq_farmer_request, (char*)&farmer_request_message, sizeof(farmer_request_message), 0);
if (sent < 0)
{
perror("error sending message in farmer");
}
}
kill_children(children_IDs);
//close message queues
int closed_farmer = mq_close(mq_farmer_request);
if (closed_farmer < 0)
{
perror("failed to close farmer request queue in farmer");
}
int closed_worker = mq_close(mq_worker_result);
if (closed_worker < 0)
{
perror("failed to close worker result queue in farmer");
}
int unlink_farmer = mq_unlink(mq_farmer_request_name);
if (unlink_farmer < 0)
{
perror("failed to unlink farmer request queue in farmer");
}
int unlink_worker = mq_unlink(mq_worker_result_name);
if (unlink_worker < 0)
{
perror("failed to unlink worker result queue in farmer");
}
output_end();
return EXIT_SUCCESS;
}
worker.c
/*
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h> // for perror()
#include <unistd.h> // for getpid()
#include <mqueue.h> // for mq-stuff
#include <time.h> // for time()
#include <complex.h>
#include "settings.h"
#include "common.h"
static double
complex_dist (complex a)
{
// distance of vector 'a'
// (in fact the square of the distance is computed...)
double re, im;
re = __real__ a;
im = __imag__ a;
return ((re * re) + (im * im));
}
static int
mandelbrot_point (double x, double y)
{
int k;
complex z;
complex c;
z = x + y * I; // create a complex number 'z' from 'x' and 'y'
c = z;
for (k = 0; (k < MAX_ITER) && (complex_dist (z) < INFINITY); k++)
{
z = z * z + c;
}
// 2
// k >= MAX_ITER or | z | >= INFINITY
return (k);
}
/*
* rsleep(int t)
*
* The calling thread will be suspended for a random amount of time
* between 0 and t microseconds
* At the first call, the random generator is seeded with the current time
*/
static void rsleep (int t)
{
static bool first_call = true;
if (first_call == true)
{
srandom (time(NULL) % getpid());
first_call = false;
}
usleep (random () % t);
}
int main (int argc, char* argv[])
{
//open message queues
char* mq_farmer_request_name = argv[1];
char* mq_worker_result_name = argv[2];
mqd_t mq_farmer_request = mq_open(mq_farmer_request_name, O_RDONLY);
if (mq_farmer_request < 0)
{
perror("error opening farmer request message queue in worker");
}
mqd_t mq_worker_result = mq_open(mq_worker_result_name, O_WRONLY);
if (mq_worker_result < 0)
{
perror("error opening worker result message queue in worker");
}
//read messages
MQ_FARMER_REQUEST_MESSAGE farmer_request_message;
MQ_WORKER_RESULT_MESSAGE worker_result_message;
while (true)
{
int received_bytes = mq_receive(mq_farmer_request, (char*)&farmer_request_message, sizeof(farmer_request_message), NULL);
if (received_bytes < 0)
{
perror("error receiving message in worker");
break;
}
if (farmer_request_message.y < 0)
{
break;
}
rsleep(10000);
worker_result_message.y = farmer_request_message.y;
int x;
for (x = 0; x < X_PIXEL; x++)
{
double mx = (X_LOWERLEFT + x * STEP);
double my = (Y_LOWERLEFT + worker_result_message.y * STEP);
worker_result_message.x_colors[x] = mandelbrot_point(mx, my);
}
int sent = mq_send(mq_worker_result, (char*)&worker_result_message, sizeof(worker_result_message), 0);
if (sent < 0)
{
perror("error sending message in worker");
break;
}
}
int closed_farmer = mq_close(mq_farmer_request);
if (closed_farmer < 0)
{
perror("failed to close farmer request queue in worker");
}
int closed_worker = mq_close(mq_worker_result);
if (closed_worker < 0)
{
perror("failed to close worker result queue in worker");
}
return EXIT_SUCCESS;
}
In order to help you understand the intended implementation in the farmer, I have attached this pseudo-code to understand how I want to achieve a maximally filled worker queue and a minimally filled farmer queue:
while (not_all_messages_sent) {
while (mq_farmer_request not full) {
send request to mq_farmer_request;
}
while (mq_worker_result not empty) {
receive result from mq_worker_result;
process result;
if (not_all_messages_sent) {
send request to mq_farmer_request;
}
}
}
I'd like to have a review on all aspects of my code. I have a Java background so I am very used to putting everything that is reusable into methods and I hate hard-coding, but these seems less applicable in C. I wouldn't mind to have more methods in my code.
You can view the full code and extract a working copy over at my Github repository.