cipc

`shmget` Invalid argument error -- is memory still allocated from a previous execution?


I have the following program to exercise using forks and shared memory.

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/wait.h>

#include <sys/shm.h>
#include <sys/ipc.h>

int main() {
    int bignum = 1000000;
    
    key_t key = ftok(".", 'x');
    int shmid = shmget(key, sizeof(int)*bignum, IPC_CREAT | 0666);
    if (shmid < 0) {
        perror("shmget\n");
        return 1;
    }
    int *arr = shmat(shmid, NULL, 0);
    pid_t c1 = fork();
    if (c1==0) {
        pid_t c2 = fork();
        if (c2==0) {
            pid_t c3 = fork();
            if (c3==0) {
                arr[0] = 10;
            } else {
                arr[1] = 11;
            }
            wait(NULL);
            exit(0);
        } else {
            arr[2] = 12;
        }
        wait(NULL);
        exit(0);
    } else {
        arr[3] = 13;
        wait(NULL);

        for (int i=0; i<4; i++) printf("%d ", arr[i]);
        printf("\n");
    }


    shmdt(arr);
    shmctl(shmid, IPC_RMID, NULL);
    exit(0);
}

I previously ran this program using a smaller amount of shared memory and have now increased it to see if that affects the program in any way. When I run this I get:

shmget
: Invalid argument

I've seen this post: C linux shmget Invalid argument

I've tried following the advice, but I'm unfamiliar with ipcs and ipcrm. I ran ipcs in a terminal and it gave me some shared memory info. But I can't tell which is the one allocated by the program, there's a lot, and I can't tell what is safe to remove or not.

It also seems odd to me that you wouldn't just do this inside the C program itself, so that makes me wonder if there isn't a better way to resolve this issue. I particularly don't get why the calls to shmdt and shmctl don't make this a non-issue. Is there some other way to "undo" memory sharing?


Edit: The question was closed because it is similar to this:

C - System V - remove shared memory segment

However, that uses code that is already in my code -- so it doesn't seem to answer the question.


Edit: Here is my updated code based on an answer. It does resolve the previous issue, but now after some error checking, raises a permission error.

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/wait.h>

#include <sys/shm.h>
#include <sys/ipc.h>

int main() {
    size_t bignum = 1000000*sizeof(int);
    
    key_t key = ftok(".", 'x');
    int shmid = shmget(key, bignum, IPC_CREAT | 0666);
    if (shmid == -1) {
        shmctl(shmid, IPC_RMID, NULL);
        shmid = shmget(key, bignum, IPC_CREAT | 0666);
        if (shmid == -1) {
            perror("shmget");
            exit(1);
        }
    }
    int *arr = shmat(shmid, NULL, 0);
    if (arr == (int *)-1) {
        perror("shmat");
        exit(1);
    }

    shmdt(arr);
    shmctl(shmid, IPC_RMID, NULL);

    shmid = shmget(key, bignum, IPC_CREAT | 0666);
    arr = shmat(shmid, NULL, 0);

    pid_t c1 = fork();
    if (c1==0) {
        pid_t c2 = fork();
        if (c2==0) {
            pid_t c3 = fork();
            if (c3==0) {
                arr[0] = 10;
            } else {
                arr[1] = 11;
            }
            wait(NULL);
            exit(0);
        } else {
            arr[2] = 12;
        }
        wait(NULL);
        exit(0);
    } else {
        arr[3] = 13;
        wait(NULL);

        for (int i=0; i<4; i++) printf("%d ", arr[i]);
    }

    if (shmdt(arr) == -1) {
        perror("shmdt");
        exit(1);
    }
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl");
        exit(1);
    }

    exit(0);
}

Specifically, when I run this it results in

shmget: Permission denied

Solution

  • The old program left a smaller shared memory segment lingering after it exited so attaching to it, requesting a bigger size, fails. You can start your new program by removing it:

    key_t key = ftok(".", 'x');
    int shmid = shmget(key, 1, 0666); // no IPC_CREAT
    
    if (shmid != -1) { // ok, there was a memory segment there already
        shmctl(shmid, IPC_RMID, NULL); // remove it
    }
    // now create the new one
    shmid = shmget(key, sizeof(int)*bignum, IPC_CREAT | 0666);