写入ashmem / 为什么安卓会释放ashmem?

7
我希望能够分享两个(ndk-)进程之间的数据。为此,我使用ashmem,使用以下源代码
一个进程不断读取(read_mem),另一个进程写入一次(write_mem)。

问题在于读取进程没有获得写入者的值。

并且

通过观察读取者的映射,我发现android在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;
}

这是输出结果:
读取中...
130|shell@mako:/data/local/tmp $ ./read_mem
ashmem_create_region: 3
PID=29655
VALUE = 0x0

写作

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

再次阅读 VALUE = 0x0 (按回车键)

观察读者的地图:

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

正如您所看到的,test_mem被删除了read_mem仍然存在。


其他信息

两个文件都使用android ndk-build命令编译为可执行文件
设备:LG Nexus 4 (AOSP Lollypop)
我检查了/dev/ashmem,它是存在的。
ashmem来源于这里


地图仍然存在,但标签中显示为“(已删除)”? - fadden
我仍然能够读写缓冲区,但似乎数据不会被写入。msync 没有任何作用。 - Eun
1个回答

32
Ashmem在Linux上不像普通的共享内存,这是有充分理由的。
首先,让我们试着解释 "(deleted)" 部分,这是ashmem在内核中实现的一个细节。它真正意味着的是在 /dev/ashmem/ 目录下创建了一个文件条目,然后稍后被删除,但相应的i-node仍然存在,因为至少有一个打开的文件描述符与之对应。
你实际上可以创建几个具有相同名称的ashmem区域,它们都将显示为 "/dev/ashmem/ (deleted)",但每个区域都对应于不同的i-node,因此是不同的内存区域。如果你查看 /dev/ashmem/ 下面,你会发现该目录仍然是空的。
这就是为什么ashmem区域的名称只用于调试的原因。没有办法通过名称“打开”现有区域。
当最后一个文件描述符关闭时,ashmem i-node和相应的内存会自动回收。这很有用,因为如果进程由于崩溃而死亡,内核将自动回收内存。这在常规SysV共享内存中不适用(崩溃的进程只会泄漏内存!这在像Android这样的嵌入式系统上是不可接受的)。
您的测试程序使用相同的名称创建了两个不同的ashmem区域,这就是它们不能按您想象的方式工作的原因。您需要的是:
1)在一个进程中创建单个ashmem区域。
2)从第一个进程向第二个进程传递新的文件描述符。
其中一种方法是fork第一个进程以创建第二个进程(这将自动复制文件描述符),但这通常不是在Android下的好主意。
更好的选择是使用sendmsg()和recvmsg()通过Unix域套接字在两个进程之间发送文件描述符。这通常很棘手,但是作为示例,请查看以下源文件中为NDK编写的SendFd()和ReceiveFd()函数:

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

看,希望这能有所帮助


3
文件描述符号是进程特定的。默认情况下,命令行可执行文件将打开3个文件描述符 (0=标准输入/1=标准输出/2=标准错误),因此第一个open()调用可能会返回3。 - Digit
示例链接已损坏。如果您知道正确的来源,请修复它。 - Sandeep
1
抱歉,疯狂链接器源代码已经移动,这里是同一源代码的先前提交的链接,包含有效的SendFd()和ReceiveFd()函数:https://android.googlesource.com/platform/ndk/+/android-5.0.0_r7/sources/android/crazy_linker/tests/test_util.h - Digit
1
@Digit,您能否指出在创建ashmem区域时确切进行文件条目添加和随后删除的参考文献? - Zoso
2
点赞是因为它很好地描述了内部情况,尽管这里有一个错误。没有 /dev/ashmem/ 文件夹,只有一个 /dev/ashmem 设备文件,在 /proc/<pid>/maps 中有 /dev/ashmem/<name> (deleted) 条目,它们只是名称。 - domen
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接