Search code examples
cmemory-managementoperating-systemforkshared-memory

Fibonacci sequence generator with fork and shared memory


I'm struggling with a homework problem. I'm asked to:

Write a program whose main routine obtains one parameter n from the user (n<40), i.e. passed to your program when it was invoked from the shell. Your program shall then create a shared memory and a child process. The shared memory shall have a size of BUF_SZ*sizeof(unsigned short), where BUF_SZ is defined as 5 using a macro, e.g. “#define BUF_SZ 5”.

The child process should obtain the value of n from the parent (you actually have multiple options for doing that) and create a Fibonacci sequence of length n and whose elements are of type unsigned short. You may find more information about Fibonacci numbers at (https://en.wikipedia.org/wiki/Fibonacci_number).

The child process shall create the elements, one at a time, and wait for a random interval of time ( 0 <= time < 2 seconds) between generating elements of the sequence. As soon as an element is generated, the child places the element in the shared memory by organizing it as described in class.

The parent process shall print elements it receives on the shared butter immediately, without waiting for the child process to exit.

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>


#define BUF_SZ 5
#define NAME "buffer"
#define NAME2 "inptr"
#define NAME3 "outptr"

int main() {
    int fd[2];
    int n = atoi(argv[1]);
    unsigned short *data;
    int* inptr;
    int* outptr;
    int size = BUF_SZ * sizeof(unsigned short);
    int size2 = sizeof(int);
    
    /*while(n >= 40 || n <= 0) {
        printf("Enter a positive integer less than 40: ");
        scanf("%i", &n);
    }*/
    
    int shmid = shm_open(NAME, O_CREAT|O_RDWR, 0666);
    ftruncate(shmid, size);
    data = (unsigned short*) mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, shmid, 0);
    
    int shmid2 = shm_open(NAME2, O_CREAT|O_RDWR, 0666);
    ftruncate(shmid2, size2);
    inptr = (int*) mmap(NULL, size2, PROT_READ|PROT_WRITE, MAP_SHARED, shmid2, 0);
    
    int shmid3 = shm_open(NAME2, O_CREAT|O_RDWR, 0666);
    ftruncate(shmid3, size2);
    outptr = (int*) mmap(NULL, size2, PROT_READ|PROT_WRITE, MAP_SHARED, shmid3, 0);
    
    memset(inptr, 0, size2); //in variable
    memset(outptr, 0, size2); //out variable
    
    pipe(fd);
    pid_t pid = fork();
    
    if(pid > 0) {                   
        write(fd[1], &n, sizeof(n)); //write n value to child   
        while (*inptr == *outptr);
        printf("%i\n", data[*outptr]);
        fflush(stdout);
        *outptr = (*outptr + 1) % BUF_SZ;           
    }
    else if(pid == 0) {
        read(fd[0], &n, sizeof(n)); //get n value from parent
        int prev = 0;
        int curr = 1;
        int tmp;
    
        for(int i = 0; i <= n; i++) {       
            while (((*inptr + 1) % BUF_SZ) == *outptr);
    
            if(i == 0) {
                data[*inptr] = 0;
                *inptr = (*inptr + 1) % BUF_SZ; 
            }
            else if(i == 1) {
                data[*inptr] = 1;
                *inptr = (*inptr + 1) % BUF_SZ; 
            }
        
            tmp = curr;
            curr += prev;
            prev = tmp;
            data[*inptr] = curr;
            *inptr = (*inptr + 1) % BUF_SZ;
        }
    }
    else {
        printf("failed");
    }
    
    return 0;

}

I created shared memory buffers for the elements of the fib sequence and in and out pointers. In writes to the buffer and out reads from the buffer. The parent process checks if the in and out pointers are equal or not. When they are not equal, it reads from the buffer. The child process outputs elements to the buffer when the in pointer is one ahead of the out pointer. The issue is that the parent process gets stuck in an infinite while loop and it doesn't switch to the child process. I'm not sure how to proceed.


Solution

  • A few issues ...

    1. You only grab a single number in the parent. You need an outer while loop.
    2. Your last shm_open uses NAME2 instead of NAME3. This causes inptr and outptr to point to the same memory cell. So, they "alias" each other and they will always be the same value.
    3. inptr and outptr should have volatile on them [as dimich mentioned].
    4. You need int main(int argc,char **argv)
    5. The parent will loop infinitely after receiving the final number from the child.
    6. Although I didn't do it in the code below, the child could send one more final value that is a sentinel (to tell the parent that it's the end). When the parent sees the sentinel, it breaks out of the outer loop (and then does a wait(NULL) to reap the child).
    7. Since fibonacci numbers can't be 0, it makes for an okay sentinel value.

    Here is the corrected code. It is annotated. I've added some debug prints to help:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/shm.h>
    #include <sys/mman.h>
    
    #define BUF_SZ 5
    #define NAME "buffer"
    #define NAME2 "inptr"
    #define NAME3 "outptr"
    
    #if DEBUG
    #define dbgprt(_fmt...) \
        printf(_fmt)
    #else
    #define dbgprt(_fmt...) \
        do { } while (0)
    #endif
    
    int
    main(int argc,char **argv)
    {
        int fd[2];
        int n = atoi(argv[1]);
        unsigned short *data;
    // NOTE/BUG: without volatile the optimizer could incorrectly elide code
    #if 0
        int *inptr;
        int *outptr;
    #else
        volatile int *inptr;
        volatile int *outptr;
    #endif
        int size = BUF_SZ * sizeof(unsigned short);
        int size2 = sizeof(int);
    
        /* while(n >= 40 || n <= 0) { printf("Enter a positive integer less than 40: "); scanf("%i", &n); } */
    
        int shmid = shm_open(NAME, O_CREAT | O_RDWR, 0666);
        ftruncate(shmid, size);
        data = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0);
    
        int shmid2 = shm_open(NAME2, O_CREAT | O_RDWR, 0666);
        ftruncate(shmid2, size2);
        inptr = mmap(NULL, size2, PROT_READ | PROT_WRITE, MAP_SHARED, shmid2, 0);
    
    // NOTE/BUG: this uses the same file as shmid2, so inptr are aliased together
    #if 0
        int shmid3 = shm_open(NAME2, O_CREAT | O_RDWR, 0666);
    #else
        int shmid3 = shm_open(NAME3, O_CREAT | O_RDWR, 0666);
    #endif
        ftruncate(shmid3, size2);
        outptr = mmap(NULL, size2, PROT_READ | PROT_WRITE, MAP_SHARED, shmid3, 0);
    
    #if 0
        memset((void *) inptr, 0, size2);           // in variable
        memset((void *) outptr, 0, size2);          // out variable
    #else
        *inptr = 0;
        *outptr = 0;
    #endif
    
        pipe(fd);
        pid_t pid = fork();
    
        if (pid > 0) {
            write(fd[1], &n, sizeof(n));    // write n value to child
    // NOTE/FIX: we need an outer loop to get all numbers from the child
            while (1) {
                while (*inptr == *outptr);
                printf("parent: %i\n", data[*outptr]);
                fflush(stdout);
                *outptr = (*outptr + 1) % BUF_SZ;
                dbgprt("parent: inptr=%d outptr=%d\n",*inptr,*outptr);
            }
        }
        else if (pid == 0) {
            read(fd[0], &n, sizeof(n));     // get n value from parent
            int prev = 0;
            int curr = 1;
            int tmp;
    
            dbgprt("child: n=%d\n",n);
    
            for (int i = 0; i <= n; i++) {
                while (((*inptr + 1) % BUF_SZ) == *outptr);
    
                if (i == 0) {
                    data[*inptr] = 0;
                    dbgprt("child: i0\n");
                    *inptr = (*inptr + 1) % BUF_SZ;
                }
                else if (i == 1) {
                    dbgprt("child: i1\n");
                    data[*inptr] = 1;
                    *inptr = (*inptr + 1) % BUF_SZ;
                }
    
                tmp = curr;
                curr += prev;
                prev = tmp;
                printf("curr=%d\n",curr);
                data[*inptr] = curr;
    
                *inptr = (*inptr + 1) % BUF_SZ;
                dbgprt("child: inptr=%d outptr=%d\n",*inptr,*outptr);
            }
        }
        else {
            printf("failed");
        }
    
        return 0;
    
    }
    

    In the code above, I've used cpp conditionals to denote old vs. new code:

    #if 0
    // old code
    #else
    // new code
    #endif
    
    #if 1
    // new code
    #endif
    

    Note: this can be cleaned up by running the file through unifdef -k