Search code examples
clinuxipcsemaphoreshared-memory

Multiple tests in shared memory between parent and child in C


I am writing a program that does multiple writing and reading tests between a parent and a child processes (One of them has the work of writing in a shared memory block and the other one has the work of reading what the other process wrote), well, I saw that this is a producer-consumer problem that would be synchronized by semaphores but I don't get it to work properly (I got them to turn each other for the actions they have to do but it gets a time when they just de synchronize and the reading line doesn't catch what the other process writes). Hope someone could help me using pretty much anything else than shm and sem.

The following is my main code:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <fcntl.h>

#include "../includes/shared_memory.h"

#define SIZES 6
#define KB 1024
#define SHM_KEY 0x1234

//int PACK_SIZES[SIZES] = {1*KB, 10*KB, 100*KB, 1*1000*KB, 10*1000*KB, 100*1000*KB};
int PACK_SIZES[SIZES] = {1, 2, 3, 4, 5, 6};
int PACK_TESTS[SIZES] = {6,5,4,3,2,1};

int fill(char *bufptr, int size);

int main(){
    int size;
    int tests_amount;
    pid_t pid;

    sem_t *sem1;
    sem_t *sem2;

    sem1 = sem_open("/semaphore1", O_CREAT,  0644, 0);
    sem2 = sem_open("/semaphore2", O_CREAT,  0644, 1);

    pid = fork();
    for (int i = 0; i < SIZES; i++){
        tests_amount = PACK_TESTS[i];
        size = PACK_SIZES[i];

        int status;
        int cnt;

        char *block = attach_shm(FILENAME, size);
        printf("Bloque de tamaño %d creado.\n", size);
        if (block == NULL) {
            printf("No se pudo abrir la memoria compartida\n");
            return -1;
        }

        for (int j = 1; j <= tests_amount; j++){
            if (pid == 0){
                //Productor
                sem_wait(sem2);
                printf("Escritura ShM. Test #%d/%d. Tamaño: %d Bytes.\n", j,tests_amount,size);
                cnt = fill(block, size);
                sem_post(sem1);
            } else {
                //Consumidor
                sem_wait(sem1);
                printf("Lectura ShM. Test #%d/%d. Tamaño: %d Bytes.\n", j,tests_amount,size);
                printf("Contenido: \"%s\"\n\n", block);
                sem_post(sem2);
            }
        }

        detach_shm(block);
        
        if (destroy_shm(FILENAME)){
            printf("Bloque destruido.\n\n");
        } else {
            printf("No se pudo destruir el bloque.\n");
        }
    }
    sem_close(sem1);
    sem_unlink("/semaphore1");
    sem_close(sem2);
    sem_unlink("/semaphore2");
    return 0;
}

int fill(char * bufptr, int size){
    static char ch = 'A';
    int filled_count;

    printf("size is %d\n", size);
    memset(bufptr, ch, size);
    
    if (ch > 90)
        ch = 65;
    
    filled_count = strlen(bufptr);

    printf("Bytes escritos: %d\n\n", filled_count);
    //printf("buffer filled is:%s\n", bufptr);
    ch++;
    return filled_count;
}

The next is shared_memory.h

#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H

#include <stdbool.h>

char * attach_shm(char *filename, int size);
bool detach_shm(char *block);
bool destroy_shm(char *filename);

#define FILENAME "../src/productor.c"

#endif

The next is shared_memory.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include "shared_memory.h"

static int get_shm(char *filename, int size) {
    key_t key;

    key = ftok(filename, 0);

    if (key < 0) {
        perror("Error en Key");
        return -1;
    }

    return shmget(key, size, 0644 | IPC_CREAT);
}

char * attach_shm(char *filename, int size) {
    int shmId = get_shm(filename, size);
    char *ptr;

    if (shmId < 0){
        perror("Error en shmget");
        return NULL;
    }

    ptr = (char *) shmat(shmId, NULL, 0);

    if (ptr < 0){
        perror("Error en shmat");
        return NULL;
    }

    return ptr;
}

bool detach_shm(char *block){
    return (shmdt(block) != -1);
}

bool destroy_shm(char *filename) {
    int shmId = get_shm(filename, 0);

    if (shmId < 0){
        perror("Error en shmget");
        return NULL;
    }
    return (shmctl(shmId, IPC_RMID, NULL) != -1);
}

And the following is one of the possible output I get:

Bloque de tamaño 1 creado.
Bloque de tamaño 1 creado.
Escritura ShM. Test #1/6. Tamaño: 1 Bytes.
size is 1
Bytes escritos: 1

Lectura ShM. Test #1/6. Tamaño: 1 Bytes.
Contenido: "A"

Escritura ShM. Test #2/6. Tamaño: 1 Bytes.
size is 1
Bytes escritos: 1

Lectura ShM. Test #2/6. Tamaño: 1 Bytes.
Contenido: "B"

Escritura ShM. Test #3/6. Tamaño: 1 Bytes.
size is 1
Bytes escritos: 1

Lectura ShM. Test #3/6. Tamaño: 1 Bytes.
Contenido: "C"

Escritura ShM. Test #4/6. Tamaño: 1 Bytes.
size is 1
Bytes escritos: 1

Lectura ShM. Test #4/6. Tamaño: 1 Bytes.
Contenido: "D"

Escritura ShM. Test #5/6. Tamaño: 1 Bytes.
size is 1
Bytes escritos: 1

Lectura ShM. Test #5/6. Tamaño: 1 Bytes.
Contenido: "E"

Escritura ShM. Test #6/6. Tamaño: 1 Bytes.
size is 1
Bytes escritos: 1

Lectura ShM. Test #6/6. Tamaño: 1 Bytes.
Bloque destruido.

Contenido: "F"

Bloque de tamaño 2 creado.
Escritura ShM. Test #1/5. Tamaño: 2 Bytes.
size is 2
Bytes escritos: 2

Bloque destruido.

Bloque de tamaño 2 creado.
Lectura ShM. Test #1/5. Tamaño: 2 Bytes.
Contenido: ""

Escritura ShM. Test #2/5. Tamaño: 2 Bytes.
size is 2
Bytes escritos: 2

Lectura ShM. Test #2/5. Tamaño: 2 Bytes.
Contenido: ""

Escritura ShM. Test #3/5. Tamaño: 2 Bytes.
size is 2
Bytes escritos: 2

Lectura ShM. Test #3/5. Tamaño: 2 Bytes.
Contenido: ""

Escritura ShM. Test #4/5. Tamaño: 2 Bytes.
size is 2
Bytes escritos: 2

Lectura ShM. Test #4/5. Tamaño: 2 Bytes.
Contenido: ""

Escritura ShM. Test #5/5. Tamaño: 2 Bytes.
size is 2
Bytes escritos: 2

Lectura ShM. Test #5/5. Tamaño: 2 Bytes.
Contenido: ""

Bloque destruido.

Bloque de tamaño 3 creado.
Escritura ShM. Test #1/4. Tamaño: 3 Bytes.
size is 3
Bytes escritos: 3

Bloque destruido.

Bloque de tamaño 3 creado.
Lectura ShM. Test #1/4. Tamaño: 3 Bytes.
Contenido: ""

Escritura ShM. Test #2/4. Tamaño: 3 Bytes.
size is 3
Bytes escritos: 3

Lectura ShM. Test #2/4. Tamaño: 3 Bytes.
Contenido: ""

Escritura ShM. Test #3/4. Tamaño: 3 Bytes.
size is 3
Bytes escritos: 3

Lectura ShM. Test #3/4. Tamaño: 3 Bytes.
Contenido: ""

Escritura ShM. Test #4/4. Tamaño: 3 Bytes.
size is 3
Bytes escritos: 3

Lectura ShM. Test #4/4. Tamaño: 3 Bytes.
Contenido: ""

Bloque destruido.

Bloque de tamaño 4 creado.
Escritura ShM. Test #1/3. Tamaño: 4 Bytes.
size is 4
Bytes escritos: 4

Bloque destruido.

Bloque de tamaño 4 creado.
Lectura ShM. Test #1/3. Tamaño: 4 Bytes.
Contenido: ""

Escritura ShM. Test #2/3. Tamaño: 4 Bytes.
size is 4
Bytes escritos: 4

Lectura ShM. Test #2/3. Tamaño: 4 Bytes.
Contenido: ""

Escritura ShM. Test #3/3. Tamaño: 4 Bytes.
size is 4
Bytes escritos: 4

Lectura ShM. Test #3/3. Tamaño: 4 Bytes.
Contenido: ""

Bloque destruido.

Bloque de tamaño 5 creado.
Escritura ShM. Test #1/2. Tamaño: 5 Bytes.
Bloque destruido.
size is 5

Bytes escritos: 5

Bloque de tamaño 5 creado.
Lectura ShM. Test #1/2. Tamaño: 5 Bytes.
Contenido: ""

Escritura ShM. Test #2/2. Tamaño: 5 Bytes.
size is 5
Bytes escritos: 5

Lectura ShM. Test #2/2. Tamaño: 5 Bytes.
Contenido: ""

Bloque destruido.

Bloque de tamaño 6 creado.
Escritura ShM. Test #1/1. Tamaño: 6 Bytes.
size is 6
Bloque destruido.
Bytes escritos: 6


Bloque destruido.

Bloque de tamaño 6 creado.
Lectura ShM. Test #1/1. Tamaño: 6 Bytes.
Contenido: ""

Error en shmget: Invalid argument
No se pudo destruir el bloque.

Solution

  • You have race conditions due to both processes sharing the concern of creating and destroying the shared memory segments, in an uncontrolled order.

    Error en shmget: Invalid argument

    One process can get ahead of the other and attempt to attach_shm(FILENAME, size) with the next size. While this is happening, the other process may still be attached to the existing shared memory. Thus,

    EINVAL A segment for the given key exists, but size is greater than the size of that segment.

    Shared memory is only actually removed when the last process detaches. See shmctl(2) for details.

    You need to carry the lockstep outside the inner loop, so that it protects the order of creation and destruction. Only one process needs to concern itself with scheduling the destruction of the shared memory segment.

    In pseudocode:

    sem1 := semaphore(0)
    sem2 := semaphore(1)
    
    for each size:
        if child: wait sem2
        if parent: wait sem1
        
        block := get_shared_memory(id, size)
    
        if child: post sem1
        if parent: post sem2
    
        for each test:
            if child:
                wait sem2
                write_to(block, size)
                post sem1
            if parent:
                wait sem1
                read_from(block, size)
                post sem2
    
        if child:
            wait sem2
            detach(block)
            schedule_destruction(id)
            post sem1
        if parent:
            wait sem1
            detach(block)
            post sem2
    

    A separate issue exists with the fill function.

    memset(bufptr, ch, size); completely fills the shared memory segment with ch. There is no room for the null-terminating byte (nor do you place one). Thus, strlen(bufptr); and printf("%s", block); are Undefined Behaviour.

    Various other issues exist, like unused or pointless variables (status, cnt), unchecked errors (fork, sem_open), error cascading (if ftok fails, several layers of functions each report the same error) and misrepresented values. As pointed out in the comments, shmat returns (void *) -1 on error, and likewise ftok returns (key_t) -1.