7

I want to share data between two (ndk-)processes. For this I use ashmem using this source.
One process is continuously reading (read_mem) and one process is writing one time (write_mem).

The problem is that the read process is not getting the values of the writer.

AND

By watching the maps of the reader I found that android deletes the shared memory file right after ashmem_create_region.


read_mem.c

// read_mem.c
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include "ashmem.h"

#define SHM_NAME "test_mem"
int main(int argc, char **argv) {
    int shID = ashmem_create_region(SHM_NAME, 2);
    if (shID < 0)
    {
        perror("ashmem_create_region failed\n");
        return 1;
    }
    // right here /dev/ashmem/test_mem is deleted
    printf("ashmem_create_region: %d\n", shID);
    char *sh_buffer = (char*)mmap(NULL, 2, PROT_READ | PROT_WRITE, MAP_SHARED, shID, 0);
    if (sh_buffer == (char*)-1)
    {
        perror("mmap failed");
        return 1;
    }
    printf("PID=%d", getpid());
    do
    {
        printf("VALUE = 0x%x\n", sh_buffer[0]);
    }
    while (getchar());
    return 0;
}

write_mem.c

// write_mem.c
#include <stdio.h>
#include <errno.h>
#include <sys/mman.h>
#include "ashmem.h"

#define SHM_NAME "test_mem"
int main(int argc, char **argv) {
    int shID = ashmem_create_region(SHM_NAME, 2);
    if (shID < 0)
    {
        perror("ashmem_create_region failed\n");
        return 1;
    }
    printf("ashmem_create_region: %d\n", shID);
    char *sh_buffer = (char*)mmap(NULL, 2, PROT_READ | PROT_WRITE, MAP_SHARED, shID, 0);
    if (sh_buffer == (char*)-1)
    {
        perror("mmap failed");
        return 1;
    }
    printf("PID=%d\n", getpid());
    int ch = getchar();
    sh_buffer[0] = ch;
    printf("Written 0x%x\n", ch);
    munmap(sh_buffer, 2);
    close(shID);
    return 0;
}

This is the output:
Reading

130|shell@mako:/data/local/tmp $ ./read_mem
ashmem_create_region: 3
PID=29655
VALUE = 0x0

Writing

shell@mako:/data/local/tmp $ ./write_mem
ashmem_create_region: 3
PID=29691
A
Written 0x41

Reading again VALUE = 0x0 (by pressing return)

Watching the maps of the reader:

shell@mako:/ $ cat /proc/29655/maps | grep test_mem
b6ef5000-b6ef6000 rw-s 00000000 00:04 116213     /dev/ashmem/test_mem (deleted)

as you can see test_mem is deleted WHILE read_mem is still alive.


Other Information

Both files are compiled as executable using the android ndk-buildcommand
Device: LG Nexus 4 (AOSP Lollypop)
I checked /dev/ashmem it exists.
ashmem taken from here

Eun
  • 4,146
  • 5
  • 30
  • 51
  • The map is still present, it just shows "(deleted)" in the label? – fadden Jan 08 '15 at 16:08
  • I am still able to read and write the buffer yes. But it seems like the data won't be written. `msync` makes no diffrence. – Eun Jan 08 '15 at 16:10

1 Answers1

32

Ashmem doesn't work like regular shared memory on Linux, and there is a good reason for it.

First, let's try to explain the "(deleted)" part, this is an implementation detail of how ashmem is implemented in the kernel. What it really means is that a file entry was created in the /dev/ashmem/ directory, then later removed, but that the corresponding i-node still exists because there is at least one open file-descriptor for it.

You could actually create several ashmem regions with the same name, and they would all appear as "/dev/ashmem/<name> (deleted)", but each one of them would correspond to a different i-node, and thus a different memory region. And if you look under /dev/ashmem/ you would see that the directory is still empty.

That's why the name of an ashmem region is really only used for debugging. There is no way to 'open' an existing region by name.

An ashmem i-node, and corresponding memory, is automatically reclaimed when the last file descriptor to it is closed. This is useful because it means that if your process dies due to a crash, the memory will be reclaimed by the kernel automatically. This is not the case with regular SysV shared memory (a crashing process just leaks the memory! Something unacceptable on an embedded system like Android).

Your test programs create two distinct ashmem regions with the same name, that's why they dont work as you think they should. What you need instead is:

1) Create a single ashmem region in one of the process.

2) Pass a new file descriptor to the region from the first process to the second one.

One way to do that is to fork the first process to create the second (this will automatically duplicate the file descriptors), but this is generally not a good idea under Android.

A better alternative is to use sendmsg() and recvmsg() to send the file descriptor through a Unix-domain socket between the two processes. This is generally tricky, but as an example, have a look at the SendFd() and ReceiveFd() functions in the following source file was written for the NDK:

https://android.googlesource.com/platform/ndk/+/android-5.0.0_r7/sources/android/crazy_linker/tests/test_util.h

Voila, hope this helps

Digit
  • 2,073
  • 1
  • 13
  • 10
  • But why are the fds in the output the same? – Eun Jan 09 '15 at 07:29
  • 3
    File descriptor numbers are process-specific. By default a command-line executable will have 3 file descriptors opened (0=stdin/1=stdout/2=stderr), so the first open() call is likely to return 3. – Digit Jan 10 '15 at 08:33
  • The example link is broken. Could you please fix it if you know the correct source – Sandeep Jul 28 '16 at 12:36
  • 1
    Sorry, it seems the crazy linker sources have been moved, here's a link to a previous commit of the same source that contains valid SendFd() and ReceiveFd() functions: https://android.googlesource.com/platform/ndk/+/android-5.0.0_r7/sources/android/crazy_linker/tests/test_util.h – Digit Aug 02 '16 at 07:07
  • 1
    @Digit Can you please point to reference where exactly the file entry addition and subsequent deletion is done on creating an ashmem region ? – Zoso May 13 '18 at 09:43
  • 2
    Upvoted because it describes the insides well, although there's an error here. There is no `/dev/ashmem/` folder, there is an `/dev/ashmem` device file, and there are `/dev/ashmem/ (deleted)` entries in `/proc//maps` which are just names. – domen Sep 05 '19 at 13:29
  • A good example of sendmsg/recvmsg for passing shmem file descriptors across Android processes is available in [android-shmem](https://github.com/pelya/android-shmem). – yugr Oct 28 '22 at 15:27