JAN
JAN

Reputation: 21865

Shared memory is not so shared with processes in C

While trying to resolve some debugging issues , I added some printf-s to my code :

I used that code :

struct PipeShm
{
    int init;
    sem_t sema;
        ...
        ...
}

struct PipeShm * sharedPipe = NULL;

func2:

int func2()
{
if (!sharedPipe)
{

    int myFd = shm_open ("/myregion", O_CREAT | O_TRUNC | O_RDWR, 0666);
    if (myFd == -1)
        error_out ("shm_open");

    // allocate some memory in the region in the size of the struct
    int retAlloc = ftruncate (myFd, sizeof * sharedPipe);
    if (retAlloc < 0)  // check if allocation failed
        error_out("ftruncate");

    // map the region and shared in with all the processes
    sharedPipe = mmap (NULL, sizeof * sharedPipe,PROT_READ | PROT_WRITE,MAP_SHARED , myFd, 0);

    if (sharedPipe == MAP_FAILED)  // check if the allocation failed
        error_out("mmap");

    // put initial value
    int value = -10;
    // get the value of the semaphore
    sem_getvalue(&sharedPipe->semaphore, &value);


    if (sharedPipe->init != TRUE) // get in here only if init is NOT TRUE !
    {
        if (!sem_init (&sharedPipe->semaphore, 1, 1)) // initialize the semaphore to 0
        {

            sharedPipe->init = TRUE;
            sharedPipe->flag = FALSE;
            sharedPipe->ptr1 = NULL;
            sharedPipe->ptr2 = NULL;
            sharedPipe->status1 = -10;
            sharedPipe->status2 = -10;
            sharedPipe->semaphoreFlag = FALSE;
            sharedPipe->currentPipeIndex = 0;
            printf("\nI'm inside the critical section! my init is: %d\n" , sharedPipe->init);

        }
        else
            perror ("shm_pipe_init");
        printf("\nI'm out the critical section! my init is: %d\n" , sharedPipe->init);

    }


}
return 1;   // always successful
}

With that main :

int main()

{
    int spd, pid, rb;
    char buff[4096];
    fork();
    func2();
    return 0;
}

And got this :

shm_pipe_mkfifo: File exists

I'm inside the critical section! my init is: 1

I'm out the critical section! my init is: 1
Output:hello world!
I'm inside the critical section! my init is: 1

I'm out the critical section! my init is: 1

It seems that the shared memory is not so shared , why ?

  1. The segment is shared between all processes due to MAP_SHARED | MAP_ANONYMOUS , so why both processes have the same before and after values ?

  2. It seems that each process has its own semaphore even though it was shared between them , so what went wrong ?

Thanks

Upvotes: 2

Views: 1728

Answers (2)

Chris Dodd
Chris Dodd

Reputation: 126203

Since you use the MAP_ANONYMOUS flag to mmap, the myFd argument is ignored, and you create two independent shared memory chunks, one in each process, which have no relation to each other.

  MAP_ANONYMOUS
          The mapping is not backed by any file; its contents are initial‐
          ized to zero.  The fd and offset arguments are ignored; however,
          some implementations require fd to be -1  if  MAP_ANONYMOUS  (or
          MAP_ANON)  is specified, and portable applications should ensure
          this.  The use of MAP_ANONYMOUS in conjunction  with  MAP_SHARED
          is only supported on Linux since kernel 2.4.

If you get rid of MAP_ANONYMOUS you'll then only have one shared memory chunk, but you then have the problem of not calling sem_init. On Linux with NPTL it will actually work, as clearing a sem_t to all 0 bytes (the initial state here) is equivalent to sem_init(&sema, anything, 0); (NPTL ignores the pshared flag), but that's not portable to other systems.

Per Karoly's comment on another answer, there's also a race condition due O_TRUNC on the open call. If the second thread calls open after the first thread has already started modifying the semaphore, that TRUNC will clobber the semaphore state. Probably the best solution is to move the code creating, opening, and mmaping the shared memory to a different function that is called BEFORE calling fork.

edit

To fix the O_TRUNC problem, you can't have more than one process calling shm_open with O_TRUNC. But if you just get rid of the O_TRUNC, then you have the startup problem that if the shared memory object already exists (from a previous run of the program), it may not be in a predictable state. On possibility is to split off the beginning of func2:

main() {
    func1();
    fork();
    func2();
}

func1() {
    int myFd = shm_open ("/myregion", O_CREAT | O_TRUNC | O_RDWR, 0666);
    if (myFd == -1)
        error_out ("shm_open");
    // allocate some memory in the region in the size of the struct
    int retAlloc = ftruncate (myFd, sizeof *sharedPipe);
    if (retAlloc < 0)  // check if allocation failed
        error_out("ftruncate");
    // map the region and shared in with all the processes
    sharedPipe = mmap (NULL, sizeof *sharedPipe, PROT_READ|PROT_WRITE, MAP_SHARED, myFd, 0);
    if (sharedPipe == MAP_FAILED)  // check if the allocation failed
        error_out("mmap");
}

func2() {
    // put initial value
    int value = -10;
    // get the value of the semaphore
    sem_getvalue(&sharedPipe->semaphore, &value);

    :

Alternately you could keep the same code (just get rid of O_TRUNC) and add a cleanup before the fork:

main() {
    shm_unlink("/myregion");
    fork();
    func2();

In all cases you'll still have problem if you run multiple copies of your program at the same time.

Upvotes: 4

asveikau
asveikau

Reputation: 40226

A few thoughts...

  1. I think this is a fundemental misunderstanding of how POSIX semaphores work. I don't see a call to sem_init or sem_open. You shouldn't be able to use them across processes without doing so much more explicitly than you've done.

  2. I'm not so fresh on the implementation of mmap on Linux and how MAP_ANONYMOUS might affect this, but in general writes to mapped regions can't really be instantaneous. The manpage on linux.die says:

MAP_SHARED
Share this mapping. Updates to the mapping are visible to other processes that map this file, and are carried through to the underlying file. The file may not actually be updated until msync(2) or munmap() is called.

The reason for this is that your memory access gets trapped in a page fault and at that point the kernel will fill contents from the file descriptor, then let you do your write in RAM, then at some later point the kernel will flush back to the file descriptor.

Upvotes: 1

Related Questions