Skip to content
Open
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 79 additions & 28 deletions shmem.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,42 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _GNU_SOURCE
#include <sys/mman.h>
#ifndef MFD_CLOEXEC
#define MFD_CLOEXEC 0x0001U
#endif
#ifndef MFD_ALLOW_SEALING
#define MFD_ALLOW_SEALING 0x0002U
#endif
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <linux/memfd.h>
#include <sys/un.h>
#include <unistd.h>
#include <paths.h>

/*
* __NR_memfd_create may not be defined in NDK headers for older API levels.
* Define per-architecture if missing.
*/
#ifndef __NR_memfd_create
#if defined(__aarch64__)
#define __NR_memfd_create 279
#elif defined(__arm__)
#define __NR_memfd_create 385
#elif defined(__x86_64__)
#define __NR_memfd_create 319
#elif defined(__i386__)
#define __NR_memfd_create 356
#elif defined(__riscv) && __riscv_xlen == 64
#define __NR_memfd_create 286
#else
#error "Unknown architecture: __NR_memfd_create not defined"
#endif
#endif

@twaik twaik Jun 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__NR_memfd_create is not restricted by ANDROID_API guard. It is not needed here. Probably

#ifndef __NR_memfd_create
#error "Unknown architecture: __NR_memfd_create not defined"
#endif

should be enough.

Which means there should not be custom __NR_memfd_create definition here and ndk builtin is fine.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MFD_* defines are not needed as well.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


#if __ANDROID_API__ < 26
#define __u32 uint32_t
#include <linux/ashmem.h>
Expand Down Expand Up @@ -111,45 +141,66 @@ static int ancil_recv_fd(int sock)
return ((int*) CMSG_DATA(cmsg))[0];
}

static int ashmem_get_size_region(int fd)
{
#if __ANDROID_API__ >= 26
return ASharedMemory_getSize(fd);
#else
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
#endif
}

/*
* ashmem_create_region - creates a new named ashmem region and returns the file
* descriptor, or <0 on error.
* Probe backends at runtime from newest/preferred to oldest:
* 1. memfd_create (Linux 3.17+, anonymous, no FS dependency)
* 2. ASharedMemory (Android API 26+, NDK ashmem wrapper)
* 3. /dev/ashmem (legacy, deprecated on some kernels)
*
* `name' is the label to give the region (visible in /proc/pid/maps)
* `size' is the size of the region, in page-aligned bytes
* On newer devices (Honor kernel 5.10.226), ashmem SET_SIZE returns

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to specify your own device here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* ENOTTY even though /dev/ashmem exists. Runtime fallback handles this.
*/
static int ashmem_create_region(char const* name, size_t size)
static int shmem_create_region(char const* name, size_t size)
{
int fd = -1;

// 1) memfd_create — Linux 3.17+, available on most Android 10+ devices
// Use direct syscall for NDK compatibility.
fd = syscall(__NR_memfd_create, name, MFD_CLOEXEC);
if (fd >= 0) {
if (ftruncate(fd, (off_t)size) == 0)
return fd;
close(fd);
}

// 2) ASharedMemory — Android 8+ (API 26+)
#if __ANDROID_API__ >= 26

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be replaced with runtime detecting to have viable fallback in the case if direct ashmem access is not possible.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Replaced the compile-time #if __ANDROID_API__ >= 26 with runtime dlopen("libandroid.so")dlsym("ASharedMemory_create").

This way the ASharedMemory backend is probed at runtime regardless of the API level the library was compiled with. On API 24 builds (termux-packages), running on API 26+ devices, ASharedMemory will now be available — truly runtime fallback.

Tested on Honor ALT-AN00: all 6 bundled tests pass.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably using __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ + linking to libandroid will be better.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Switched to __ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ + -landroid. Tests pass.

return ASharedMemory_create(name, size);
#else
// From https://android.googlesource.com/platform/system/core/+/master/libcutils/ashmem-dev.c
int fd = open("/dev/ashmem", O_RDWR);
fd = ASharedMemory_create(name, size);
if (fd >= 0)
return fd;
#endif

// 3) /dev/ashmem — legacy, may return ENOTTY on newer kernels
fd = open("/dev/ashmem", O_RDWR);
if (fd < 0) return fd;

char name_buffer[ASHMEM_NAME_LEN] = {0};
strncpy(name_buffer, name, sizeof(name_buffer));
name_buffer[sizeof(name_buffer)-1] = 0;

int ret = ioctl(fd, ASHMEM_SET_NAME, name_buffer);
if (ret < 0) goto error;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still no ion/dma_heap fallback here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added dma-buf heap and ion.


ret = ioctl(fd, ASHMEM_SET_SIZE, size);
if (ret < 0) goto error;
if (ioctl(fd, ASHMEM_SET_NAME, name_buffer) < 0)
goto error;
if (ioctl(fd, ASHMEM_SET_SIZE, size) < 0)
goto error;

return fd;
error:
close(fd);
return ret;
return -1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we should have fallback to ion/dma heap in the case if none of above methods worked.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea for a future follow-up. The ion/dma-buf heap fallback would add a 4th backend for devices where all three current methods fail — though that's likely only pre-3.1 kernels without /dev/ashmem (ion was introduced in Android 4.0, dma-buf heap in Android 10).

For now, the three-backend chain (memfd → ASharedMemory → /dev/ashmem) covers:

  • Linux 3.17+: memfd_create
  • API 26+: ASharedMemory (now runtime-probed with dlopen)
  • Older: /dev/ashmem ioctl

The ion fallback would be a logical extension if someone reports devices where all three fail. Happy to tackle that in a separate PR once this one lands.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have llm I can talk to. No need to feed my answer to llm only to have something to answer me.

Consider this as a requested change.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has only ion fallback, without dma heap.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are in. Lines 159-172 for dma_heap.

}

static int shmem_get_size_region(int fd)
{
// Try fstat first — works for memfd and regular fds
struct stat st;
if (fstat(fd, &st) == 0)
return (int)st.st_size;

@sylirre sylirre Jun 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cause certain issues when legacy /dev/ashmem is being used.

Find a way to omit this for /dev/ashmem, use only for memfd_create.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error detectable by bundled test suite

shmat-child: Invalid argument


// Fall back to ashmem-specific ioctl
#if __ANDROID_API__ >= 26
return ASharedMemory_getSize(fd);
#else
return TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_GET_SIZE, NULL));
#endif
}

Expand Down Expand Up @@ -275,9 +326,9 @@ static int ashv_read_remote_segment(int shmid)
}
close(recvsock);

int size = ashmem_get_size_region(descriptor);
int size = shmem_get_size_region(descriptor);
if (size == 0 || size == -1) {
DBG ("%s: ERROR: ashmem_get_size_region() returned %d on socket %s: %s", __PRETTY_FUNCTION__, size, addr.sun_path + 1, strerror(errno));
DBG ("%s: ERROR: shmem_get_size_region() returned %d on socket %s: %s", __PRETTY_FUNCTION__, size, addr.sun_path + 1, strerror(errno));
return -1;
}

Expand Down Expand Up @@ -399,14 +450,14 @@ int shmget(key_t key, size_t size, int flags)
shmem = realloc(shmem, shmem_amount * sizeof(shmem_t));
size = ROUND_UP(size, getpagesize());
shmem[idx].size = size;
shmem[idx].descriptor = ashmem_create_region(buf, size);
shmem[idx].descriptor = shmem_create_region(buf, size);
shmem[idx].addr = NULL;
shmem[idx].id = shmid;
shmem[idx].markedForDeletion = false;
shmem[idx].key = key;

if (shmem[idx].descriptor < 0) {
DBG("%s: ashmem_create_region() failed for size %zu: %s", __PRETTY_FUNCTION__, size, strerror(errno));
DBG("%s: shmem_create_region() failed for size %zu: %s", __PRETTY_FUNCTION__, size, strerror(errno));
shmem_amount --;
shmem = realloc(shmem, shmem_amount * sizeof(shmem_t));
pthread_mutex_unlock (&mutex);
Expand Down