Home > Software engineering >  Can't write to fd from shm_open on MacOS
Can't write to fd from shm_open on MacOS

Time:09-18

I am trying to write to and then read from a file descriptor opened using shm_open. It works as I expect on Linux, but not on macOS (specifically macOS Monterey 12.5 21G72). Here is the code:

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

int main(int argc, const char * argv[]) {
    int fd = shm_open("/example", O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
    if (fd < 0) {
        printf("shm_open() failed %s (%d)\n", strerror(errno), errno);
        return 1;
    }
    
    const char *buf = "hello world";
    unsigned long len = strlen(buf);
        
    ssize_t ret = write(fd, buf, len);
    if (ret < 0) {
        printf("write() failed %s (%d)\n", strerror(errno), errno);
        return 1;
    }
    
    ret = lseek(fd, 0, SEEK_SET);
    if (ret < 0) {
        printf("lseek() failed %s (%d)\n", strerror(errno), errno);
        return 1;
    }
    
    char *newbuf = calloc(len   1, 1);
    
    ret = read(fd, newbuf, len);
    if (ret < 0) {
        printf("read() failed %s (%d)\n", strerror(errno), errno);
        return 1;
    }
    
    printf("read: %s\n", newbuf);
    return 0;
}

On Linux the output is what I would expect:

$ cc main.c
$ ./a.out
read: hello world

On macOS I get this:

$ cc main.c
$ ./a.out
write() failed Device not configured (6)

CodePudding user response:

Under Linux, the POSIX shared memory is typically backed by a tmpfs file system mounted on /dev/shm:

$ cat /proc/mounts | grep /dev/shm
tmpfs /dev/shm tmpfs rw,nosuid,nodev,inode64 0 0

The name passed to shm_open() is the name of the file entry corresponding to the shared memory area:

$ gcc main.c -lrt  
$ ./a.out 
read: hello world
$ ls -l /dev/shm
total 4
-rw------- 1 xxx xxx 11 sept.  17 08:53 example

The above file system is typically mounted at startup through /etc/fstab or by systemd.

Under MacOS, this manual says that there is no visible entry in the file system for the shared memory segments:

There is no visible entry in the file system for the created object in this implementation.

So, the low level implementation is different than the one in Linux. You may only be able to access the shared memory by adding a call to ftruncate() to set the size of the memory segment and use mmap() to map the content into the process address space as it is the way we normally use the shared memory. Any process wanting to access the area, will do the same except that only one should specify O_CREAT to shm_open() and call ftruncate() to create/resize the object:

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

int main(int argc, const char * argv[]) {

    int flags = O_RDWR;

    // Pass some parameter to trigger the creation of the object
    if (argc > 1) {
      flags  |= O_CREAT;
    }

    int fd = shm_open("/example", flags, S_IRUSR|S_IWUSR);
    if (fd < 0) {
        printf("shm_open() failed %s (%d)\n", strerror(errno), errno);
        return 1;
    }
    
    const char *buf = "hello world";
    unsigned long len = strlen(buf);

    if (argc > 1) {

      ssize_t ret = ftruncate(fd, len   1);
      if (ret < 0) {
          printf("ftruncate() failed %s (%d)\n", strerror(errno), errno);
          return 1;
      }

    }

    char *newbuf = (char *)mmap(NULL, len   1, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (newbuf == MAP_FAILED) {
      printf("mmap() failed %s (%d)\n", strerror(errno), errno);
      return 1;
    }

    if (argc > 1) {

      memcpy(newbuf, buf, len   1);

    }
  
    printf("read: %s\n", newbuf);
    return 0;
}

Example of execution under Linux:

$ gcc main.c -lrt
$ ls -l /dev/shm
total 0
$ ./a.out
shm_open() failed No such file or directory (2)
$ ls -l /dev/shm
total 0
$ ./a.out creat
read: hello world
$ ls -l /dev/shm
total 4
-rw------- 1 xxx xxx 12 sept.  17 09:36 example
$ ./a.out      
read: hello world

Additional information

MacOS is derived from BSD. The manual of the latter clearly specifies that operations like read() or write() on the resulting file descriptor return in error:

The result of using open(2), read(2), or write(2) on a shared memory object, or on the descriptor returned by shm_open(), is undefined. It is also undefined whether the shared memory object itself, or its contents, persist across reboots.
In FreeBSD, read(2) and write(2) on a shared memory object will fail with EOPNOTSUPP and neither shared memory objects nor their contents persist across reboots.

  • Related