0

What the code is doing: (FULL CODE is available at the bottom) Parent(Producer) process reads files in an array one by one, and 3 bytes by 3 bytes and put it into shared_buffer(3 bytes of memory size) using fread(), Child(Consumer) process reads from the shared_buffer. Mutual exclusion between read and write is ensured by synchronization with 3 mutexes: _full_, _empty_ and _mutex_.

However, even though It is ensured that parent and child access shared memory located in the same address, the data they received are different.

the first text file in the array:

This is a test file of 8 words.

my code: (extracted)

main:

#include <stdio.h>
//a lot of other libraries
#include "wordCount.h" //wordCount function
#define BUFFER_SIZE 3 
char **file_paths;
int file_count = 0, word_count = 0;
int main(int argc, char **argv)
{
    int process_id; 
    file_paths = malloc(100 * sizeof(char *)); //supposed that we already have files inside
    int shmid, f,e,m;
    char *shared_buffer;
    shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666 | IPC_CREAT);
    shared_buffer = (char *)shmat(shmid, 0, 0);
    
    sem_t *_full_, *_empty_, *_mutex_;
    f = shmget(IPC_PRIVATE, sizeof(sem_t), 0666 | IPC_CREAT);
    e = shmget(IPC_PRIVATE, sizeof(sem_t), 0666 | IPC_CREAT); 
    m = shmget(IPC_PRIVATE, sizeof(sem_t), 0666 | IPC_CREAT);
    _full_ = (sem_t *)shmat(f, 0, 0);
    _empty_ = (sem_t *)shmat(e, 0, 0);
    _mutex_ = (sem_t *)shmat(m, 0, 0);
    sem_init(_full_, 1, 0);
    sem_init(_empty_, 1, 1);
    sem_init(_mutex_, 1, 1); 
    int *read_file_count;
    int rKey = shmget(IPC_PRIVATE, sizeof(int), 0666 | IPC_CREAT);
    read_file_count = (int *)shmat(rKey, 0, 0);
    *read_file_count = 0;
    switch (process_id = fork())
    {

Parent:

    default:
        printf("Parent process: My ID is %jd\n", (intmax_t)getpid());
        printf("address of the shared buffer in parent: %p\n", (void *)shared_buffer);
        printf("parent: address of full: %p; empty: %p; mutex: %p\n", (void *)_full_, (void *)_empty_, (void *)_mutex_);
        int value; sem_getvalue(_full_, &value);
        printf("Value of _full_ semaphore: %d\n", value);
        sem_getvalue(_empty_, &value);
        printf("Value of _empty_ semaphore: %d\n", value);
        sem_getvalue(_mutex_, &value);
        printf("Value of _mutex_ semaphore: %d\n", value);

        while(*read_file_count < file_count){ //LOOPING all the files in the array
        FILE *fp = fopen(file_paths[*read_file_count],"r");     
        if( fp == NULL ){
            printf("Fail to open file!\n");
            exit(0);
        }
            size_t total_read = 0;
            size_t num_read = 0;
            printf("++++++++++Reading FILE %d++++++++++\n", *read_file_count);
            while((num_read = fread(shared_buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) { //LOOPing the same file
                sem_wait(_empty_); printf("Parent process got empty. ");
                sem_wait(_mutex_); printf("Parent process got mutex.\n");
                for (size_t i = 0; i < num_read; i++) {
                    printf("address of shared_buffer[%zu]: %p, ", i, (void *)(shared_buffer+i));
                    printf("shared_buffer[%zu] = [%c]\n", i, shared_buffer[i]);
                }
                printf("------------------------------\n");
                sem_post(_mutex_); printf("Parent process released mutex. "); 
                sem_post(_full_); printf("Parent process released full.\n");
            }
            (*read_file_count)++;               
        }
    printf("Parent process: Finished.\n");
    printf("Parent process: The total number of words is %d.\n", word_count);
    saveResult("p2_result.txt", word_count);
    break;

Child:


    case 0: //child process
        printf("Child process: My ID is %jd\n", (intmax_t)getpid());
        printf("address of the shared buffer in child: %p\n", (void *)shared_buffer);
        printf("child: address of full: %p; empty: %p; mutex: %p\n", (void *)_full_, (void *)_empty_, (void *)_mutex_);
        sem_getvalue(_full_, &value); printf("Value of _full_ semaphore: %d\n", value);
        sem_getvalue(_empty_, &value); printf("Value of _empty_ semaphore: %d\n", value);
        sem_getvalue(_mutex_, &value); printf("Value of _mutex_ semaphore: %d\n", value);
        while(*read_file_count < file_count){
        sem_wait(_full_); printf("Child process got full. ");
        sem_wait(_mutex_); printf("Child process got mutex.\n");
        printf("Child:\n");
        for (size_t i = 0; i < 3; i++) {
            printf("address of shared_buffer[%zu]: %p, ", i, (void *)(shared_buffer+i));
            printf("shared_buffer[%zu] = [%c]\n", i, shared_buffer[i]);
        }
        printf("previous word count: %d\n", word_count);
        word_count += wordCount(shared_buffer);
        printf("child counted words : %d\n", word_count);
        printf("------------------------------\n");
        sem_post(_mutex_); printf("Child process released mutex. ");
        sem_post(_empty_); printf("Child process released empty.\n");
        }
        printf("Child process: Finished.\n");
        exit(0);

OUTPUT:

address of the shared buffer in parent: 0x7fc37dd11000
Child process: My ID is 3231821
parent: address of full: 0x7fc37dcd4000; empty: 0x7fc37dcd3000; mutex: 0x7fc37dcd2000
address of the shared buffer in child: 0x7fc37dd11000
child: address of full: 0x7fc37dcd4000; empty: 0x7fc37dcd3000; mutex: 0x7fc37dcd2000
Value of _full_ semaphore: 0
Value of _full_ semaphore: 0
Value of _empty_ semaphore: 1
Value of _empty_ semaphore: 1
Value of _mutex_ semaphore: 1
Value of _mutex_ semaphore: 1
++++++++++Reading FILE 0++++++++++
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [T]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [h]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [i]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [i]
previous word count: 0
address of text[0]: 0x7fc37dd11000, text[0] = [s]
address of text[1]: 0x7fc37dd11001, text[1] = [ ]
address of text[2]: 0x7fc37dd11002, text[2] = [i]

child counted words : 2
------------------------------
Child process released mutex. Child process released empty.
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [i]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [a]
previous word count: 2
address of text[0]: 0x7fc37dd11000, text[0] = [s]
address of text[1]: 0x7fc37dd11001, text[1] = [ ]
address of text[2]: 0x7fc37dd11002, text[2] = [a]

child counted words : 4
------------------------------
Child process released mutex. Child process released empty.
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [a]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [ ]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [t]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [e]
previous word count: 4
address of text[0]: 0x7fc37dd11000, text[0] = [ ]
address of text[1]: 0x7fc37dd11001, text[1] = [t]
address of text[2]: 0x7fc37dd11002, text[2] = [e]

child counted words : 6
------------------------------
Child process released mutex. Child process released empty.
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [ ]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [t]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [e]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [t]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [ ]
previous word count: 6
address of text[0]: 0x7fc37dd11000, text[0] = [s]
address of text[1]: 0x7fc37dd11001, text[1] = [t]
address of text[2]: 0x7fc37dd11002, text[2] = [ ]

child counted words : 8
------------------------------
Child process released mutex. Child process released empty.
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [t]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [ ]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [f]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [i]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [l]
previous word count: 8
address of text[0]: 0x7fc37dd11000, text[0] = [f]
address of text[1]: 0x7fc37dd11001, text[1] = [i]
address of text[2]: 0x7fc37dd11002, text[2] = [l]

child counted words : 9
------------------------------
Child process released mutex. Child process released empty.
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [f]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [i]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [l]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [e]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [o]
previous word count: 9
address of text[0]: 0x7fc37dd11000, text[0] = [e]
address of text[1]: 0x7fc37dd11001, text[1] = [ ]
address of text[2]: 0x7fc37dd11002, text[2] = [o]

child counted words : 11
------------------------------
Child process released mutex. Child process released empty.
Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [e]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [o]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [f]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [8]
previous word count: 11
address of text[0]: 0x7fc37dd11000, text[0] = [f]
address of text[1]: 0x7fc37dd11001, text[1] = [ ]
address of text[2]: 0x7fc37dd11002, text[2] = [8]
...

As you can see from the output:

Parent process got empty. Parent process got mutex.
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [T]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [h]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [i]
------------------------------
Parent process released mutex. Parent process released full.
Child process got full. Child process got mutex.
Child:
address of shared_buffer[0]: 0x7fc37dd11000, shared_buffer[0] = [s]
address of shared_buffer[1]: 0x7fc37dd11001, shared_buffer[1] = [ ]
address of shared_buffer[2]: 0x7fc37dd11002, shared_buffer[2] = [i]

They literally access the same memory, why is the output different, and as I can observe, what the child read are actually the next 3 characters following with the 3 characters read by parents, why would this happen? How to fix it?

Don't mind the word Count function and the text[] that is printed out. The main issue here is why child reads different buffer data from parent.

FULL CODE:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <pthread.h>
#include <sys/shm.h>
#include <errno.h>
#include <assert.h>
#include <dirent.h>

#include "helpers.h"
#define BUFFER_SIZE 3 

/**
 * @brief This function recursively traverse the source directory.
 *
 * @param dir_name : The source directory name.
 */

char **file_paths;
int file_count = 0;
int word_count = 0;

void traverseDir(char *dir_name);

int main(int argc, char **argv)
{
    int process_id; 
    file_paths = malloc(100 * sizeof(char *));

    
    
    char *dir_name = argv[1];

    if (argc < 2){
        printf("Main process: Please enter a source directory name.\nUsage: ./main <dir_name>\n");
        exit(-1);
    }

    traverseDir(dir_name);
    if (file_count == 0)
    {
        printf("Main process: No txt files found in the source directory.\n");
        exit(-1);
    }
    if (file_count > 100)
    {
        printf("Main process: The number of txt files is greater than 100.\n");
        exit(-1);
    }
    
    printf("Found %d files in the source directory.\n", file_count);
    for (int i = 0; i < file_count; i++)
    {
        printf("%s\n", file_paths[i]);
    }

    
    
    

    
    int shmid;
    int f,e,m;
    
    char *shared_buffer;
    shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666 | IPC_CREAT);
    shared_buffer = (char *)shmat(shmid, 0, 0);
    
    sem_t *_full_, *_empty_, *_mutex_;
    f = shmget(IPC_PRIVATE, sizeof(sem_t), 0666 | IPC_CREAT);
    e = shmget(IPC_PRIVATE, sizeof(sem_t), 0666 | IPC_CREAT); 
    m = shmget(IPC_PRIVATE, sizeof(sem_t), 0666 | IPC_CREAT);
    _full_ = (sem_t *)shmat(f, 0, 0);
    _empty_ = (sem_t *)shmat(e, 0, 0);
    _mutex_ = (sem_t *)shmat(m, 0, 0);
    sem_init(_full_, 1, 0); 
    sem_init(_empty_, 1, 1); 
    sem_init(_mutex_, 1, 1); 

    int *read_file_count;
    int rKey = shmget(IPC_PRIVATE, sizeof(int), 0666 | IPC_CREAT);
    read_file_count = (int *)shmat(rKey, 0, 0);
    *read_file_count = 0;
    switch (process_id = fork())
    {

    default:
    
        printf("Parent process: My ID is %jd\n", (intmax_t)getpid());
        
        
        
        
            printf("address of the shared buffer in parent: %p\n", (void *)shared_buffer);
            printf("parent: address of full: %p; empty: %p; mutex: %p\n", (void *)_full_, (void *)_empty_, (void *)_mutex_);
            int value;
            sem_getvalue(_full_, &value);
            printf("Value of _full_ semaphore: %d\n", value);

            sem_getvalue(_empty_, &value);
            printf("Value of _empty_ semaphore: %d\n", value);

            sem_getvalue(_mutex_, &value);
            printf("Value of _mutex_ semaphore: %d\n", value);

            while(*read_file_count < file_count){
            FILE *fp = fopen(file_paths[*read_file_count],"r");     
            if( fp == NULL ){
                printf("Fail to open file!\n");
                exit(0);
            }
                size_t total_read = 0;
                size_t num_read = 0;
                printf("++++++++++Reading FILE %d++++++++++\n", *read_file_count);
                while((num_read = fread(shared_buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) {
                    sem_wait(_empty_); printf("Parent process got empty. ");
                    sem_wait(_mutex_); printf("Parent process got mutex.\n");
                    if(shared_buffer[0] == ' '){
                        word_count--;
                    } else if (shared_buffer[num_read] == ' '){
                        word_count--;
                    }
                    for (size_t i = 0; i < num_read; i++) {
                        printf("address of shared_buffer[%zu]: %p, ", i, (void *)(shared_buffer+i));
                        printf("shared_buffer[%zu] = [%c]\n", i, shared_buffer[i]);
                    }
                    printf("------------------------------\n");
                    sem_post(_mutex_); printf("Parent process released mutex. "); 
                    sem_post(_full_); printf("Parent process released full.\n");
                }
                (*read_file_count)++;               
            }
        printf("Parent process: Finished.\n");
        printf("Parent process: The total number of words is %d.\n", word_count);
        saveResult("p2_result.txt", word_count);
        break;

    case 0:
    

        printf("Child process: My ID is %jd\n", (intmax_t)getpid());
        printf("address of the shared buffer in child: %p\n", (void *)shared_buffer);
        printf("child: address of full: %p; empty: %p; mutex: %p\n", (void *)_full_, (void *)_empty_, (void *)_mutex_);
        sem_getvalue(_full_, &value);
        printf("Value of _full_ semaphore: %d\n", value);

        sem_getvalue(_empty_, &value);
        printf("Value of _empty_ semaphore: %d\n", value);

        sem_getvalue(_mutex_, &value);
        printf("Value of _mutex_ semaphore: %d\n", value);

        
        
        
        sleep(3);
        while(*read_file_count < file_count){
        sem_wait(_full_); printf("Child process got full. ");
        sem_wait(_mutex_); printf("Child process got mutex.\n");
        printf("Child:\n");
        for (size_t i = 0; i < 3; i++) {
            printf("address of shared_buffer[%zu]: %p, ", i, (void *)(shared_buffer+i));
            printf("shared_buffer[%zu] = [%c]\n", i, shared_buffer[i]);
        }
        printf("previous word count: %d\n", word_count);
        word_count += wordCount(shared_buffer);
        printf("child counted words : %d\n", word_count);
        printf("------------------------------\n");
        sem_post(_mutex_); printf("Child process released mutex. ");
        sem_post(_empty_); printf("Child process released empty.\n");
        }
        printf("Child process: Finished.\n");
        exit(0);

    case -1:
    
        printf("Fork failed!\n");
        exit(-1);
    }

    exit(0);
}

/**
 * @brief This function recursively traverse the source directory.
 *
 * @param dir_name : The source directory name.
 */
void traverseDir(char *dir_name)
{
    
    
    DIR *dir;
    struct dirent *ent;
    if ((dir = opendir(dir_name)) != NULL)
    {
    
        while ((ent = readdir(dir)) != NULL)
        {
            if (ent->d_type == DT_DIR)
            {
                
                if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
                {
                    continue;
                }
                char *new_dir_name = malloc(strlen(dir_name) + strlen(ent->d_name) + 2);
                strcpy(new_dir_name, dir_name);
                strcat(new_dir_name, "/");
                strcat(new_dir_name, ent->d_name);
                traverseDir(new_dir_name);
            }
            else
            {
                
                if (strstr(ent->d_name, ".txt") != NULL)
                {
                    char *new_file_path = malloc(strlen(dir_name) + strlen(ent->d_name) + 2);
                    strcpy(new_file_path, dir_name);
                    strcat(new_file_path, "/");
                    strcat(new_file_path, ent->d_name);
                    
                    
                    file_paths[file_count] = new_file_path;
                    file_count++;
                }
            }
        }
        
        closedir(dir);
    }
    else
    {
    
        perror("");
        return;
    }
    }

helper:

int wordCount(char *text) {
    int counter = 0;
    int i=0;
    while(text[i] != '\0'){ 
        printf("address of text[%d]: %p, ", i, &text[i]);
        printf("text[%d] = [%c]\n", i, text[i]);
        if (text[i] == ' ' || text[i] == '\n'){
            counter++;
        }
        i++;
    }
    printf("\n");
    return counter + 1;
}

helper.h only provides the wordCount function, can ignore

  • 1
    Have you heard of 'functions'? They're good things to write. You can make your code easier to read by writing and using your own functions. – Jonathan Leffler Apr 03 '23 at 18:34
  • You don't seem to error check many (any?) of the system functions that you call. That leads to concerns about whether there are errors reported that you're ignoring. If things are going wrong, adding the checking is a first step to ensuring you know where things go awry. (Side note: error messages should be written to `stderr`, not `stdout`. That's what `stderr` is for!) – Jonathan Leffler Apr 03 '23 at 18:39
  • Sorry about that, I will try to eliminate as much irrelevant stuff in the snippet as I can to make the code better viewable. – Kenny Ynnek Apr 03 '23 at 18:39

1 Answers1

1

If you want to completely share memory between two programs, you should probably use threads rather than forks; see: What is the difference between fork and thread? I see no reason to use forks in your code.

They literally access the same memory, why is the output different

This is irrelevant – yes, they access the same memory thanks to your “Shared memory operations,” but the same addresses would have been shown even if you had simply used malloc (or an automatic array). Thus, what is shown is that each of your two processes (parent and child) use the same internal addresses for these values, but actually every process has its own memory map, where addresses might be the same as in other memory maps, but the values are specific to the process. The operating system, above all that, knows how to convert processes’ internal addresses to physical addresses in memory. Obviously, modern operating systems check addresses; otherwise they couldn’t detect segmentation faults, for example, and any process could easily change the data of another process. After such a check, the OS converts the process’s internal address to the physical one (with a formula which is specific to the process).

Therefore, your processes may show they have the same internal addresses, while that the values associated to them are different. You can see it with a program as simple as the following one:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
   int myVar = 1, * myPtr = (int *)malloc(sizeof(*myPtr));
   *myPtr = 4;
   if (fork() == 0) {
      myVar = 3;
   } else {
      *myPtr = 2;
   }
   sleep(2);
   printf(" myVar: %p %d\n", &myVar, myVar);
   printf("*myPtr: %p %d\n", myPtr, *myPtr);
}

While each process will normally change one of the values before any of the two processes accesses the printf instruction, you will see that different values are shown for the same addresses; for instance:

 myVar: 0x7fff663a0f5c 1
*myPtr: 0x55e1b084a2a0 2
 myVar: 0x7fff663a0f5c 3
*myPtr: 0x55e1b084a2a0 4

and as I can observe, what the child read are actually the next 3 characters following with the 3 characters read by parents, why would this happen?

This is because the parent jumps back to the head of the loop which contains fread(shared_buffer, sizeof(char), BUFFER_SIZE, fp), before you actually lock (using sem_wait) your mutex. Therefore, the buffer gets the next 3 characters, the parent doesn’t use them but this is what the child has when it enters its for loop. Actually, your system is non-deterministic and there is a small chance that the child prints the buffer contents before the parent reads the next 3 characters.

How to fix it?

You should be careful to lock and unlock your mutexes in a contiguous block with nothing else (such as the condition part of a loop) in between:

  • Parent:
    while (*read_file_count < file_count) {
        FILE * fp = fopen(file_paths[*read_file_count], "r");
        if (fp == NULL) {
            fprintf(stderr, "Failed to open file %s!\n", file_paths[*read_file_count]);
        } else {
            while ((*num_read = fread(shared_buffer, sizeof(*shared_buffer), BUFFER_SIZE, fp)) > 0) {
                printf("[Parent] Buffer of size %d from %p contains: %.*s\n",
                    *num_read, shared_buffer, *num_read, shared_buffer);
                sem_post(_full_);
                sem_wait(_empty_);
            }
        }
        ++*read_file_count;
    }
    sem_post(_full_);
    
  • Child:
    sem_wait(_full_);
    while (*read_file_count < file_count) {
        printf("[Child]  Buffer of size %d from %p contains: %.*s\n",
            *num_read, shared_buffer, *num_read, shared_buffer);
        sem_post(_empty_);
        sem_wait(_full_);
    }
    

The parent has no reason to wait or post before it starts its work, so it should not touch any mutexes before it reaches the end of the while loop. In fact, because the child does not change any shared data, the parent could post before it prints – and to my mind, it should do it (as in my code below), but if you don’t particularly want to look into details, just remember that you should post and wait with nothing in between.

On the contrary, the child must start by waiting. And at the very end, the parent must free the child so that it will see that all files have been parsed.

Here is a full code that works for me:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <dirent.h>

#define BUFFER_SIZE 3ul

#define Declare_Shared(type, var_name, size) \
    type * var_name = (type *)shmat(shmget(IPC_PRIVATE, size * sizeof(type), 0666 | IPC_CREAT), 0, 0)
#define Declare_Shared2(type, var_name) Declare_Shared(type, var_name, 1)

char * file_paths[100];
int file_count = 0;

void traverseDir(char * dir_name);

int main(int argc, char * * argv)
{
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <dir_name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    traverseDir(argv[1]);
    if (file_count <= 0 || 100 < file_count) {
        fprintf(stderr, "%d .txt files found in the directory; must be more than 0 and no more than 100.\n", file_count);
        exit(EXIT_FAILURE);
    }

    printf("Found %d files in the source directory.\n", file_count);
    for (int i = 0; i < file_count; ++i) {
        printf("%s\n", file_paths[i]);
    }

    Declare_Shared(char, shared_buffer, BUFFER_SIZE);

    Declare_Shared2(sem_t, _full_);
    Declare_Shared2(sem_t, _empty_);
    sem_init(_full_, 1, 0);
    sem_init(_empty_, 1, 0);

    Declare_Shared2(int, read_file_count);
    Declare_Shared2(int, num_read);
    *read_file_count = 0;

    switch (fork()) {
    default:
        while (*read_file_count < file_count) {
            FILE * fp = fopen(file_paths[*read_file_count], "r");
            if (fp == NULL) {
                fprintf(stderr, "Failed to open file %s!\n", file_paths[*read_file_count]);
            } else {
                while ((*num_read = fread(shared_buffer, sizeof(*shared_buffer), BUFFER_SIZE, fp)) > 0) {
                    sem_post(_full_);
                    printf("[Parent] Buffer of size %d from %p contains: %.*s\n",
                        *num_read, shared_buffer, *num_read, shared_buffer);
                    sem_wait(_empty_);
                }
            }
            ++*read_file_count;
        }
        sem_post(_full_);
        break;

    case 0:
        sem_wait(_full_);
        while (*read_file_count < file_count) {
            printf("[Child]  Buffer of size %d from %p contains: %.*s\n",
                *num_read, shared_buffer, *num_read, shared_buffer);
            sem_post(_empty_);
            sem_wait(_full_);
        }
        break;

    case -1:
        fprintf(stderr, "Fork failed!\n");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < file_count; ++i) {
        free(file_paths[i]);
    }
}

void traverseDir(char * dir_name)
{
    DIR * dir;
    if ((dir = opendir(dir_name)) != NULL) {
        struct dirent *ent;
        while ((ent = readdir(dir)) != NULL) {
            size_t ent_name_len = strlen(ent->d_name);
            static char const accepted_ext[] = ".txt";
            size_t accepted_ext_len = sizeof(accepted_ext) - 1;
            if (ent->d_type == DT_DIR) {
                if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
                    char new_dir_name[strlen(dir_name) + ent_name_len + 2];
                    sprintf(new_dir_name, "%s/%s", dir_name, ent->d_name);
                    traverseDir(new_dir_name);
                }
            }
            else if (ent_name_len >= accepted_ext_len &&
                    memcmp(ent->d_name + ent_name_len - accepted_ext_len, accepted_ext, accepted_ext_len) == 0) {
                char * new_file_path = (char *)malloc(strlen(dir_name) + ent_name_len + 2);
                sprintf(new_file_path, "%s/%s", dir_name, ent->d_name);
                file_paths[file_count] = new_file_path;
                ++file_count;
            }
        }
        closedir(dir);
    } else {
        perror("");
    }
}

But you can get the same result with threads, e.g. with C11 threads (note: to avoid polluting the global scope with many global variables, you should probably put your variables in a struct):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <dirent.h>

#define BUFFER_SIZE 3ul

void traverseDir(char * dir_name);

int child_func(void * arg);

char * file_paths[100];
int file_count = 0;

char shared_buffer[BUFFER_SIZE];

mtx_t full, empty;

int read_file_count = 0, num_read;

int main(int argc, char * * argv)
{
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <dir_name>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    traverseDir(argv[1]);
    if (file_count <= 0 || 100 < file_count) {
        fprintf(stderr, "%d .txt files found in the directory; must be more than 0 and no more than 100.\n", file_count);
        exit(EXIT_FAILURE);
    }

    printf("Found %d files in the source directory.\n", file_count);
    for (int i = 0; i < file_count; ++i) {
        printf("%s\n", file_paths[i]);
    }

    mtx_init(&full, mtx_plain);
    mtx_init(&empty, mtx_plain);

    thrd_t thr;
    if (thrd_create(&thr, &child_func, NULL) != thrd_success) {
        fprintf(stderr, "Thread creation failed!\n");
        exit(EXIT_FAILURE);
    }

    while (read_file_count < file_count) {
        FILE * fp = fopen(file_paths[read_file_count], "r");
        if (fp == NULL) {
            fprintf(stderr, "Failed to open file %s!\n", file_paths[read_file_count]);
        } else {
            while ((num_read = fread(shared_buffer, sizeof(*shared_buffer), BUFFER_SIZE, fp)) > 0) {
                mtx_unlock(&full);
                printf("[Parent] Buffer of size %d from %p contains: %.*s\n",
                    num_read, shared_buffer, num_read, shared_buffer);
                mtx_lock(&empty);
            }
        }
        ++read_file_count;
    }
    mtx_unlock(&full);

    int child_res;
    thrd_join(thr, &child_res);

    mtx_destroy(&full);
    mtx_destroy(&empty);

    for (int i = 0; i < file_count; ++i) {
        free(file_paths[i]);
    }
}

int child_func(void * arg)
{
    mtx_lock(&full);
    while (read_file_count < file_count) {
        printf("[Child]  Buffer of size %d from %p contains: %.*s\n",
            num_read, shared_buffer, num_read, shared_buffer);
        mtx_unlock(&empty);
        mtx_lock(&full);
    }
    return 0;
}

void traverseDir(char * dir_name)
{
    DIR * dir;
    if ((dir = opendir(dir_name)) != NULL) {
        struct dirent *ent;
        while ((ent = readdir(dir)) != NULL) {
            size_t ent_name_len = strlen(ent->d_name);
            static char const accepted_ext[] = ".txt";
            size_t accepted_ext_len = sizeof(accepted_ext) - 1;
            if (ent->d_type == DT_DIR) {
                if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) {
                    char new_dir_name[strlen(dir_name) + ent_name_len + 2];
                    sprintf(new_dir_name, "%s/%s", dir_name, ent->d_name);
                    traverseDir(new_dir_name);
                }
            }
            else if (ent_name_len >= accepted_ext_len &&
                    memcmp(ent->d_name + ent_name_len - accepted_ext_len, accepted_ext, accepted_ext_len) == 0) {
                char * new_file_path = (char *)malloc(strlen(dir_name) + ent_name_len + 2);
                sprintf(new_file_path, "%s/%s", dir_name, ent->d_name);
                file_paths[file_count] = new_file_path;
                ++file_count;
            }
        }
        closedir(dir);
    } else {
        perror("");
    }
}

Threads are a bit easier to use, and use much less resources than forks (for the reasons explained in the link I provided at the beginning of my answer). Indeed they were created for multiprocessing for which threads are sometimes created and destroyed very frequently so the heaviness of forks is very inappropriate. They are also more portable: provided by many libraries for a long time, and in the C standard since 2011. (But dirent.h is not standard: it’s a Unix function.)