Synchronizing processes with semaphores and signals in C

4.4k views Asked by At

I have to write program in C on Linux. It has to have 3 processes - first reads from STDIN, sends message through FIFO to second process, which counts lenght of recevied message and sends result to third process (also through FIFO), which displays it on STDOUT. I have to synchronize it using semaphores. Also I have to add signal handling (which I am doing using shared memory) - one signal to end program, second to stop it, third to resume. Signal can be send to any of the processes. I have some code already, but it's not working as it should.

First problem is with synchronization - as you can see by running it, first message is received by second process, but then it stuck. First and second processes are showing their messages, but not the third one. There are very similar, so it's confusing. I have to send another message, then P3 is showing the length of previous one.

Second problem is with signals - after sending one, I have to press enter (for SIGUSR) or send message (for SIGINT) for it to be served.

Any ideas what's wrong? There are some improvements from what I posted before, but it's still not working properly and I don't have much time to finish it (till monday). I know it's a lot of code, but if someone could just analyze communication of second and third process, I will be very grateful.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/sem.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

#define WRITE 1
#define READ 0
#define MEM_ID 1
#define MEM_SIZE 1

#define OK  "[ \033[1;32mOK\033[0m ]\n"
#define ERR "[\033[1;31mFAIL\033[0m] "
#define SIGNAL "\033[1;33m\033[5m>> SIGNAL <<\033[0m\n"

#define S1 SIGINT
#define S2 SIGUSR1
#define S3 SIGUSR2
#define S4 SIGCONT

/* union semun - from POSIX specification for semctl() */
/* NB: on Mac OS X, and apparently in defiance of POSIX, <sys/sem.h> declares union semun */
/*
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short  *array;
} arg;
*/

/*
union semun
{
    int val;
    ushort *array;
};
*/

int process1(void);
int process2(void);
int process3(void);
void signal_callback(int signo);
//void signal_handling(int snd, int rcv);   // JL
void signal_handling(void);     // JL
void sem_down(int semid, int semnum);
void sem_up(int semid, int semnum);

char work = 1, quit = 0;
char inter_snd = 0, inter_rcv = 0;
struct sembuf semstruct;
int sem23, sem12, mem;
char *message;
int res[3];

static const char *fifoname[] = { "1fifo2", "2fifo3" };

static
void main_quit(int n)
{
    printf("%s(): signal %d\n", __func__, n);  // JL
    kill(res[0], S1);
}

int main(void)
{
    //union semun arg;  // JL
    printf("[G] Launching\n");

    signal(SIGINT, main_quit);
    signal(SIGTERM, main_quit);
    // creating FIFO
    printf("[G] Creating FIFO... ");
    res[0] = mkfifo(fifoname[0], 0644);
    res[1] = mkfifo(fifoname[1], 0644);
    if ((res[0] == -1) || (res[1] == -1))
    {
        perror(ERR);
        unlink(fifoname[0]);
        unlink(fifoname[1]);
        return 1;
    }
    else
        printf(OK);

    // create two semaphores and set values
    printf("[G] Creating semaphores... ");
    sem12 = semget(READ, 1, IPC_CREAT | 0644);
    sem23 = semget(WRITE, 1, IPC_CREAT | 0644);

    if ((sem23 == -1) || (sem12 == -1))
    {
        perror(ERR);
        return 1;
    }
    else
        printf(OK);

    printf("[G] Initializing semaphores values... ");
    semctl(sem12, 0, SETVAL, 0);
    semctl(sem12, 1, SETVAL, 1);
    semctl(sem23, 0, SETVAL, 0);
    semctl(sem23, 1, SETVAL, 1);
    printf(OK);

    // creating shared memory
    printf("[G] Reserving shared memory... ");
    mem = shmget(MEM_ID, MEM_SIZE, IPC_CREAT | 0644);
    message = (char *)shmat(mem, 0, 0);

    if (mem == -1)
    {
        perror(ERR);
        return 1;
    }
    else
        printf(OK);

    if ((res[0] = fork()) == 0)
    {
        process1();
        exit(0);
    }

    if ((res[1] = fork()) == 0)
    {
        process2();
        exit(0);
    }

    if ((res[2] = fork()) == 0)
    {
        process3();
        exit(0);
    }

    printf("[G] Building process tree... ");
    if ((res[0] == -1) || (res[1] == -1) || (res[2] == -1))
    {
        perror(ERR);
        return 1;
    }
    else
    {
        printf(OK);
        printf("[G] P1[pid]: %d, P2[pid]: %d, P3[pid]: %d\n", res[0], res[1], res[2]);
    }

    wait(NULL);
    wait(NULL);
    wait(NULL);

    printf("[G] Deleting FIFO... ");
    res[0] = unlink(fifoname[0]);
    res[1] = unlink(fifoname[1]);
    if ((res[0] == -1) || (res[1] == -1))
        perror(ERR);
    else
        printf(OK);

    printf("[G] Freeing shared memory... ");
    res[0] = shmdt((char *)message);
    res[1] = shmctl(mem, IPC_RMID, 0);
    if ((res[0] == -1) || (res[1] == -1))
        perror(ERR);
    else
        printf(OK);

    printf("[G] Deleting semaphores... ");
    res[0] = semctl(sem23, 0, IPC_RMID, 0);
    res[1] = semctl(sem12, 0, IPC_RMID, 0);
    if ((res[0] == -1) || (res[1] == -1))
        perror(ERR);
    else
        printf(OK);

    printf("[G] Ending...\n");
    return 0;
}

int process1(void)
{
    char tab[100];
    FILE *fifoh;

    signal(S1, signal_callback);
    signal(S2, signal_callback);
    signal(S3, signal_callback);
    signal(S4, signal_callback);

    fifoh = fopen(fifoname[0], "w");
    setbuf(fifoh, NULL);

    printf("[P1] Ready.\n");
    //while (fgets(tab, sizeof(tab), stdin) > 0)    // JL
    while (fgets(tab, sizeof(tab), stdin) != 0)     // JL
    {
        if (work)
        {
            sem_down(sem12, WRITE);
            printf("[P1] Sending: %s", tab);
            fprintf(fifoh, "%s\n", tab);
            sem_up(sem12, READ);
        }
        //signal_handling(inter_snd, inter_rcv);    // JL
        signal_handling();  // JL
    }
    fclose(fifoh);
    printf("[P1] Ending...\n");
    return 0;
}

int process2(void)
{
    char tab[100];
    FILE *fifo_in, *fifo_out;

    printf("[P2] Ready.\n");

    fifo_in = fopen(fifoname[0], "r");
    fifo_out = fopen(fifoname[1], "w");

    setbuf(fifo_out, NULL);
    setbuf(fifo_in, NULL);

    signal(S1, signal_callback);
    signal(S2, signal_callback);
    signal(S3, signal_callback);
    signal(S4, signal_callback);

    do
    {
        if (work)
        {
            sem_down(sem12, READ);
            fscanf(fifo_in, "%s", (char *)tab);
            sem_up(sem12, WRITE);
            printf("[P2] Received \"%s\" with length %zu.\n", tab, strlen(tab));
            sem_down(sem23, WRITE);
            fprintf(fifo_out, "%d\n", (int)strlen(tab));
            sem_up(sem23, READ);
        }
        //signal_handling(inter_snd, inter_rcv);    // JL
        signal_handling();  // JL
    } while (!quit);

    fclose(fifo_in);
    fclose(fifo_out);
    printf("[P2] Ending...\n");
    return 0;
}

int process3(void)
{
    FILE *fifo_in;
    int count;

    printf("[P3] Ready.\n");

    signal(S1, signal_callback);
    signal(S2, signal_callback);
    signal(S3, signal_callback);
    signal(S4, signal_callback);

    fifo_in = fopen(fifoname[1], "r");
    setbuf(fifo_in, NULL);

    do
    {
        if (work)
        {
            sem_down(sem23, READ);
            fscanf(fifo_in, "%d\n", (int *)&count);
            sem_up(sem23, WRITE);
            printf("[P3] Received: %d characters.\n", count);
        }
        //signal_handling(inter_snd, inter_rcv);    // JL
        signal_handling();  // JL
    } while (!quit);
    fclose(fifo_in);
    printf("[P3] Ending...\n");
    return 0;
}

//void signal_handling(int snd, int rvc)
void signal_handling(void)
{
    if (inter_snd > 0)
    {
        printf("Signal received...\n");

        semstruct.sem_op = -3;
        semop(sem23, &semstruct, 1);

        *message = inter_snd;
        inter_snd = 0;

        semstruct.sem_op = 3;
        semop(sem12, &semstruct, 1);

        printf("Sending to other processes\n");
        kill(0, S4);
    }
    if (inter_rcv)
    {
        inter_rcv = 0;

        semstruct.sem_op = -1;
        semop(sem12, &semstruct, 1);

        switch (*message)
        {
        case 1:
            printf("Quitting...\n");
            quit = 1;
            break;
        case 2:
            printf("Stopping...\n");
            work = 0;
            break;
        case 3:
            printf("Starting...\n");
            work = 1;
            break;
        default:
            printf("There's garbage in memory :/..\n");
        }
        semstruct.sem_op = 1;
        semop(sem23, &semstruct, 1);
    }
}

void signal_callback(int signo)
{
    printf(SIGNAL);
    switch (signo)
    {
    case S1:
        inter_snd = 1;
        break;
    case S2:
        inter_snd = 2;
        break;
    case S3:
        inter_snd = 3;
        break;
    case S4:
        inter_rcv = 1;
        break;
    }
}

void sem_down(int semid, int semnum)
{
    semstruct.sem_flg = 0;
    semstruct.sem_num = semnum;
    semstruct.sem_op = -1;
    do
    {
        errno = 0;
        semop(semid, &semstruct, 1);
    } while (errno == EINTR);
}

void sem_up(int semid, int semnum)
{
    semstruct.sem_flg = 0;

    semstruct.sem_num = semnum;
    semstruct.sem_op = 1;
    semop(semid, &semstruct, 1);
}

Expected behaviour:

[P3] Ready.
[P2] Ready.
[P1] Ready.
asd
[P1] Sending: asd
[P2] Received "asd" with length 3.
[P3] Received: 3 characters.
as
[P1] Sending: as
[P2] Received "as" with length 2.
[P3] Received: 2 characters.

Signals:

Signal should be recieved and sent to every other processes. What happens next is specified in singnal_handling. Every process should show their message (Quitting/Stopping/etc).

Actual behaviour:

[P3] Ready.
[P2] Ready.
[P1] Ready.
asd 
[P1] Sending: asd
[P2] Received "asd" with length 3.
dd
[P1] Sending: dd
[P2] Received "dd" with length 2.
[P3] Received: 3 characters. //first message
as
[P1] Sending: as
[P2] Received "as" with length 2.
[P3] Received: 2 characters. //second
asd
[P1] Sending: asd
[P2] Received "asd" with length 3.
[P3] Received: 2 characters. // third

And with signals, well... I figured out that after SIGINT I could send message, hit enter twice and then I got expected behaviour (quitting program). Pressing enter also works when I'm sending another signals:

$ kill -s SIGUSR1 2900
$ kill -s SIGUSR2 2900

Gives:

asd
[P1] Sending: asd
[P2] Received "asd" with length 3.
[P3] Received: 3 characters.
Signal received...
Sending to other processes
>> SIGNAL <<
Stopping...
>> SIGNAL <<
>> SIGNAL <<
>> SIGNAL <<
Signal received...
Sending to other processes
>> SIGNAL <<
>> SIGNAL <<
>> SIGNAL <<
Starting...

[P1] Sending: 
Starting...
asd
[P1] Sending: asd
[P2] Received "asd" with length 3.
Starting...
[P3] Received: 3 characters.

So again - after sending a signal I have to send a message for it to be handled. So, it's kinda working. But very, very bad.

1

There are 1 answers

10
Jonathan Leffler On BEST ANSWER

Taking your code verbatim onto a Mac running Mac OS X 10.10.3, using GCC 5.1.0, using the standard compilation options I use for compiling code from Stack Overflow, I get compilation warnings as shown (I called your code semshm.c):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror semshm.c -o semshm
semshm.c:29:7: error: redefinition of ‘union semun’
 union semun {
       ^
In file included from semshm.c:8:0:
/usr/include/sys/sem.h:176:7: note: originally defined here
 union semun {
       ^
semshm.c:48:6: error: no previous prototype for ‘main_quit’ [-Werror=missing-prototypes]
 void main_quit(int n) {
      ^
semshm.c: In function ‘main_quit’:
semshm.c:48:20: error: unused parameter ‘n’ [-Werror=unused-parameter]
 void main_quit(int n) {
                    ^
semshm.c: In function ‘main’:
semshm.c:54:17: error: unused variable ‘arg’ [-Werror=unused-variable]
     union semun arg;
                 ^
semshm.c: In function ‘process1’:
semshm.c:161:43: error: ordered comparison of pointer with integer zero [-Werror=extra]
     while (fgets(tab, sizeof(tab), stdin) > 0) {
                                           ^
semshm.c: In function ‘process2’:
semshm.c:197:20: error: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘size_t {aka long unsigned int}’ [-Werror=format=]
             printf("[P2] Recieved \"%s\" with length %d.\n", tab, strlen(tab));
                    ^
semshm.c:197:20: error: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘size_t {aka long unsigned int}’ [-Werror=format=]
semshm.c: In function ‘signal_handling’:
semshm.c:239:26: error: unused parameter ‘snd’ [-Werror=unused-parameter]
 void signal_handling(int snd, int rvc) {
                          ^
semshm.c:239:35: error: unused parameter ‘rvc’ [-Werror=unused-parameter]
 void signal_handling(int snd, int rvc) {
                                   ^
cc1: all warnings being treated as errors

The first problem is peculiar to Mac OS X; it seems to define union semun in <sys/sem.h> despite the POSIX specification for semctl() saying explicitly:

The semctl() function provides a variety of semaphore control operations as specified by cmd. The fourth argument is optional and depends upon the operation requested. If required, it is of type union semun, which the application shall explicitly declare:

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short  *array;
} arg;

The only excuse for the POSIX requirement is bad historical precedent, which Mac OS X has over-ridden. So, the double declaration of struct semun is not held against you. It is curious that POSIX requires an extra member, though, compared with what you specify. Anyway, I commented out that declaration to get past that.

There are other warnings, though many of them are not all that serious. Unused variables are not good, but not hugely harmful. The format mismatch between %d and size_t matters if, as on my machine, %d looks for a 32-bit value and size_t is a 64-bit quantity. The test for fgets() should be != 0; it returns NULL (or 0 — I use 0 too) on failure.

The most curious problem is the unused arguments to signal_handling(). The calls pass the global variables inter_snd and inter_rcv to the function, but the function ignores those values and simply manipulates the global variables. There is at least a serious disconnect between the calling and called code here.

For the purposes of getting on with life, I converted signal_handling() to a parameterless function void signal_handling(void); and left its body unchanged (but fixed all the calls to it). I added:

printf("%s(): signal %d\n", __func__, n);

to main_quit() in defiance of the advice on how to avoid using printf() in a signal handler. I also fixed the 'i' before 'e' except after 'c' spelling mistakes. Also 'quitting' has two t's and 'stopping' has two p's — English is such a weird language.

Finally, I extracted the two FIFO names (1fifo2 and 2fifo3) into an array:

static const char *fifoname[] = { "1fifo2", "2fifo3" };

and used either fifoname[0] or fifoname[1] in place of the literals. Repeated file name literals are a bad idea. I also used the new names to clean up (unlink(fifoname[0]); unlink(fifoname[1]);) the FIFOs if either existed after failing to create them.

  • Despite the collection of warnings, the code is not bad; few programs pass that set of options unscathed without some editing to make it pass cleanly.

With the code compiled, I ran it a few times. One of the last runs was:

$ ./semshm
[G] Launching
[G] Creating FIFO... [ OK ]
[G] Creating semaphores... [ OK ]
[G] Initializing semaphores values... [ OK ]
[G] Reserving shared memory... [ OK ]
[G] Building process tree... [ OK ]
[G] P1[pid]: 36030, P2[pid]: 36031, P3[pid]: 36032
[P2] Ready.
[P1] Ready.
[P3] Ready.
^C>> SIGNAL <<
main_quit(): signal 2
>> SIGNAL <<
>> SIGNAL <<
>> SIGNAL <<
absolute baloney
[P1] Sending: absolute baloney
Signal received...
[P2] Received "absolute" with length 8.
Signal received...
commestible goods for everyone
^C>> SIGNAL <<
>> SIGNAL <<
main_quit(): signal 2
>> SIGNAL <<
Sending to other processes
>> SIGNAL <<
Sending to other processes
>> SIGNAL <<
>> SIGNAL <<
>> SIGNAL <<
Quitting...
>> SIGNAL <<
Quitting...
[P2] Ending...
[P3] Received: 8 characters.
Signal received...
Sending to other processes
>> SIGNAL <<
>> SIGNAL <<
Quitting...
>> SIGNAL <<
[P3] Ending...
^\Quit: 3
$

I typed an interrupt, which was handled. I typed 'absolute baloney', of which the 'absolute' part was apparently received in one place, but the 'baloney' part never made it. That's puzzling. I typed 'commestible goods for everyone' and to all appearances, it was completely ignored. I tried another interrupt, which apparently interrupted things. I tried control-D (EOF), which apparently did nothing. I used control-backslash (control-\) to generate a quit signal, which did indeed stop the program. I'd done that before; reruns showed the wisdom of cleaning up after spotting that the FIFOs were already created as I got the failure message, then reran the code and it worked OK. The output shows green OK messages, and flashing yellow-ish >>> SIGNAL <<< messages.

So, you didn't tell us how to use the program, or the output to expect from it.

Your 'no compiler warnings' assertion was a little optimistic, but not too far off the truth.

Now you need to upgrade the question to specify/illustrate the inputs you give it and the expected behaviour from that input and to show the unexpected behaviour you actually get from it.

One semi-working version of semshm.c

This is the 'as compiled', partially annotated code that I compiled and ran. To compile it on Linux or other more accurately POSIX-compliant platform (more accurate than Mac OS X, that is), you'll need to uncomment one of the union semun blocks. I'd normally remove <sys/types.h> (modern POSIX almost never requires you to include it; older versions from the last millennium did require it — see POSIX 1997 which did, but compare with POSIX 2004 which did not).

#include <stdio.h>

…code co-opted into question…see revision 3 at https://stackoverflow.com/posts/30813144/revisions

…I ran over the 30,000 character limit with this code on board…

}

Another mostly working version of semshm.c

Taking the extra information about how the code is supposed to be working, and putting in a lot of extra diagnostic printing, and observing that process 2 and 3 get stuck waiting on a semaphore which is not signalled, I came up with this variant of the code:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/sem.h>
#include <signal.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>

#define WRITE 1
#define READ 0
#define MEM_ID 1
#define MEM_SIZE 1

#define OK  "[ \033[1;32mOK\033[0m ]\n"
#define ERR "[\033[1;31mFAIL\033[0m] "
#define SIGNAL "\033[1;33m\033[5m>> SIGNAL <<\033[0m\n"

#define S1 SIGINT
#define S2 SIGUSR1
#define S3 SIGUSR2
#define S4 SIGCONT

int process1(void);
int process2(void);
int process3(void);
void signal_callback(int signo);
void signal_handling(void);
void sem_down(int semid, int semnum);
void sem_up(int semid, int semnum);

char work = 1, quit = 0;
char inter_snd = 0, inter_rcv = 0;
struct sembuf semstruct;
int sem23, sem12, mem;
char *message;
int res[3];

static const char *fifoname[] = { "1fifo2", "2fifo3" };

static
void main_quit(int n)
{
    printf("%s(): signal %d\n", __func__, n);
    kill(res[0], S1);
}

int main(void)
{
    printf("[G] Launching\n");

    signal(SIGINT, main_quit);
    signal(SIGTERM, main_quit);
    // creating FIFO
    printf("[G] Creating FIFO... ");
    res[0] = mkfifo(fifoname[0], 0644);
    res[1] = mkfifo(fifoname[1], 0644);
    if ((res[0] == -1) || (res[1] == -1))
    {
        perror(ERR);
        unlink(fifoname[0]);
        unlink(fifoname[1]);
        return 1;
    }
    else
        printf(OK);

    // create two semaphores and set values
    printf("[G] Creating semaphores... ");
    sem12 = semget(READ, 1, IPC_CREAT | 0644);
    sem23 = semget(WRITE, 1, IPC_CREAT | 0644);

    if ((sem23 == -1) || (sem12 == -1))
    {
        perror(ERR);
        return 1;
    }
    else
        printf(OK);

    printf("[G] Initializing semaphores values... ");
    semctl(sem12, 0, SETVAL, 0);
    semctl(sem12, 1, SETVAL, 1);
    semctl(sem23, 0, SETVAL, 0);
    semctl(sem23, 1, SETVAL, 1);
    printf(OK);

    // creating shared memory
    printf("[G] Reserving shared memory... ");
    mem = shmget(MEM_ID, MEM_SIZE, IPC_CREAT | 0644);
    message = (char *)shmat(mem, 0, 0);

    if (mem == -1 || message == 0)
    {
        perror(ERR);
        return 1;
    }
    else
        printf(OK);

    if ((res[0] = fork()) == 0)
    {
        process1();
        printf("Returned from process1() - exiting\n");
        exit(0);
    }

    if ((res[1] = fork()) == 0)
    {
        process2();
        printf("Returned from process2() - exiting\n");
        exit(0);
    }

    if ((res[2] = fork()) == 0)
    {
        process3();
        printf("Returned from process3() - exiting\n");
        exit(0);
    }

    printf("[G] Building process tree... ");
    if ((res[0] == -1) || (res[1] == -1) || (res[2] == -1))
    {
        perror(ERR);
        return 1;
    }
    else
    {
        printf(OK);
        printf("[G] P1[pid]: %d, P2[pid]: %d, P3[pid]: %d\n", res[0], res[1], res[2]);
    }

    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
        printf("[G] PID %d exited with status 0x%.4X\n", corpse, status);

    printf("[G] Deleting FIFO... ");
    res[0] = unlink(fifoname[0]);
    res[1] = unlink(fifoname[1]);
    if ((res[0] == -1) || (res[1] == -1))
        perror(ERR);
    else
        printf(OK);

    printf("[G] Freeing shared memory... ");
    res[0] = shmdt((char *)message);
    res[1] = shmctl(mem, IPC_RMID, 0);
    if ((res[0] == -1) || (res[1] == -1))
        perror(ERR);
    else
        printf(OK);

    printf("[G] Deleting semaphores... ");
    res[0] = semctl(sem23, 0, IPC_RMID, 0);
    res[1] = semctl(sem12, 0, IPC_RMID, 0);
    if ((res[0] == -1) || (res[1] == -1))
        perror(ERR);
    else
        printf(OK);

    printf("[G] Ending...\n");
    return 0;
}

int process1(void)
{
    char tab[100];
    FILE *fifoh;

    signal(S1, signal_callback);
    signal(S2, signal_callback);
    signal(S3, signal_callback);
    signal(S4, signal_callback);

    fifoh = fopen(fifoname[0], "w");
    setbuf(fifoh, NULL);

    printf("[P1] Ready.\n");
    while (fgets(tab, sizeof(tab), stdin) != 0)
    {
        if (work)
        {
            sem_down(sem12, WRITE);
            printf("[P1] Sending: %s", tab);
            fprintf(fifoh, "%s", tab);
            sem_up(sem12, READ);
        }
        else
            printf("[P1] Ignoring line because work is zero: %s", tab);
        signal_handling();
    }
    fclose(fifoh);
    printf("[P1] Ending...\n");
    sem_up(sem12, READ);    // Crucial
    return 0;
}

int process2(void)
{
    char tab[100];
    FILE *fifo_in, *fifo_out;

    printf("[P2] Ready.\n");

    fifo_in = fopen(fifoname[0], "r");
    fifo_out = fopen(fifoname[1], "w");

    setbuf(fifo_out, NULL);
    setbuf(fifo_in, NULL);

    signal(S1, signal_callback);
    signal(S2, signal_callback);
    signal(S3, signal_callback);
    signal(S4, signal_callback);

    do
    {
        if (work)
        {
            printf("[P2]: Waiting on semaphore sem12/R\n");
            sem_down(sem12, READ);
            printf("[P2]: Proceed on semaphore sem12/R\n");
            if (fscanf(fifo_in, "%s", (char *)tab) != 1)
            {
                printf("[P2]: EOF\n");
                break;
            }
            printf("[P2]: Signalling semaphore sem12/W\n");
            sem_up(sem12, WRITE);
            printf("[P2]: Proceeding semaphore sem12/W\n");
            printf("[P2] Received \"%s\" with length %zu.\n", tab, strlen(tab));
            printf("[P2]: Waiting on semaphore sem23/R\n");
            sem_down(sem23, WRITE);
            printf("[P2]: Proceed on semaphore sem23/R\n");
            fprintf(fifo_out, "%zu\n", strlen(tab));
            printf("[P2]: Signalling semaphore sem23/W\n");
            sem_up(sem23, READ);
            printf("[P2]: Proceeding semaphore sem23/W\n");
        }
        else
            printf("[P2] Looping: work is zero\n");
        printf("[P2]: signal handling\n");
        signal_handling();
        printf("[P2]: signal handling done\n");
    } while (!quit);

    fclose(fifo_in);
    fclose(fifo_out);
    printf("[P2] Ending...\n");
    sem_up(sem23, READ);    // Crucial
    return 0;
}

int process3(void)
{
    FILE *fifo_in;
    int count;

    printf("[P3] Ready.\n");

    signal(S1, signal_callback);
    signal(S2, signal_callback);
    signal(S3, signal_callback);
    signal(S4, signal_callback);

    fifo_in = fopen(fifoname[1], "r");
    setbuf(fifo_in, NULL);

    do
    {
        if (work)
        {
            printf("[P3]: Waiting on semaphore sem23\n");
            sem_down(sem23, READ);
            if (fscanf(fifo_in, "%d", &count) == 1)
                printf("[P3] Received: %d as the length.\n", count);
            else
            {
                printf("[P3] Failed to read an integer\n");
                break;
            }
            sem_up(sem23, WRITE);
        }
        else
            printf("[P3] Looping: work is zero\n");
        printf("[P3]: signal handling\n");
        signal_handling();
        printf("[P3]: signal handling done\n");
    } while (!quit);
    fclose(fifo_in);
    printf("[P3] Ending...\n");
    return 0;
}

void signal_handling(void)
{
    if (inter_snd > 0)
    {
        printf("PID %d: Signal received...\n", (int)getpid());

        semstruct.sem_op = -3;
        semop(sem23, &semstruct, 1);

        *message = inter_snd;
        inter_snd = 0;

        semstruct.sem_op = 3;
        semop(sem12, &semstruct, 1);

        printf("Sending to other processes\n");
        kill(0, S4);
    }
    else
        printf("PID %d: inter_snd = %d\n", (int)getpid(), inter_snd);

    if (inter_rcv)
    {
        inter_rcv = 0;

        semstruct.sem_op = -1;
        semop(sem12, &semstruct, 1);

        switch (*message)
        {
        case 1:
            printf("Quitting...\n");
            quit = 1;
            break;
        case 2:
            printf("Stopping...\n");
            work = 0;
            break;
        case 3:
            printf("Starting...\n");
            work = 1;
            break;
        default:
            printf("There's garbage in memory :/..\n");
        }
        semstruct.sem_op = 1;
        semop(sem23, &semstruct, 1);
    }
    else
        printf("PID %d: inter_rcv = %d\n", (int)getpid(), inter_rcv);
}

void signal_callback(int signo)
{
    printf("%sSignal %d in PID %d\n", SIGNAL, signo, (int)getpid());
    switch (signo)
    {
    case S1:
        inter_snd = 1;
        break;
    case S2:
        inter_snd = 2;
        break;
    case S3:
        inter_snd = 3;
        break;
    case S4:
        inter_rcv = 1;
        break;
    }
}

void sem_down(int semid, int semnum)
{
    semstruct.sem_flg = 0;
    semstruct.sem_num = semnum;
    semstruct.sem_op = -1;
    do
    {
        errno = 0;
        semop(semid, &semstruct, 1);
    } while (errno == EINTR);
}

void sem_up(int semid, int semnum)
{
    semstruct.sem_flg = 0;

    semstruct.sem_num = semnum;
    semstruct.sem_op = 1;
    semop(semid, &semstruct, 1);
}

Note the two sum_up() calls marked 'crucial'; they are indeed crucial. Also note that the input operations are error checked; that too is crucial. If you don't check that they worked, you miss the EOF indications. Always, but always check that the I/O operations worked correctly.

With that code, a sample run was:

$ ./semshm
[G] Launching
[G] Creating FIFO... [ OK ]
[G] Creating semaphores... [ OK ]
[G] Initializing semaphores values... [ OK ]
[G] Reserving shared memory... [ OK ]
[G] Building process tree... [ OK ]
[G] P1[pid]: 36545, P2[pid]: 36546, P3[pid]: 36547
[P2] Ready.
[P3] Ready.
[P1] Ready.
[P3]: Waiting on semaphore sem23
[P2]: Waiting on semaphore sem12/R
absolutely-ineffable-twaddle-for-onward-transmission
[P1] Sending: absolutely-ineffable-twaddle-for-onward-transmission
PID 36545: inter_snd = 0
PID 36545: inter_rcv = 0
[P2]: Proceed on semaphore sem12/R
[P2]: Signalling semaphore sem12/W
[P2]: Proceeding semaphore sem12/W
[P2] Received "absolutely-ineffable-twaddle-for-onward-transmission" with length 52.
[P2]: Waiting on semaphore sem23/R
[P2]: Proceed on semaphore sem23/R
[P2]: Signalling semaphore sem23/W
[P2]: Proceeding semaphore sem23/W
[P2]: signal handling
PID 36546: inter_snd = 0
PID 36546: inter_rcv = 0
[P2]: signal handling done
[P2]: Waiting on semaphore sem12/R
[P3] Received: 52 as the length.
[P3]: signal handling
PID 36547: inter_snd = 0
PID 36547: inter_rcv = 0
[P3]: signal handling done
[P3]: Waiting on semaphore sem23
[P1] Ending...
Returned from process1() - exiting
[P2]: Proceed on semaphore sem12/R
[P2]: EOF
[P2] Ending...
Returned from process2() - exiting
[P3] Failed to read an integer
[P3] Ending...
Returned from process3() - exiting
[G] PID 36545 exited with status 0x0000
[G] PID 36546 exited with status 0x0000
[G] PID 36547 exited with status 0x0000
[G] Deleting FIFO... [ OK ]
[G] Freeing shared memory... [ OK ]
[G] Deleting semaphores... [ OK ]
[G] Ending...
$

I've not worked out how much of the code could be eliminated — but my suspicion is that 'quite a lot' is an accurate statement. The semaphore operations are really not necessary; the processes would block correctly on the file streams anyway. I'm not convinced that the shared memory helps at all, either. And I'm not convinced that work is helping very much, but it might conceivably do something useful under signal handling. Note that it is not shared between processes (it is private to each process), so changes made to work by one process only affect that process, not the other processes.