Search code examples
cunixposixsemaphoreshared-memory

How to write unnamed Posix Semaphore to Shared Memory?


I want to write a semaphore to shared memory. My first idea was to pass the pointer returned by mmap to sem_init():

#include <stdio.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
    sem_t *sem_ptr;
    int shm_fd = shm_open("Shm", O_CREAT | O_RDWR, DEFFILEMODE);
    fprintf(stderr, "%s\n", strerror(errno));
    sem_ptr = mmap(NULL, sizeof(sem_t), PROT_WRITE, MAP_SHARED, shm_fd, 0);
    fprintf(stderr, "%p\n", strerror(errno));
    sem_init(sem_ptr, 1, 1);
    fprintf(stderr, "%s\n", strerror(errno));

    sem_destroy(sem_ptr);
    return 0;
}

But it leads to this error(when sem_init() is called): Process finished with exit code 135 (interrupted by signal 7: SIGEMT)

Then I tried to initialize the semaphore with a sem_t variable and write it to the shared memory:

int main(void)
{
    sem_t *sem_ptr;
    sem_t s;
    int shm_fd = shm_open("Shm", O_CREAT | O_RDWR, DEFFILEMODE);
    fprintf(stderr, "%s\n", strerror(errno));
    sem_ptr = mmap(NULL, sizeof(sem_t), PROT_WRITE, MAP_SHARED, shm_fd, 0);
    fprintf(stderr, "%p\n", strerror(errno));
    sem_init(&s, 1, 1);
    fprintf(stderr, "%s\n", strerror(errno));

    *sem_ptr = s;

    sem_destroy(&s);
    return 0;
}

Now the line *sem_ptr = s; leads to the same error as in the first programm

Can anyone help me please?


Solution

  • Your first strategy for creating the semaphore is correct. You can't necessarily copy a sem_t object to a different memory address and have it still work.

    I'm not sure why you're getting SIGEMT, which I thought was never generated by modern Unixes. But when I run either of your programs on my computer, they crash with SIGBUS instead, and that pointed me at a bug that I know how to fix. When you mmap a file (a shared memory object is considered to be a file), and the size you ask for in the mmap call is bigger than the file, and then you access the memory area beyond the end of the file (by far enough that the CPU can trap this), you get a SIGBUS. And let me quote you a key piece of the shm_open manpage:

    O_CREAT: Create the shared memory object if it does not exist. [...]

    A new shared memory object initially has zero length—the size of the object can be set using ftruncate(2).

    What you need to do is call ftruncate on shm_fd to make the shared memory object big enough to hold the semaphore.

    Some less-important bugs you should fix at the same time:

    All of the system calls that work with memory maps may malfunction if you give them offsets or sizes that aren't a multiple of the system page size. (They're supposed to round up for you, but historically there have been a lot of bugs in this area.) You get the system page size by calling sysconf(_SC_PAGESIZE), and you round up with a little helper function shown below.

    Most C library functions are allowed to set errno to a nonzero value even if they succeed. You should check whether each function actually failed before printing strerror(errno). (In the code below I used perror instead for brevity.)

    The name of a shared memory object is required to start with a slash, followed by up to NAME_MAX characters that are not slashes.

    sem_init may read from as well as writing to the memory pointed to by sem_ptr, and subsequent use of sem_wait and sem_post definitely will, so you should use PROT_READ|PROT_WRITE in the mmap call.

    Putting it all together, this is a revised version of your first program which works on my computer. Because of the SIGEMT thing I can't promise it will work for you.

    #include <fcntl.h>
    #include <semaphore.h>
    #include <stdio.h>
    #include <sys/mman.h>
    #include <unistd.h>
    
    #ifndef DEFFILEMODE
    # define DEFFILEMODE 0666
    #endif
    
    static long round_up(long n, long mult)
    {
        return ((n + mult - 1) / mult) * mult;
    }
    
    int main(void)
    {
        long pagesize;
        long semsize;
        sem_t *sem_ptr;
        int shm_fd;
    
        pagesize = sysconf(_SC_PAGESIZE);
        if (pagesize == -1) {
            perror("sysconf(_SC_PAGESIZE)");
            return 1;
        }
    
        shm_fd = shm_open("/Shm", O_CREAT|O_RDWR, DEFFILEMODE);
        if (shm_fd == -1) {
            perror("shm_open");
            return 1;
        }
    
        semsize = round_up(sizeof(sem_t), pagesize);
        if (ftruncate(shm_fd, semsize) == -1) {
            perror("ftruncate");
            return 1;
        }
    
        sem_ptr = mmap(0, semsize, PROT_READ|PROT_WRITE, MAP_SHARED, shm_fd, 0);
        if (sem_ptr == MAP_FAILED) {
            perror("mmap");
            return 1;
        }
        if (sem_init(sem_ptr, 1, 1)) {
            perror("sem_init");
            return 1;
        }
    
        sem_destroy(sem_ptr);
        shm_unlink("/Shm");
        return 0;
    }
    

    An additional complication you should be aware of is that calling sem_init on a semaphore that has already been initialized causes undefined behavior. This means you have to use some other kind of locking around the creation of the shared memory segment and the semaphore within. Off the top of my head I don't know how to do this in a bulletproof way.